Skip to content

docs(spec): the view filter any-of group is described as live, not planned - #29534

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-aar2or
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-aar2or

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #28522

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:视图筛选「任一满足」组 { or: [...] } 的 spec 说明文字与 $or 拒收提示,去掉「[PLANNED] 尚无控制台读取 / 载入即被拒」的旧说法,改述现状;同步 liveness 注记与生成的参考文档。无行为改动。
  • 为什么改:控制台那一半已落地(Console list query and filter builder do not read a stored view filter's any-of group #28370,PR feat(console): a stored view filter's OR group filters the list, and the filter builder saves and reads it #28475),/meta/types 与拒收提示仍告诉作者这个形态不可用,把人引向两个视图或客户端变通。
  • 风险与回滚:仅文字;schema 接受与拒收的内容逐字节不变。回滚即 revert 本 PR。描述文字随 dist 与 src/**/*.zod.ts 发布,故带 patch changeset。
  • 先看:packages/spec/src/ui/view.zod.ts(ViewFilterOrGroupSchema 文档注释与 .describe)、packages/spec/src/ui/filter-rule-array.ts($or 拒收句)、packages/spec/liveness/view.json(list.filter 注记)。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/ui/view-filter-or-group.test.ts src/ui/filter-rule-array-guidance.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

What changes

The console now applies a view filter's any-of group: toFilterNode in ui/packages/core/src/utils/filter-converter.ts lowers { or: [rule, ...] } to one or node inside the list's AND, and the filter builder edits a filter that is exactly one group (builderRulesOfSpecFilter, the save fold in ui/packages/app-shell/src/views/viewFilterFold.ts). The spec still said the opposite. This PR corrects the prose only.

  • packages/spec/src/ui/view.zod.ts, ViewFilterOrGroupSchema: the ## [PLANNED — not yet rendered] docblock section becomes ## Applied by the console, which names the lowering and the builder's one-group rule. The .describe loses its [PLANNED — not yet rendered] prefix and the "No console reads it yet … refused when it loads" sentences; it now ends "The console applies it, ANDed with its sibling elements, and its filter builder edits a filter that is exactly one group."
  • packages/spec/src/ui/filter-rule-array.ts: the $or refusal still prescribes the any-of group element, without "(declared, but no console applies it yet, so it does not narrow the rows today)". The code comment no longer cites the PLANNED note.
  • packages/spec/liveness/view.json, props.list.children.filter.note: the "rides this live verdict at PLANNED strength" sentence is amended as it asked, citing the group read (toFilterNode via viewFilterOrGroupToNode, reached from ListView.tsx buildEffectiveFilter through mergeFilterNodes and from ObjectView.tsx; the builder fold and its read-back). Status stays live.
  • packages/spec/src/conversions/registry.ts, dollarKeysReason: the TODO reason that the record-form filter conversion gives for a $or record drops "(declared, not yet applied by any console)".
  • content/docs/references/ui/{view,page,component}.mdx: regenerated by gen:docs. Each changed line is only this describe.
  • .changeset/28522-view-or-group-live.md: @objectstack/spec patch.

Why

Every author, human or AI, who reads /meta/types for a view filter, or who writes a MongoDB-style $or and gets the parse refusal, was told the group is not applied and that a view storing one fails to load. That steered them away from the one shape that works.

Tests

No behaviour changed, so there is no new behaviour test and no ablation. Two pins held the old wording and are updated:

  • packages/spec/src/ui/view-filter-or-group.test.ts: the describe pin now asserts the description starts with "An any-of group:" and carries no [PLANNED marker (it asserted the marker before).
  • packages/spec/src/ui/filter-rule-array-guidance.test.ts: the toContain('no console applies it yet') line is removed; the pin that the refusal names the any-of group element stays.

Acceptance notes

  • The liveness note edit is outside the claim's listed file surface but inside the card's Wanted ("Any liveness / marker register row that lists this key as planned moves with it"). No other register lists the key as planned. A line grep missed one more copy of the false status: dollarKeysReason in packages/spec/src/conversions/registry.ts splits it across two string literals. The contract review found it (record 6585135393), and it is fixed here. A re-sweep with literals and comment lines joined finds no other copy.
  • .changeset/28020-view-filter-or-group.md and packages/spec/CHANGELOG.md keep their wording; both are release-owned history.
  • Main was merged before opening (16 commits, including PR feat(spec)!: a view no longer declares itself personal — scope 'personal' and sharing.type retire #29447 at other view.zod.ts hunks). view.mdx was regenerated from the merged tree; the branch's delta vs origin/main is exactly the nine files above.

Verification

Patch round 1: head 6966a0b17 (f42d8a2e + origin/main merged again): 105 derived, 104 exit 0, check:dual-build-cjs-loads NOT MEASURED (exit 3; CI runs it); spec 794 files / 23549 tests pass; typecheck 0. Contract re-review PASS 6586088767 on this head.

Round 0 readings, kept below, were at head b71e28d33 (base 94d3e254, then origin/main c88467d4a merged in).

dispatch-gates: 104 derived, 104 exit 0
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at b71e28d33: 104 commands, each run from a runner with its exit code captured before any pipe.
  • All 104 exited 0. Reconciliation: dispatch-gates --ran printed "104 derived famil(ies) accounted for — 104 run, 0 NOT-MEASURED".
  • Prerequisites built first under the verify lock: turbo run build for formula, lint, client, client-react and objectql; the pinned fixture commit of check-plugin-teardown-shape --self-test fetched.
  • An earlier pass at 70500e67, before the merge, read 103 exit 0 and check:dual-build-cjs-loads exit 3 (prerequisite); the final pass measured it at exit 0.
spec package: test, typecheck, generated artifacts
  • pnpm --filter @objectstack/spec test: Test Files 792 passed (792), Tests 23522 passed, 1 todo. Exit 0.
  • pnpm --filter @objectstack/spec typecheck: exit 0.
  • pnpm --filter @objectstack/spec check:generated --fix after the merge: 1 of 14 stale (content/docs/references/**), regenerated and committed. The diff of view.mdx against origin/main is only the five describe lines.
  • Published text: after the build, packages/spec/dist carries "The console applies it, ANDed with its" (chunk-HDN2DLYE.js, chunk-M3JDHXOJ.mjs) and 0 files carry "No console reads it yet". view.zod.ts and liveness/ also ship through files[], so the changeset is patch, not skip-changeset.

Generated by Claude Code

The console half landed (toFilterNode lowers a group to one or node
inside the list's AND, and the builder edits a one-group filter), so
ViewFilterOrGroupSchema's PLANNED docblock and describe, the $or refusal
prose in filter-rule-array.ts and the view liveness note now state the
live behaviour. Tests that pinned the old wording are updated.

Claude-Session: https://claude.ai/code/session_01JQwxbYCMdGp2Cju6A39cTB
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:ui tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/liveness/view.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/view.json) — pages documenting those are invisible to this run
  • 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); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 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 8e505db8178cedbed174049b211cc409b22adaea → packageMentionDocs.

Which tree this was computed on

This run read content/docs from bf01f67849ebb3db8571bb36ec7dc97b0bca28f5 — the merge of head 6966a0b17784d89c8aa016f37b64d6aa417096a8 into base 8e505db8178cedbed174049b211cc409b22adaea, 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 bf01f67849ebb3db8571bb36ec7dc97b0bca28f5 && git checkout bf01f67849ebb3db8571bb36ec7dc97b0bca28f5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8e505db8178cedbed174049b211cc409b22adaea 6966a0b17784d89c8aa016f37b64d6aa417096a8 && git checkout -B drift-repro 8e505db8178cedbed174049b211cc409b22adaea && git merge --no-ff 6966a0b17784d89c8aa016f37b64d6aa417096a8

node scripts/docs-audit/affected-docs.mjs --json 8e505db8178cedbed174049b211cc409b22adaea

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#5 · os-warren · session_01JQwxbWR3K3h7i1ytwycVg1

Contract-review-claim: session_01JQwxbWR3K3h7i1ytwycVg1 · head b71e28d · 2026-11-02T18:27Z

The published-schema face (packages/spec/src/ui/view.zod.ts, packages/spec/src/ui/filter-rule-array.ts) is reviewed at CONTRACT_REVIEW_TIER by an isolated at-tier reviewer the dispatching seat spawns; it is not the drafter and is fed only the card, the PR and the check-runs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#5 · os-warren · session_01JQwxbWR3K3h7i1ytwycVg1

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b71e28d33c7a050a0d8b4f8847b81bbffa13a86d
Local-runs: none
Scope: packages/spec/src/ui/view.zod.ts, packages/spec/src/ui/filter-rule-array.ts + packages/spec/src/conversions/registry.ts, packages/spec/liveness/view.json, packages/spec/src/ui/view-filter-or-group.test.ts, packages/spec/src/ui/filter-rule-array-guidance.test.ts, packages/spec/src/data/filter.zod.ts, ui/packages/core/src/utils/filter-converter.ts, ui/packages/app-shell/src/views/viewFilterFold.ts, ui/packages/app-shell/src/views/metadata-admin/widgets.tsx, ui/packages/plugin-view/src/config/view-config-utils.ts, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-list/src/ListView.tsx, ui/packages/plugin-grid/src/objectGridQueryInputs.ts, ui/packages/app-shell/src/views/viewFilterOrGroup.roundTrip-28370.test.tsx

Face: schema (packages/spec/src/ui/view.zod.ts is a published schema path). Inputs read: card #28522 body and all four comments (two triage posts, the claim, the os-dev report), PR #29534 body and nine-file list, the net diff against the merge-base c88467d4 on main (9 files, +53 / −42, byte-identical to the file list), and the sixty-five check-runs on the head. The rest of the diff (the three generated content/docs/references/ui/*.mdx pages, the changeset, the two test pins, the liveness note) is the lane seat's ACCEPT at TIER_DEFAULT; the cited code was read, never reviewed.

Gate verdicts on the head, read off the check-runs: Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI, Build & E2E UI, Live E2E UI all success; Lint UI concluded skipped (no ui/ path in the diff), a neutral conclusion, not a red; Spec property liveness, Check Changeset, Check PR Size, PR-body guards and Build Docs all success. No required context is red or in progress.

① Derived judgments

  • Accept-set: unchanged — right. ViewFilterOrGroupSchema keeps its strictObject, its non-empty or array, its { or: [] } refusal and its stray-key guidance; ViewFilterElementSchema's union and reportClaimedFilterElementBranch are untouched; ruleArrayFilterError still refuses a top-level $or with invalid_type and the same first sentence. Only prose bodies move. The changeset's "the schema accepts and refuses exactly what it did" is true of the tree.
  • Public surface: the .describe text of ViewFilterOrGroupSchema changes — right, and fully regenerated. The description ships in dist, json-schema/ and /meta/types, and is projected into fifteen nested-shape blocks across content/docs/references/ui/{component,page,view}.mdx (every changed line in those three files is this one describe, nothing else). check:docs sits inside the green TypeScript Type Check job, so the committed projection matches the source. No export is added, removed or renamed, so no api-surface movement — right.
  • Runtime string: the $or refusal sentence in filter-rule-array.ts loses its parenthetical — right. The sentence still names the any-of group element as the one spelling of $or over flat rules, still refuses to flatten, and carries no tracker number.
  • Register: packages/spec/liveness/view.json props.list.children.filter.note amended, status stays live — right. The old note asked to be amended "citing the group read, when it lands"; the new note cites toFilterNode via viewFilterOrGroupToNode, the ListView.tsx buildEffectiveFilter path through mergeFilterNodes, ObjectView.tsx, the save fold and builderRulesOfSpecFilter, each of which I read on origin/main (below). The liveness gate is green.
  • Test pins: two re-pinned — right. view-filter-or-group.test.ts now asserts the description starts with An any-of group: and carries no [PLANNED; filter-rule-array-guidance.test.ts drops the no console applies it yet assertion and keeps the any-of-group-element pin. Both pins hold the card's filing gate (the false status is gone), not merely the absence of a word.
  • Sibling copy of the same false status left in the package — wrong (omission). packages/spec/src/conversions/registry.ts, function dollarKeysReason, lines 13930–13931 on this head, still reads "$or over flat rules only an any-of group element { or: [rule, ...] } (declared, not yet applied by any console) that this conversion does not write". Judged in ④ as must-fix.

② Semver level

.changeset/28522-view-or-group-live.md: '@objectstack/spec': patch, body carrying Clause-②: no and Metadata-protocol: none, no arm; the PR body carries the same two lines. Matches what the diff publishes: nothing an author can write changes (none ⇒ patch under the Post-Task Checklist's level rule), and skip-changeset would be wrong because the describe text ships in dist and view.zod.ts itself ships through the package's files[] (src/**/*.zod.ts). Check Changeset is green. No ADR-0087 disposition is owed (nothing retired, no conversion entry). Level and declarations: right.

③ Boundary flags

  • Dev deviation 1 — packages/spec/liveness/view.json edited outside the claim's listed file surface: answered. The card's Wanted names it ("Any liveness / marker register row that lists this key as planned moves with it"), the edit is the amendment the old note itself requested, and ④ reads it. Accepted.
  • Dev deviation 2 — PR template headings folded into the os-dev section order: a PR-body matter, not a tree matter; nothing to judge here. Not escalated.
  • Dev deviation 3 — main merged before the PR opened (sixteen commits, including PR feat(spec)!: a view no longer declares itself personal — scope 'personal' and sharing.type retire #29447's disjoint view.zod.ts hunks) and view.mdx regenerated from the merged tree: verified. git diff from the merge-base c88467d4 to the head is exactly the nine listed files, and the view.zod.ts hunks are L1163–L1175 and L1214–L1223 alone.
  • open_questions: empty in the report. None raised by this review beyond the ④ must-fix, which is a finding, not a question.
  • The PR is still a draft; landing is the owning seat's act and outside this record.

④ Design judgment

packages/spec/src/ui/view.zod.ts — holds against the code it cites; one imprecision noted. Read on origin/main:

  • "toFilterNode lowers a group to one or node of its rules, a child of the list's AND like any rule" — true. ui/packages/core/src/utils/filter-converter.ts: toFilterNode maps an array that carriesViewFilterElement through lowerViewFilterElement, which routes an isViewFilterOrGroup element to viewFilterOrGroupToNode, which lowers each member through viewFilterRuleToNode and returns ['or', ...nodes]; mergeFilterNodes wraps the surviving sources in one ['and', ...]. The server accepts the node: packages/spec/src/data/filter.zod.ts isFilterAST admits ["and" | "or", ...children]. Consumers reach that sink from ListView.tsx buildEffectiveFilter (through mergeFilterNodes), ObjectView.tsx (mergeFilterNodes(authoredFilters.view || authoredFilters.table), and toFilterNodeSafely on the grid rung), objectGridQueryInputs.ts (toFilterNodeSafely), and the related-list, line-items, picker, lookup and element-data-source paths — every filter slot the describe is projected onto lowers through the one sink, so "the console applies it, ANDed with its sibling elements" is true of every nested-shape block the generated reference carries.
  • "The console's filter builder edits a filter that is exactly one group as its OR logic, and saves its OR logic over two or more rules as one group" — true. viewFilterFold.ts foldFilterGroupToSpecRules: when logic is 'or' and the rule count is greater than one it returns { ok: true, rules: [{ or: rules }] }, otherwise the rules flat. filter-converter.ts builderRulesOfSpecFilter: a list with no group is AND, exactly one group is { logic: 'or', rules: only.or }, anything else null.
  • "a filter holding a group beside other elements is applied, and shown read-only in the builder rather than flattened on save (builderRulesOfSpecFilter)" — true. Applied: toFilterNode lowers the group in place regardless of its siblings. Read-only: metadata-admin/widgets.tsx FilterBuilderField sets notEditable = shown === null and the comment says "shown, never edited"; the pin is viewFilterOrGroup.roundTrip-28370.test.tsx (filterOrGroupNotEditable title). The runtime toolbar's userFilterUrlState.ts folds the user filter with logic: 'and' into URL state and never writes the stored view filter, so no second save path can flatten a mixed list.
  • Note, no fix owed: for a group with ONE member, viewFilterOrGroupToNode returns that member's node bare (nodes.length === 1 ? nodes[0] : ['or', ...nodes]), so "one or node" is literal only for two or more members. The rows selected are identical, which is what the sentence is read for.
  • The describe keeps "the list still ANDs every element" and the two-rule example, both still true; the new closing sentence makes the one claim the card asked for and no wider one.

packages/spec/src/ui/filter-rule-array.ts — holds, with the scenario it is accepted against half-met in the package. One must-fix.

  • The hunk itself is right: the refusal keeps naming the any-of group element as the spelling of $or over flat rules, drops the "(declared, but no console applies it yet, so it does not narrow the rows today)" status, and its code comment no longer cites a PLANNED note. The guidance test re-pins the sentence.
  • Must-fix — packages/spec/src/conversions/registry.ts L13930–13931 (dollarKeysReason) still states the same false status in the same words' sibling form: "(declared, not yet applied by any console)". This is the TODO reason the ADR-0087 record-form filter conversion writes for an author whose stored MongoDB-form filter carries $or — the very author and the very $or shape the card's filing gate names ("who writes a MongoDB-form $or and gets the parse refusal"), met one step earlier on the migration path instead of at the parse. The file is tracked, lives under packages/spec/src and ships in dist; the wording was introduced in efb1620c, the tree the card was filed against, so it was present when the card named its two sites; no test pins it (one occurrence in the whole head). After this PR the package tells the same author, on one path, that the group is live and, on the other, that no console applies it. The scenario the hits are accepted against — the $or refusal no longer points authors away from the shape that works — therefore does not hold across the package the spec lane owns. The PR's acceptance note reports a sweep for "the marker and the old sentences" that found nothing; the tree contradicts it because this copy is worded differently, not because it is absent. Remedy: delete the parenthetical "(declared, not yet applied by any console)" in dollarKeysReason (two words of prose), and pin it in the registry's conversion test if one asserts that reason text. This is a FAIL under the rule that a must-fix in ④ is a FAIL; the hits files themselves need no further change.

Not judged here: packages/spec/src/ai/solution-blueprint.zod.ts L173 ("Flat rules only: the any-of group element ListView.filter takes is deliberately NOT admitted here") describes the blueprint's own narrower accept-set, not the group's console status, and is correct as written. view.zod.ts L2303's "planned marker on its describe" belongs to a different key.

Implemented-by: claude/project-thread-aar2or
Reviewed-by: session_01JQwxbWR3K3h7i1ytwycVg1

VERDICT: FAIL

… its stale status

dollarKeysReason still told a $or author the any-of group element is
"declared, not yet applied by any console"; the console applies it.

Claude-Session: https://claude.ai/code/session_01JQwxbYCMdGp2Cju6A39cTB
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#5 · os-warren · session_01JQwxbWR3K3h7i1ytwycVg1

Contract-review-claim: session_01JQwxbWR3K3h7i1ytwycVg1 · head 6966a0b · 2026-11-02T19:06Z

Re-review after the patch round on FAIL record 6585135393 (f42d8a2e + merge of main). The published-schema face (view.zod.ts, filter-rule-array.ts, conversions/registry.ts) is reviewed at CONTRACT_REVIEW_TIER by a fresh isolated at-tier reviewer the dispatching seat spawns, fed only the card, the PR and the check-runs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#5 · os-warren · session_01JQwxbWR3K3h7i1ytwycVg1

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6966a0b17784d89c8aa016f37b64d6aa417096a8
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/ui/filter-rule-array.ts, packages/spec/src/ui/view.zod.ts + packages/spec/liveness/view.json, packages/spec/src/ui/view-filter-or-group.test.ts, packages/spec/src/ui/filter-rule-array-guidance.test.ts, packages/spec/src/conversions/page-component-filter-record-to-rule-array.test.ts, packages/spec/src/data/filter.zod.ts, packages/spec/src/ai/solution-blueprint.zod.ts, ui/packages/core/src/utils/filter-converter.ts, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-grid/src/objectGridQueryInputs.ts, ui/packages/plugin-list/src/ListView.tsx, ui/packages/app-shell/src/views/viewFilterFold.ts, ui/packages/app-shell/src/views/metadata-admin/widgets.tsx, ui/packages/app-shell/src/views/viewFilterOrGroup.roundTrip-28370.test.tsx

Face: schema (packages/spec/src/ui/view.zod.ts and packages/spec/src/ui/filter-rule-array.ts are published schema paths; packages/spec/src/conversions/registry.ts ships in dist). Re-review of the head that follows FAIL record 6585135393 on b71e28d3: the must-fix is judged first, everything else fresh. Inputs read: card #28522 body and all five comments (two triage posts, the claim, the os-dev report, the claim amendment), PR #29534 body, its ten-file list and all four comments (the docs-drift bot, two contract-review claims, the earlier FAIL record; no review comments, no reviews), the net diff from the merge-base 8e505db8 on main to the head (10 files, +55 / −44, byte-identical to the file list), and the sixty-five check-runs on the head. The rest of the diff (the three generated content/docs/references/ui/*.mdx pages, the changeset, the two test pins, the liveness note) is the lane seat's ACCEPT at TIER_DEFAULT; the cited code was read, never reviewed.

Gate verdicts on the head, read off the check-runs: Lint & Repo Gates, TypeScript Type Check, Test Core and its eight shards, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI and its sixteen shards, Build & E2E UI, Live E2E UI, Build UI Deps, Build Docs, Spec property liveness, Check Changeset, Check PR Size, PR-body guards, Check Documentation Links, Dogfood Regression Gate, every Lint · and Type Check · sub-gate, filter, Auto Label and Flag docs affected by code changes all success. Lint UI and the UI-artifact, dogfood-shard, temporal, packed-tarball and 18.x opt-in jobs concluded skipped (no ui/ path in the diff; opt-in lanes), a neutral conclusion, not a red. No required context is red or in progress.

① Derived judgments

  • Must-fix of record 6585135393 resolved — right. packages/spec/src/conversions/registry.ts dollarKeysReason (head L13928–13934) no longer carries "(declared, not yet applied by any console)"; the sentence now reads "$or over flat rules only an any-of group element { or: [rule, ...] } that this conversion does not write, and $not has no rule spelling". The claim it keeps is true of the tree: recordFilterToRules (head L13972–13974) declines on any top-level $ key before reading a field, so the conversion never writes a group. No test pinned the deleted parenthetical; the pins that exist (page-component-filter-record-to-rule-array.test.ts L627–631, L682) assert only "carries the combinator $or" and its variants and are untouched and green.
  • Accept-set: unchanged — right. ViewFilterOrGroupSchema keeps its strictObject, its non-empty or array, its { or: [] } refusal and its stray-key guidance; ViewFilterElementSchema's union and the claimed-branch reporter are untouched; ruleArrayFilterError still refuses a top-level $or with invalid_type and the same first sentence; the ADR-0087 record-form conversion still declines the same inputs with the same verdict. Only prose bodies move. The changeset's "the schema accepts and refuses exactly what it did" is true of the tree.
  • Public surface: the .describe text of ViewFilterOrGroupSchema changes — right, and fully projected. The description ships in dist, in view.zod.ts itself (files[] carries src/**/*.zod.ts) and in /meta/types, and is projected into eighteen nested-shape blocks across content/docs/references/ui/{component,page,view}.mdx; the uniq of the mdx hunks is exactly one removed line (the old describe) and one added line (the new describe), eighteen times each, nothing else. The committed json-schema/ directory carries neither the old nor the new describe (zero hits for both on the head), so no projection is missing. No export is added, removed or renamed: no api-surface movement — right.
  • Runtime strings: two lose a false status — right. The $or parse refusal in filter-rule-array.ts and the conversion TODO reason in registry.ts both still name the any-of group element as the one spelling of $or over flat rules, both still refuse to flatten, and neither carries a tracker number.
  • Register: packages/spec/liveness/view.json props.list.children.filter.note amended, status stays live — right. The old note asked to be amended "citing the group read, when it lands"; the new note cites toFilterNode via viewFilterOrGroupToNode, ListView.tsx buildEffectiveFilter through mergeFilterNodes, ObjectView.tsx, the save fold and builderRulesOfSpecFilter; every one of those citations was read on origin/main (④). Spec property liveness is green.
  • Test pins: two re-pinned — right. view-filter-or-group.test.ts asserts the description starts with An any-of group: and carries no [PLANNED; filter-rule-array-guidance.test.ts drops the no console applies it yet assertion and keeps the any-of-group-element pin. Both pin the card's filing gate (the false status is gone), not the mere absence of a word.
  • Sweep for a further surviving copy: none — right. Over the whole head tree, the exact old sentences ("no console applies", "not yet applied by any console", "No console reads it yet", "[PLANNED — not yet rendered]") survive only in .changeset/28020-*, .changeset/28102-*, packages/spec/CHANGELOG.md (release-owned history) and content/docs/references/ui/app.mdx, where the marker belongs to the navigation entry's dataSource key, a different key with its own status. Within packages/spec, no line pairing a group term (any-of, { or, $or, ViewFilterOr) with a status word (PLANNED, no console, not yet, not applied, does not narrow, refused when it loads, no reader, declared) remains outside the liveness README's generic vocabulary table. view.zod.ts L2303's "planned marker" belongs to emptyState.link; solution-blueprint.zod.ts L173 describes the blueprint's own narrower accept-set, not the group's console status, and is correct as written.

② Semver level

.changeset/28522-view-or-group-live.md: '@objectstack/spec': patch, body carrying Clause-②: no and Metadata-protocol: none, no arm; the PR body carries the same two lines. Matches what the diff publishes: nothing an author can write changes (none ⇒ patch under the Post-Task Checklist's level rule), and skip-changeset would be wrong because the describe ships in dist, view.zod.ts ships through files[], and registry.ts ships in dist. The changeset body names the two author-facing texts it corrects; it does not name the third (the conversion TODO reason) added in the patch round — a release-note omission, not a level or declaration error, and the level is unaffected. Check Changeset is green. No ADR-0087 disposition is owed (nothing retired, no conversion entry added or changed in behaviour). Level and declarations: right.

③ Boundary flags

  • Dev deviation 1 — packages/spec/liveness/view.json edited outside the claim's listed file surface: answered. The card's Wanted names it, the edit is the amendment the old note itself requested, and ④ reads it. Accepted.
  • Dev deviation 2 — PR template headings folded into the os-dev section order: a PR-body matter, not a tree matter. Not escalated.
  • Dev deviation 3 — main merged before the PR opened and again after the patch round; view.mdx regenerated from the merged tree: verified. The three-dot diff from the merge-base 8e505db8 to the head is exactly the ten listed files; the view.zod.ts hunks are L1163–L1175 and L1214–L1223 alone.
  • Claim amendment 6585150110 widens the file surface to registry.ts (dollarKeysReason only) and any test pinning that reason: the head's registry.ts hunk is that one function (two lines), and no test needed re-pinning. Consistent with the diff.
  • open_questions: empty in the os-dev report; none raised here.
  • The PR is a draft; landing is the owning seat's act and outside this record.

④ Design judgment

packages/spec/src/conversions/registry.ts — holds. The hunk is the deletion the FAIL record asked for. The sentence that remains makes one claim about this conversion ("that this conversion does not write") and one about the rule array ("a combinator is never flattened"); both are true on main: recordFilterToRules returns { declined } on any $ key, and the entry's docblock (L14177–14185) and summary (L14255) say the same. The reason text is consumed by context.reportTodo and read by an operator at os migrate meta --stored; it no longer contradicts the parse refusal that the same author meets one step later. The scenario the FAIL record named — the same author told on one path that the group is live and on the other that no console applies it — no longer exists on this head.

packages/spec/src/ui/view.zod.ts — holds against the code it cites. Read on origin/main:

  • "toFilterNode lowers a group to one or node of its rules, a child of the list's AND like any rule, so a group is ANDed with its sibling elements" — true. filter-converter.ts L1490–1500: an array that carriesViewFilterElement is mapped through lowerViewFilterElement, which routes an isViewFilterOrGroup element to viewFilterOrGroupToNode (L1406–1411), which lowers each member through viewFilterRuleToNode and returns ['or', ...nodes]; the lowered array is the legacy flat array isFilterAST admits as an implicit AND (filter.zod.ts L2870–2873), and mergeFilterNodes (L1574–1581) wraps surviving sources in one explicit ['and', ...]. The server admits the logical node (isFilterAST L2857–2862).
  • Consumers reach that sink from ListView.tsx buildEffectiveFilter (L923–938, through mergeFilterNodes), ObjectView.tsx L1503 (mergeFilterNodes(authoredFilters.view || authoredFilters.table)) and its grid rung (L2696, filter: viewFilter || gridTableFilterRung(...), lowered in objectGridQueryInputs.ts L124 through toFilterNodeSafely). The card names ui/packages/plugin-grid/src/objectGridQuery.ts; on main that file carries no lowering call, the grid's sink is objectGridQueryInputs.ts. A citation difference in the card, not in the diff; the liveness note cites the files that do lower.
  • "The console's filter builder edits a filter that is exactly one group as its OR logic, and saves its OR logic over two or more rules as one group" — true. viewFilterFold.ts L227–228: logic === 'or' with more than one rule returns [{ or: rules }], otherwise the rules flat. builderRulesOfSpecFilter (L1606–1611): no group is AND, exactly one group is { logic: 'or', rules: only.or }, anything else null. Pinned by viewFilterOrGroup.roundTrip-28370.test.tsx (the fold, the read-back, the round trip, the AND control, the widget's read and write).
  • "a filter holding a group beside other elements is applied, and shown read-only in the builder rather than flattened on save" — true. Applied: toFilterNode lowers the group in place whatever its siblings. Read-only: widgets.tsx L2814–2815 notEditable = shown === null, the trigger disabled with the filterOrGroupNotEditable title (L2875–2876); pinned at the round-trip test's "a group beside another condition is shown read-only" case. FilterBuilderField is the one builder for the view tab filter (L2589) and the Studio's generic FilterBuilderWidget (L2905), so the describe's "its filter builder" sentence holds on the carriers the describe is projected onto.
  • Note, no fix owed: for a group with ONE member viewFilterOrGroupToNode returns that member's node bare (L1410), so "one or node" is literal only for two or more members; the rows selected are identical, which is what the sentence is read for.
  • The describe keeps "the list still ANDs every element" and the two-rule example, both still true; its closing sentence makes the claim the card asked for and no wider one.

packages/spec/src/ui/filter-rule-array.ts — holds, and the scenario it is accepted against now holds across the package. The refusal names the any-of group element as the spelling of $or over flat rules, drops the "(declared, but no console applies it yet, so it does not narrow the rows today)" status, and its code comment cites ViewFilterOrGroupSchema as the shape, not a PLANNED note. No status word (planned, console, not yet, today) remains anywhere in the file. With the registry.ts deletion, the $or author is told the same thing on the migration path and at the parse: write the group.

Findings: none must-fix, none should-fix. Three notes recorded above (one-member group lowering is bare, the card's objectGridQuery.ts citation names a neighbour of the grid's actual sink, the changeset body does not name the third corrected text); none is a false sentence in the tree.

Implemented-by: claude/project-thread-aar2or
Reviewed-by: session_01JQwxbWR3K3h7i1ytwycVg1

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#5 · os-warren · session_01JQwxbWR3K3h7i1ytwycVg1

维护者速读

  • 改了什么:视图过滤器 any-of 组 { or: [rule, ...] } 的 schema 文档(view.zod.ts docblock 与 describe)、$or 拒收提示(filter-rule-array.ts)、记录表单过滤转换的 $or 理由(conversions/registry.ts)去掉「PLANNED / 无控制台应用」的过时说法,改述现行行为;再生成参考文档。
  • 为什么改:Console list query and filter builder do not read a stored view filter's any-of group #28370 已由 PR feat(console): a stored view filter's OR group filters the list, and the filter builder saves and reads it #28475 落地,控制台会把该组降为 AND 内的一个 or 节点;旧文字把作者引离唯一可用的写法。
  • 风险与回滚:纯文字(+55 / -44),接受集不变;回滚即 revert。
  • 先看:packages/spec/src/ui/view.zod.ts 的 ViewFilterOrGroupSchema 一段。
  • 两分钟验证:git grep -n "not yet rendered\|no console" packages/spec/src/ui 在本分支无命中。
  • 需要你做的:无,席位落地。
  • 席位意见:第一轮 at-tier 审查 FAIL(6585135393,registry.ts 残留一句),补丁后重审 PASS(6586088767);全树扫过无残留。

席位核对

ACCEPT · domain:spec#5 · head 6966a0b17784d89c8aa016f37b64d6aa417096a8 · 2026-11-02T19:13Z

  1. Shape: ten files inside claim 6582625485 + amendment 6585150110: view.zod.ts, filter-rule-array.ts, conversions/registry.ts (the dollarKeysReason parenthetical only), two re-pinned tests, liveness/view.json note, three generated ui reference mdx, .changeset/28522-view-or-group-live.md (@objectstack/spec patch). Draft, base main, Fixes #28522.
  2. Premise: the any-of group is live on main: filter-converter.ts lowers it to an or node inside the list's AND, ObjectView.tsx and the grid query inputs send it, isFilterAST admits it, the builder folds an OR toggle into one group (viewFilterFold.ts), pinned by viewFilterOrGroup.roundTrip-28370.test.tsx.
  3. Gates: 65 check-runs on this head, 57 success, 8 skipped (Lint UI: no ui/ path; the rest opt-in or path-scheduled); Lint & Repo Gates, TypeScript Type Check, Test Core, Check Changeset success.
  4. Contract review: packages/spec/src/** hit ⇒ owed. Round 1 FAIL 6585135393 on b71e28d3 (must-fix in registry.ts); patch f42d8a2e + merge of main; re-review claim 6585921263, fresh isolated at-tier reviewer, record 6586088767 PASS on this same head, no must-fix, no should-fix. Accepted deviations: none.
  5. Out of scope: the changeset body names the two original texts, not the registry reason added in the patch round (reviewer: no fix owed; one patch changeset covers the PR). Release-owned history (.changeset/28020-*, 28102-*, CHANGELOG.md) keeps its era wording by rule.
  6. Landing: not governed (check-governed-merges --pr 29534: NOT governed, 99 changed lines ≤ 3000), mergeable_state clean. Seat lands it: pr_ready + automerge_enable.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 19:14
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 1a95c74 Nov 2, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-aar2or branch November 2, 2026 19:56
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 tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(view): the any-of filter group still says [PLANNED] and "no console applies it yet" after #28370 landed

1 participant