Repository navigation
refactor(ui-types): one source for the filter-builder field-type list, read by the schema, the interface and the renderer; close the three K4 review should-fixes #27369
Description
Filing gate ③ (maintainer-directed). This is the follow-up to K4 (#27145, PR #27329, merged 2026-10-29 03:51Z) of the objectui.org docs fold. In the "Restore objectui.org docs deploy" project thread on 2026-10-29, Tesla asked 「k4 从长远考虑应该怎么处理」. The dispatching seat answered: merge K4 as it is, then file one card that gives the filter-builder field-type list a single source and also takes the K4 review's three should-fixes. Tesla confirmed with 「K4 先合并,然后再单独派卡是吗」.
维护者速读
filter-builder 能接受的 14 种字段类型,现在由三处手写列表共同决定:校验 schema、TS 接口、K4 测试里冻结的那份列表。另外,渲染器里还有一份按行为划分的分组表。objectui#6939、#7562、#8774 修的都是这几份列表对不上的问题。本卡把渲染器的分组表搬到 @object-ui/types,作为唯一来源。schema、接口和渲染器都从这里引用,测试只校验两点:每个类型都有渲染分支,除 status(spec 里没有这个类型,单独列出豁免)外,每个类型都是 spec 的 FieldType。K4 合约评审的三条建议改项也一并处理。
Wanted
- One source. Move the renderer's behaviour buckets out of
ui/packages/components/src/custom/filter-builder.tsx:numberLikeTypes(number,currency,percent,rating),dateLikeTypes(date,datetime,time),selectLikeTypes(select,status) andlookupLikeTypes(lookup,master_detail,user). Put them in a module of@object-ui/types, which@object-ui/componentsalready depends on. Export from there the full accepted list, which is those buckets plustextandboolean, 14 members today.FilterFieldSchema.typeinui/packages/types/src/zod/complex.zod.tsbuilds itsz.enumfrom that list, and its.describe()stops citing "the published doc".FilterField.typeinui/packages/types/src/complex.tsis typed from the sameas constlist, not restated as a union.filter-builder.tsximports the buckets and declares none of its own.
- The pin moves.
ui/packages/types/src/__tests__/filter-builder-mirror-6939.test.tsdrops the frozenLAST_PUBLISHED_TYPESlist, which K4 added because its doc authority had gone. In its place it asserts that:- every member of the list has a branch in the renderer, using the existing per-member render measurement in
examples/schema-catalog/test/filter-builder-mirror-6939.test.tsx; - every member is a member of
FieldTypeinpackages/spec/src/data/field.zod.ts, except an explicitly named exemption list. On mainc4c515adexactly one member needs an exemption:statusis not a specFieldType. Exempt it with a one-line reason, and do not drop it, because the accepted set must not narrow.
Rewrite the header'sobjectstack#27145section to say where the authority now lives. The accepted set must not change: still 14 members,typestill optional,textstill the default.
- every member of the list has a branch in the renderer, using the existing per-member render measurement in
- K4 should-fix (1). The skill pointers in
skills/objectui/guides/app-composition.md:62andskills/objectui/rules/styling.md:318are prose URLs, and no gate checks them. Either turn them into a form thatcheck-skills-paths.mjsorcheck-doc-links.mjsalready judges, or addskillsto a gate's scan roots. State which in the PR. - K4 should-fix (2). At
.github/workflows/ci.yml:454the comment says "four roots outside ui/", but the list under it has five globs. Correct the comment. - K4 should-fix (3).
ui/AGENTS.md:106still tells contributors to updatecontent/docs/guide/*.md, which no longer exists. Point it atcontent/docs/frontend/.
These should-fixes are recorded in triage's PASS records on #27329 (6485237613 on 6a7c7794, 6486708135 on df07a80e).
Who acts
A domain:devx card thread. The file surface is:
ui/packages/types/src/**, the new module plus the zod and interface lines;ui/packages/components/src/custom/filter-builder.tsx;- the two
filter-builder-mirror-6939tests; skills/objectui/{guides/app-composition.md,rules/styling.md};ui/scripts/check-doc-links.mjsorui/scripts/check-skills-paths.mjsand their tests, only if item 3 needs them;.github/workflows/ci.yml, one comment line;ui/AGENTS.md;- a changeset.
Item 4 makes the PR Tier H by path: it stays a draft until green, the triage seat writes the contract review, and the maintainer merges.
Business reason. A metadata author who puts a filter-builder in a page relies on two things agreeing: the types that validation accepts and the types the renderer draws. Today a third hand-kept list sits between them. Three past cards (objectui#6939, #7562, #8774) each fixed a case where these lists had drifted apart, and validation either refused a column the renderer could draw or accepted a spelling it drew as plain text. A single source removes that class of bug. The three should-fixes leave a stale pointer for contributors and two links that no gate checks.
Acceptance
git grep -n "LAST_PUBLISHED_TYPES\|published doc's fourteen"prints nothing.- Adding a fifteenth member to the shared list, without a renderer branch, makes the mirror test red. Removing one member from a bucket narrows the schema, the interface and the renderer together. Record both as one-time ablations in the PR.
pnpm --dir ui exec vitest run packages/types examples/schema-catalogis green, and the Lint UI steps are green.- The four
components-complex-filter-builder/*catalog entries and the nestedsearch-interfaceentry still validate.
Duplicate search. I ran a semantic issue search for "filter-builder FilterField type vocabulary single source list" on objectstack-ai/objectstack. Its one hit, #24640 (closed, about filterableFields), is a different surface. New.
Size: S–M. Clause-②: no. Metadata-protocol: none.
Activity
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · os-tesla · session_01L9qibyAYPFxpNP4BCr7Z82
Claim: card thread "Fold objectui.org into objectstack docs", dispatch of #27369 (maintainer-directed: 「K4 先合并,然后再单独派卡是吗」, 2026-10-29 02:09Z, answering the seat's long-term proposal for the frozen filter-builder list)
Session:
session_01L9qibyAYPFxpNP4BCr7Z82(dispatching thread) · dev: a new card thread the project coordinator opens for this card; it names its own session in its os-dev-reportAccount:
os-teslaBranch: the dev card thread's platform-assigned
claude/project-thread-*branch (mode:thread; the only branch it pushes)Worktree: the dev card thread's own checkout
Domain:
domain:devxSeat:
domain:devx#1(seat 1 by default; dispatched on the maintainer's instruction from a card thread, not from a lane seat)File surface:
ui/packages/types/src/**(new shared module,zod/complex.zod.tsFilterFieldSchema.type,complex.tsFilterField.type,__tests__/filter-builder-mirror-6939.test.ts),ui/packages/components/src/custom/filter-builder.tsx(the four bucket declarations become imports),ui/examples/schema-catalog/test/filter-builder-mirror-6939.test.tsx,skills/objectui/guides/app-composition.md,skills/objectui/rules/styling.md,ui/scripts/{check-doc-links,check-skills-paths}.mjsand their tests (only if item 3 needs a scan-root row),.github/workflows/ci.yml(the one comment line at ~454),ui/AGENTS.md(line 106), one.changeset/*.md; ⛔ the accepted set does not change (14 members,typeoptional,textdefault; stop on breach)Container & model: M,
mode:thread,model: opus,effort: highClause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: none
Premise: holds (re-read on main
c4c515ad: K4 #27329 merged as 7587fd4;LAST_PUBLISHED_TYPESis inui/packages/types/src/__tests__/filter-builder-mirror-6939.test.ts; the four buckets are atfilter-builder.tsx:1207–1223;FilterFieldSchemaatzod/complex.zod.ts:582;statusis the one member outside specFieldType)Serial constraints cleared: none. Tier H by path (
.github/workflows/ci.yml): draft until green, the triage seat renders the contract review, GOVERNED_APPROVERS review, the maintainer merges, and the seat never enqueues it.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:devx#1 · os-tesla · session_01L9qibxvBS38kVpxSqdV8T4
os-dev-report
{ "issue": 27369, "status": "done", "branch": "claude/project-thread-jq1yxo", "pr": "https://github.com/objectstack-ai/objectstack/pull/27400", "session": "session_01L9qibxvBS38kVpxSqdV8T4", "premise_still_valid": true, "summary": "FILTER_FIELD_TYPES and its four behaviour buckets now live in @object-ui/types (filter-field-types.ts); FilterFieldSchema.type, FilterField['type'] and filter-builder.tsx all read them. The mirror test drops LAST_PUBLISHED_TYPES and pins spec FieldType membership (status exempted by name) plus, in the render half, a branch per member. K4 should-fixes: check-doc-links scans skills/objectui (new per-row diskRoot; the two prose URLs became links; heading scan now reads blockquoted headings), ci.yml comment says five paths, ui/AGENTS.md #2 points at content/docs/frontend/. Accepted set unchanged.", "tests": "head 796bf3a3 (parent 7dc5d3f6 differs only in styling.md): pnpm --dir ui exec vitest run packages/types examples/schema-catalog -> Test Files 429/429, Tests 12850/12850; check-doc-links.test.ts 123/123; type-check exit 0 for @object-ui/types, @object-ui/components, @object-ui/example-schema-catalog. Ablations (src-aliased, restored from HEAD, status clean): +'email' without bucket -> render half 1 red; +'zz_not_spec' -> spec check 1 red; -'rating' from number bucket -> schema pin red, FilterField TS2322, render probe red.", "gates": "dispatch-gates --commands: 88 derived; 87 run exit 0 (token ratchet exit 1 on 7dc5d3f6 fixed to 0 on 796bf3a3; check:skill-examples exit 0 after client-react build); check:dual-build-cjs-loads exit 3 NOT MEASURED (needs full dist; CI runs it). --ran: 88 accounted, 87 run, 1 NOT-MEASURED. Lint UI lint:root + ui package lint: 0 errors. List: /mnt/project-files/d1-merge/out/27369-gates-ran-796bf3a3.list. CI: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — issue_read x2, subscribe_pr_activity; no MCP write", "api_writes": "2 — relay pr_create (repository_dispatch, run 40821293906), this os-dev-report comment", "open_questions": [], "out_of_scope_findings": [] }