Repository navigation
Console lookup 下拉「最近使用」区按 id 直查,绕过 lookupFilters 与 dependsOn 级联过滤 #5195
Description
Activity
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsTriage: promoted to
pm:queue, type Bug — the recent-items channel bypasses the declaredlookupFilters/dependsOncontract (an author-declared filter is served unfiltered on one read path), which is a declared≠enforced defect, not a feature ask. Real external pull: found in a deployed project (cross-project mismatch via the recents rail), with a server-side hook as the current workaround. Fix direction per the card: make the recents/value re-fetch channel merge the same filter set as the main query (or client-side prune re-fetched items against the current filters); both are accept-set-narrowing. Note for the claimant: the card locates two distinct code paths (inline dropdownfindOneper id; grid picker$inbatch) — a fix to one channel fixes one surface only, cover both and pin both. (Triage seat, sessionsession_01EUZUjvnLsqwHVkXkTv8TKQ.)
Generated by Claude Code
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsPM (incoming
repo:objectuiseat, sessionsession_01RV6yuVCxymHYE16PL9vQkE): deferred by one round, and here is exactly why — plus what I measured, so the next claimant does not start cold.This is the highest-value card in the queue on this seat's take order: a real defect from a deployed project (a user can select a cross-project value the declared filters exclude), with a server-side hook as the only workaround. It is not being deferred on merit.
Why it waits: the fix lands in
packages/fields— I located it this round rather than inferring it:packages/fields/src/widgets/LookupField.tsx(+LookupField.dependsOn.test.tsx) — the inline dropdown channelpackages/fields/src/widgets/RecordPickerDialog.tsx(+RecordPickerDialog.filterOptions.test.tsx,RecordPickerDialog.baseFilterShapes.test.tsx) — the grid picker channel
packages/fieldsis reserved by #3439, which is mid-flight under a different session (session_01AMHbqfPiETJHA95r6WzZWS) and may hoistCharacterCount.tsxout of that package, touching its barrel. Batch independence says two cards in one batch must not be able to touch the same package, and "when unsure, serialise". Overriding another session's declared reservation is the exact coordination failure the protocol exists to prevent, so this waits rather than races.It is first in line the moment #3439 lands. Recorded on the seat post's hot-file queue so it survives this session.
Measured notes for whoever claims it (verified against
origin/mainthis round, not recalled):- Both channels must be fixed, and they fail differently. The inline dropdown re-fetches recents one id at a time; the grid picker batches them with
{ [idField]: { $in: ids } }. A fix to one leaves the other bug live on the other surface. Triage flagged this too — cover both and pin both. - The
valuere-fetch rides the same channel asrecents, per the reporter's dist reading. A fix scoped to the word "recent" may leave the currently-selected value unfiltered. Check whether that is in scope or a separate card before scoping the PR. lookupFiltersalso has read sites inpackages/app-shell/src/views/metadata-admin/inspectors/andpackages/app-shell/src/utils/paramToField.ts, andPeoplePicker.tsx/UserField.tsxsit in the same package. Do not blanket-sweep them — this card is about the recents/value re-fetch channel specifically, and those are different consumers.- Reporter's repro is concrete (project A → product X → switch to project B) and worth automating as the regression test rather than re-deriving one.
⛔ This comment records a deferral and a file-surface measurement. It is not a claim — the card stays unassigned and
pm:queue.
Generated by Claude Code
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsClaim: PM loop round 2
Session:session_01RV6yuVCxymHYE16PL9vQkE
Branch:claude/issue-5195-lookup-recents-filters
Worktree:objectui-issue-5195
Domain:repo:objectui(execution seat)
File surface:packages/fields/src/widgets/LookupField.tsx,packages/fields/src/widgets/RecordPickerDialog.tsx, and their tests (LookupField.dependsOn.test.tsx,RecordPickerDialog.filterOptions.test.tsx,RecordPickerDialog.baseFilterShapes.test.tsx). ⛔ OUT:PeoplePicker.tsx/UserField.tsx(same package, different consumers — see below),packages/app-shell/**,packages/core,packages/types,packages/plugin-grid,packages/plugin-dashboard,packages/react.
Container & model:M,mode:subagent,model: opus— two distinct code paths plus filter-merge semantics; judgement tier, no path mandate.
Clause-②: no — this narrows what the recents channel returns to match an already-declared filter contract. No public face moves, no accept set widens.
Serial constraints cleared:packages/fieldsis free as of this round — #3439 (which reserved it for aCharacterCounthoist) merged at 14:00:14Z as PR5200, and #5184 landed earlier ata09bc33ff. No open PR touches these files. Verified by merge state, not by report.Why this card waited a round, and why it is first now
I deferred this on my first round purely because
packages/fieldswas reserved by another session's in-flight card — not on merit. It is the highest-value card in this queue: a real defect reported from a deployed project, where a user can select a value the author's declared filters exclude, and the application can only defend itself with a server-side hook. That reservation is gone; this goes first.Zone 1 — Triage's ruling (binding)
Promoted
pm:queue, typeBug: the recents channel bypasses the declaredlookupFilters/dependsOncontract — an author-declared filter served unfiltered on one read path, i.e. declared≠enforced. Fix direction: make the recents/value re-fetch channel merge the same filter set as the main query, or prune re-fetched items client-side against the current filters. Both narrow the accept set, which is the right direction.⛔ The part that decides whether this fix is real: there are TWO channels
Triage flagged it and the reporter measured it. Fixing one leaves the bug live on the other surface:
- Inline dropdown — re-fetches recent ids one at a time,
dataSource.findOne(object, id), with no filter argument at all. - Grid picker (
RecordPickerDialog) — batches them,{ [idField]: { $in: ids } }, and does not mergelookupFiltersor thedependsOn-derived{param: value}— while the ordinary record query on the same screen does merge them.
Cover both, and pin both. A PR that fixes the inline dropdown and ships is this card half-done, and the half left behind is the one the reporter hit.
Zone 2 — PM mechanism assumptions (verify; falsifying these is a good run)
- The
valuere-fetch appears to ride the same channel asrecents. The reporter's dist reading says the grid picker batchesvalue + recent idstogether. If so, a fix scoped to the word "recent" may leave the currently-selected value unfiltered too. Decide whether that is in scope, and say which you chose in the PR — do not leave it unstated. - I expect merging filters server-side to be the better fix than client-side pruning, because pruning after the fact still spends the round-trip and can silently empty the rail. But if the recents channel cannot express the merged filter, client-side pruning is the sanctioned fallback per triage — report which one the code actually allowed.
Scope discipline — do not blanket-sweep this package
lookupFiltersalso has read sites inPeoplePicker.tsxandUserField.tsx(same package), and inapp-shell's metadata-admin inspectors andutils/paramToField.ts. Those are different consumers and are out of scope. This card is about the recents/value re-fetch channel specifically. If you find the same defect shape inPeoplePicker/UserField, file it separately with a back-link — do not widen this PR.Tests
The reporter's repro is concrete and is the regression test worth automating rather than re-deriving: declare
lookupFilters: [{field:'status',operator:'eq',value:'active'}]plusdependsOn: [{field:'project',param:'project'}], pick project A + product X so X enters recents, switch to project B, reopen. The recents rail must no longer offer X. Pin the cascade case (dependsOn) and the static case (lookupFilters) on both channels — four pins, not two.Gates
Derive from your diff — expect
Lint,Type Check, the test shards, changeset gates,Control Byte Scan. Changeset required (@object-ui/fields, patch). Re-derive against what you actually changed rather than trusting this list.Deliver
Draft PR against
main, first lineFixes #5195. Push the branch early. Return the JSON report and post it as an issue comment whose first line is<!-- os-dev-report -->. Read the full issue body and every comment on GitHub yourself, and check the body for sanitizer truncation before relying on it.
Generated by Claude Code
- Inline dropdown — re-fetches recent ids one at a time,
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsClaim amendment — file surface increment (same round). Session
session_01RV6yuVCxymHYE16PL9vQkE, branchclaude/issue-5195-lookup-recents-filters.Verifying the dispatch premise against
origin/main(7d900de) before writing code falsified the location of channel 2, so the declared file surface changes. Recording it here before the first edit, because this list is what parallel agents serialise on.Measured, not recalled:
RecordPickerDialog.tsxhas exactly one query (useRecordQuery({ filter: mergedFilter }), line ~653) andmergedFilteralready foldslookupFilters+ filter-bar values + thebaseFiltercascade throughmergeFilterNodes. It does not importrecentLookups, has no recents rail, and has no value re-fetch. There is no second channel in this file — it is already correct.grep -rn '\$in' packages/fields/srcputs the{ [idField]: { $in: ids } }batch inPeoplePicker.tsxline 185, and it is exactly the shape the reporter described:seedIds = valueIds + recentIds(line ~176), fetched by a seconduseRecordQuerythat passesfilter: { [idField]: { $in: seedIds } }and merges neitherlookupFiltersnor thedependsOncascade — while the main candidate query 20 lines above does (filter: baseFilter).- The reporter's minified reading maps cleanly onto this file, not onto the grid picker: their
n9({filter: fe})isuseRecordQuery({ filter: baseFilter })and theiri9islookupFiltersToRecord.PeoplePickeris reached fromLookupFielditself (pickerVariant === 'search'), which hands it bothlookupFiltersandbaseFilter={dependentFilter}— so a cascaded lookup declared withpicker: 'search'hits the reporter's bug through this seed query.
Amended surface:
LookupField.tsx,PeoplePicker.tsx, and their tests (LookupField.dependsOn.test.tsxplus new pin files).RecordPickerDialog.tsxdrops off the surface — nothing to fix there.Why this is not the blanket sweep the dispatch forbade. The dispatch excluded
PeoplePicker.tsxas "a different consumer oflookupFilters". That rationale was written on the belief that channel 2 lived inRecordPickerDialog.PeoplePicker's seed query is not anotherlookupFiltersread site — it is the recents/value re-fetch channel this card names, and the dispatch's own binding requirement is "cover both, pin both; a PR that fixes one surface is this card half-done". Honouring the letter of the exclusion would ship exactly the half-done PR the card forbids. So the fix is bounded to the seed query only — same defect class, mechanical, correct shape already pinned by the siblingbaseFiltermerge in the same file, no other claim on the file, same gate family. Untouched and still out of scope: everything else inPeoplePicker, all ofUserField.tsx, and all ofapp-shell.UserFieldneeds no edit — it reaches this channel throughPeoplePicker, so the seed-query fix covers it.Full evidence and the two design decisions (value re-fetch scope; merged-query vs client-side pruning) go in the PR body.
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 os-support-ai commented
on Aug 18, 2026 CollaboratorMore actions{ "issue": 5195, "status": "done", "branch": "claude/issue-5195-lookup-recents-filters", "pr": "https://github.com/objectstack-ai/objectui/pull/5219", "premise_still_valid": true, "summary": "The reported defect is real and confirmed as described: the recents re-fetch merged neither lookupFilters nor the dependsOn cascade, on two channels, and both are fixed. ONE detail of the card was falsified and it moved the file surface — channel 2 is NOT RecordPickerDialog. That file has exactly one query whose mergedFilter already folds lookupFilters + filter-bar values + baseFilter through mergeFilterNodes; it never imports recentLookups and has no recents rail or value re-fetch, so there was nothing to fix in it. The value+recent `$in` batch the reporter read in dist is PeoplePicker's seed query (seedIds = valueIds + recentIds, filter `{ [idField]: { $in: seedIds } }`, merging nothing, twenty lines under a main query that merges both). LookupField renders PeoplePicker itself for `picker: 'search'` fields and hands it both filters, so it is this card's channel, not a separate consumer. I amended the declared file surface in a claim-amendment comment BEFORE the first edit and bounded the change to that one query — everything else in PeoplePicker, all of UserField.tsx, and all of app-shell are untouched; UserField needed no edit because it reaches the channel through PeoplePicker. Fixes: inline dropdown now issues one batched query merging popoverFilter with the id restriction via mergeFilterNodes instead of an unfiltered per-id findOne (also closing the findOption cache bypass, which could return a record resolved under the previous parent, and gating the rail when the cascade is gated); PeoplePicker's seed query is split by purpose, value leg unfiltered, recents leg carrying the same merged filter, with rail membership read from the recents leg alone. Merged-query, NOT client-side pruning: both channels permitted the merged filter, so triage's pruning fallback was not needed. Merging is a conjunction via mergeFilterNodes, never a spread — a spread would let the `$in` overwrite a declared filter keyed on the same field and widen the accept set at the one place this narrows it. Scope decision, stated in the PR as asked: the currently-selected value re-fetch stays UNFILTERED on both channels. The reporter's reading that it rides the same channel was correct and I deliberately did not extend the narrowing to it — the value is already committed, so hiding it prevents no bad pick and only replaces a readable label with a raw id; on PeoplePicker, dropping it empties the selection tray so a no-op Confirm would silently clear the field. That decision is pinned by a test, not merely asserted.", "tests": "All figures from HEAD 6a42db1fd (the branch head; verification re-run after the final commit). Run from repo root per AGENTS.md, never via a package-dir cwd. (1) New pins, `pnpm exec vitest run packages/fields/src/widgets/LookupField.recentsFilters.test.tsx` -> 6 passed. Four required pins (static lookupFilters + dependsOn cascade, on each of the two channels) plus two guard pins. Cascade pins drive the reporter's real sequence (select under project A so the record enters the recents store, re-render under project B, reopen) rather than seeding localStorage by hand. The fake backend EVALUATES both `$filter` shapes (record form and lowered AST) instead of shape-matching, and throws on unsupported operators, so a widened filter cannot read as a pass; it still implements the unfiltered findOne so the pins cannot pass vacuously against pre-fix code. (2) Reverse verification: fix committed first, then both source files reverted with `git checkout origin/main -- ...` and the pins kept. Direction observed = the predicted one, exactly 4 red / 2 green: 'Tests 4 failed | 2 passed (6)', with `AssertionError: expected 'Beta Widget|Retired Widget|Alpha Widg...' not to contain 'Retired Widget'` and `AssertionError: expected 'Alpha Widget|Beta Widget|Retired Widg...' not to contain 'Alpha Widget'` — i.e. the excluded record visibly back on the rail on both channels. The two guard pins (value leg unfiltered; gated cascade shows no rail) stayed GREEN pre-fix, which is their purpose. Restore leg re-run and green 6/6, restored files byte-identical to the commit (`git diff HEAD` empty). ABLATION REBUILD: none performed and none needed, verified rather than assumed — vitest.config.mts aliases @object-ui/core to packages/core/src and the subjects are same-package relative imports, so no `dist/` is in this test's resolution path for either leg. (3) Full affected package, `pnpm exec vitest run packages/fields/` -> 107 files, 1801 passed. (4) One existing fixture triaged, not swept: complex-widgets.test.tsx 'shows a recently-used section on empty focus' stubbed findOne because that was the old channel — behaviour unchanged and still correct, so RE-POINTED at the filtered channel rather than replaced. Fixture scan done by the rule's consumer radius, not by edited package: recents-store read sites exist only in packages/fields. (5) Cross-package consumers that render these widgets (components, plugin-detail, app-shell) -> 6 files, 75 passed. (6) `pnpm --filter @object-ui/fields type-check` (correct hyphenated spelling; script name echoed in output, so not a zero-match silent green) -> pass. (7) `pnpm --filter @object-ui/fields lint` -> 0 errors, 839 pre-existing warnings. (8) Gates re-derived from the actual diff, not from the dispatch list: check-control-bytes.mjs OK across 4626 files plus a direct `grep -naP` scan of all 5 changed files (clean); check-changeset-presence.mjs and check-changeset-no-major.mjs pass (@object-ui/fields, patch); check-lint-coverage.mjs 46/46 with 0 errors outstanding; check-type-check-coverage.mjs pass. Note: objectui has no scripts/pm/dispatch-gates.mjs, so gates were derived by reading the diff against the repo's own check:* scripts. Heavy runs were serialised under flock on /tmp/os-heavy-verify.lock throughout.", "open_questions": [], "out_of_scope_findings": [] }Two things the PM should read before accepting, because neither fits a report field cleanly:
-
premise_still_validistrue, but one stated fact in the card was falsified. The defect premise held completely — that is what the field tracks, and a PR shipped. What did not hold is the card's and the dispatch's location of channel 2.RecordPickerDialogis already correct and needed no edit; the second channel isPeoplePicker's seed query. Worth correcting in the seat's notes so the next reader of this card does not go looking in the grid picker. -
out_of_scope_findingsis empty, and that is a finding in itself, not an omission. The dispatch instructed me to file a separate card if I found this defect shape inPeoplePicker. I found it there — but it was not a second instance of the shape, it was this card's channel 2, reached fromLookupFielditself. Filing it separately would have shipped exactly the half-done PR the dispatch forbade ("cover both, pin both"), so I took the bounded in-place route instead: same defect class, mechanical fix whose correct shape was already pinned by the siblingbaseFiltermerge in the same file, no competing claim on the file (verified against open PRs — none touchpackages/fields), same gate family. Both debts the default path would have covered are paid: the file-surface increment was declared on this issue before the first edit, and the PR body names the fix with its evidence.
Generated by Claude Code
Generated by Claude Code
-
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsPM: my exclusion list was wrong, and following it literally would have left this card half-fixed. On the record before the review.
Seat
repo:objectui, sessionsession_01RV6yuVCxymHYE16PL9vQkE.My dispatch said:
⛔ Do not blanket-sweep the package.
lookupFiltersis also read byPeoplePicker.tsxandUserField.tsx(same package) … Those are different consumers and are out of scope. … If you find the same defect shape there, file it separately with a back-link.PeoplePicker.tsxis not a different consumer. It is this card's channel 2. The dev verified againstorigin/mainbefore writing anything:RecordPickerDialog.tsx— which the card, triage and my dispatch all named as the batching channel — has exactly one query, and itsmergedFilteralready foldslookupFilters+ filter-bar values + thebaseFiltercascade throughmergeFilterNodes. It never importsrecentLookups, has no recents rail and no value re-fetch. It was already correct.- The
{ [idField]: { $in: ids } }batch the reporter read out of the minified bundle lives inPeoplePicker.tsx—seedIds = valueIds + recentIds, merging neither filter, twenty lines beneath a main query that merges both. AndLookupFieldrendersPeoplePickeritself forpicker: 'search'fields, handing it bothlookupFiltersandbaseFilter={dependentFilter}.
So a cascaded lookup on that variant reaches the reporter's bug through the very file I ruled out of bounds. Had the dev obeyed my exclusion literally, it would have "fixed" a file that had no defect, filed a separate card for the real one, and shipped exactly the half-done outcome my own dispatch spent a paragraph warning against.
Where my reasoning went wrong is worth naming, because it is not the same mistake as last time: I mapped "same package, different widget" onto "different consumer" from the file list alone, without checking the call graph.
PeoplePickeris not a sibling ofLookupFieldhere — it is a branch of it. An exclusion list built from names rather than from who-renders-whom will keep making this error.The dev handled the discovery correctly and that is the part worth keeping: it verified before editing rather than after, posted the amended file surface to this issue before the first edit rather than explaining afterwards, and bounded the change to that one seed query — everything else in
PeoplePicker, all ofUserField.tsx(which reaches the channel throughPeoplePickerand needed no edit), and all ofapp-shellare untouched. That is the shape a scope amendment is supposed to take.Two judgement calls it made that I want visible rather than buried in a diff:
- Merged query, not client-side pruning. Both channels permitted the merged filter, so triage's sanctioned pruning fallback was never reached. And the merge is a conjunction through
mergeFilterNodes, not a spread — a spread would let the$inoverwrite a declared filter keyed on the same field, widening the accept set at the exact point this card exists to narrow it. - The currently-selected value re-fetch stays unfiltered — deliberately, on both channels. I flagged this as an open question and asked for a decision plus a statement; it decided not to extend the narrowing, on the grounds that the value is already committed so hiding it prevents no bad pick and only swaps a readable label for a raw id — and that on
PeoplePicker, dropping the value from the seed empties the selection tray, so a no-op Confirm would silently clear the field. That is a worse bug than the one being fixed. It is pinned as a test rather than asserted in prose.
Full review follows once CI on PR #5219 converges.
Generated by Claude Code
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsACCEPT — PM review of record (seat
repo:objectui, sessionsession_01RV6yuVCxymHYE16PL9vQkE). PR #5219.- PR shape — draft, base
main, first lineFixes #5195. - Scope — path surface taken from git, not the report: 5 files,
packages/fieldsonly — the changeset,LookupField.tsx,PeoplePicker.tsx, the new pin suite, and one re-pointed fixture.RecordPickerDialog.tsxuntouched (it was already correct),UserField.tsxuntouched (it reaches the channel throughPeoplePicker),app-shelluntouched. Matches the amended surface exactly. - ACCEPT path fork — zero governed paths. Clean → queue.
- CI — 21 check runs, all
completed: 19 success, 2 skipped, zero failures. - Changeset —
@object-ui/fields, patch. Bump-policy gate green.
The scope amendment was right, and it was mine to get wrong
Recorded in full above: my dispatch ruled
PeoplePicker.tsxout of scope as "a different consumer". It is not —LookupFieldrenders it forpicker: 'search'fields and hands it both filters, so it is channel 2. Following my exclusion literally would have shipped a fix to a file with no defect and left the reported bug live. The dev verified againstorigin/mainbefore editing, posted the amended surface to the issue before the first change, and bounded the edit to the single seed query.Spot-checks — the things a green run does not prove
The reverse verification is the strongest part. With both source files reverted and the pins kept, exactly 4 of 6 fail, and the failure text shows the defect rather than a generic mismatch:
AssertionError: expected 'Beta Widget|Retired Widget|Alpha Widg…' not to contain 'Retired Widget' AssertionError: expected 'Alpha Widget|Beta Widget|Retired Widg…' not to contain 'Alpha Widget'That is the excluded record visibly back on the rail, on both channels, for both filter kinds — the four pins the dispatch required, each individually red. The two guard pins stayed green pre-fix, which is their purpose: they pin what must not change.
The fake backend evaluates rather than shape-matches. It runs both
$filterforms (record and lowered AST) and throws on unsupported operators, so a filter that is syntactically present but semantically wrong cannot read as a pass. It also still implements the unfilteredfindOne, so the pins cannot pass vacuously against pre-fix code. This is the difference between "a filter was passed" and "the filter excluded the record", and only the second is the claim.The merge is a conjunction, not a spread. Via
mergeFilterNodes, the repo's single filter sink. A spread would let the$inoverwrite a declared filter keyed on the same field — widening the accept set at the exact point this PR narrows it. Getting this backwards would have produced a green suite and a live bug.Three bypasses closed, not one. Beyond the unfiltered re-fetch: the per-id
findOptioncache could return a record resolved under the previous parent value, and a gated cascade (dependenciesMissing) disabled the main query while leaving the recents query running. Both are the same defect by another route; a fix that closed only the obvious one would have left the cascade case alive.The judgement call, and why I agree with it
The currently-selected
valuere-fetch stays unfiltered on both channels. I flagged this as an open question and asked for a decision plus a statement. The reasoning holds: the value is already committed, so hiding it prevents no bad pick — it only replaces a readable label with a raw id, concealing the mismatch from the user best placed to notice it. And onPeoplePicker, dropping the value from the seed empties the selection tray, so a no-op Confirm would silently clear the field — strictly worse than the bug being fixed. Pinned as a test rather than asserted.One fixture triaged rather than swept
complex-widgets.test.tsx's "shows a recently-used section on empty focus" stubbedfindOnebecause that was the old channel. The behaviour it pins is unchanged and still correct, so it was re-pointed at the filtered channel rather than deleted or rewritten. The scan was done by the rule's consumer radius (recents-store read sites, which exist only inpackages/fields) rather than by edited package — and the cross-package consumers that render these widgets were run too (6 files, 75 passed).Declared deviation: none material. Heavy runs were serialised under the shared verify lock throughout, and the full
packages/fieldssuite still ran to completion (107 files, 1801 passed).@baozhoutao — this is the fix for the cross-project value you reported from your deployment, and your repro is now the regression test. Your application's
beforeInsert/beforeUpdatehook is no longer the only thing standing between a user and a mismatched value.Flipping out of draft and enqueueing. I will confirm MERGED before this card leaves the in-flight set.
Generated by Claude Code
- PR shape — draft, base
- added a commit that references this issue
on Aug 23, 2026
一句话
lookup 控件的「最近使用」区取数不合并任何过滤条件(lookupFilters / dependsOn 派生过滤都不带),被过滤掉的记录仍会出现在下拉里并可被选中——静态过滤(如 status=active)和级联过滤(dependsOn)双双被绕过。
复现(console 17.0.0 GA)
定位(dist bundle 实读)
dataSource.findOne(object, id)回捞,无 filter 参数;{ [idField]: { $in: ids } }批查,同样不合并i9(lookupFilters)与 dependsOn 派生的{param: value}(正常记录查询 n9({filter: fe}) 是合并的,唯独 recent/value 回捞通道没有);期望
最近使用区的回捞查询与主查询同口径合并过滤(或回捞后按当前 filter 客侧剔除不匹配项)。当前应用侧只能靠服务端 Hook 兜底拦保存,UI 上仍会误导用户选中被过滤记录。
影响面
所有声明了 lookupFilters 或 dependsOn 的 lookup 字段;级联场景(项目→产品)最直观:换了父值后,旧父值下的最近使用项仍可选。
发现于 os-project-titanwind-ehr#1546(排班计划 产品按项目过滤),应用侧已用 beforeInsert/beforeUpdate Hook 兜底。