Stacked on top of #20533 — merge that one first.
## Summary
Splits `turnRecordFilterIntoRecordGqlOperationFilter` into a thin
dispatcher and a private `buildDirectFieldGqlOperationFilter`. Same
observable behaviour, but:
- The two ordering-constraint comments that #20533 introduced go away
(\"must run before the emptiness shortcut\", \"drop rather than fall
through to legacy relation-by-record\"). They were both flagging a real
code smell: dispatch and direct-filter logic were interleaved in the
same function body and depended on a flag inside the input.
- Removes the self-recursion + \"inject target into fieldMetadataItems\"
hack.
- Removes three duplicated `fieldMetadataItems.find(...)` lookups that
only existed to compose error-message labels — the resolved
`fieldMetadataItem` was already in scope.
- Renames `correspondingFieldMetadataItem` → `fieldMetadataItem` inside
the extracted function (the variable IS the field; the longer prefix
predated the split).
## Why
Discussed in #20533: the conditional ordering between the
relation-traversal branch and the per-type switch was easy to get wrong,
and the legacy `case 'RELATION':` foot-gun (parsing the filter value as
a UUID list when it's actually a text value) was guarded by a defensive
`return` rather than by structure.
After this PR the dispatcher picks exactly one branch up front and
`buildDirectFieldGqlOperationFilter` only ever runs against the field
the filter operates on — no \"is this the source field or the target
field?\" ambiguity inside the switch.
## Test plan
- [x] `npx nx build twenty-shared` succeeds (no type regressions).
- [x] `jest packages/twenty-shared` — 1212 tests pass.
- [x] `jest view-query-params.service.spec.ts` — 7 tests pass, including
the relation-traversal round-trip case.
- [ ] CI green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)