Repository navigation
fix(app-shell): a related list's View all carries the list's own filter, or is not offered - #26060
Conversation
…er, or is not offered Claude-Session: https://claude.ai/code/session_01HYEgis8rPZK6Dr3G5Jas4q Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HYEgis8rPZK6Dr3G5Jas4q Co-authored-by: Claude <noreply@anthropic.com>
…t renderer change Claude-Session: https://claude.ai/code/session_01HYEgis8rPZK6Dr3G5Jas4q Co-authored-by: Claude <noreply@anthropic.com>
…-0pu6d9 # Conflicts: # scripts/sdui-manifest.record.json
Claude-Session: https://claude.ai/code/session_01HYEgis8rPZK6Dr3G5Jas4q Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments
② Semver levelThe changeset ③ Boundary flags
Implemented-by: VERDICT: PASS |
席位核对 · ACCEPT · seat
|
…-0pu6d9 # Conflicts: # scripts/sdui-manifest.record.json
…merged tree Claude-Session: https://claude.ai/code/session_01HYEgis8rPZK6Dr3G5Jas4q Co-authored-by: Claude <noreply@anthropic.com>
席位核对 · ACCEPT (carried to the landing head) · seat
|
Requested by Steve · project thread
Fixes #25888
Clause-②: yes (an optional member on the exported
ResolveRelatedRecordActionsInputof@object-ui/reactwidens its public type surface, additive; the seat owes a TIER_DEFAULT review record before the queue; re-declared if the PR changes no exported type)Metadata-protocol: none
What was wrong
A
record:related_list"View all" link opened the child object's list scoped by the list's relationship alone. The list's ownfilternever reached the host that builds the link: the renderer'sresolvecall handed the bridgeobjectName,relationshipFieldandparentIdonly, and the bridge builtrecordListPath(appName, objectName, { [relationshipField]: parentId }). On the card's repro (Approvals on a record page, filterobject_name, relationshiprecord_id) the destination readfilter[record_id]=IDonly, and the approvals request read gate, which admits a record reader only on a read pinning both fields, answered it with the empty participant slice: 2 rows on the tab, 0 on "View all".What changed
@object-ui/react—ResolveRelatedRecordActionsInputgains an optionalfilter(ViewFilterRule[] | FilterNode): the list's own scope filter, already resolved. TheonViewAlldoc now says the destination is the relationship AND that filter, and names the cases where the handler is withheld.@object-ui/plugin-detail(record-related-list.tsx) — resolvesschema.filterwithuseResolvedFilter(schema.filter, useFilterScope()), the same hook and scopeRelatedListuses for its own query at the same place in the tree, and hands that value toresolve. Resolving here and not in the bridge is deliberate: the bridge is mounted above the page'sRecordContextProvider, so its filter scope has norecordIdand a{record_id}token would stay unresolved there while the tab resolved it.@object-ui/app-shell— newexactRecordListFilter(sources, canRead)besiderecordListPathinuseOpenRecordList.ts: the AND of the sources as a filter the list route writes EXACTLY, ornull. The bridge passes[{ [relationshipField]: parentId }, filter]and draws "View all" only on a non-null answer.The open question, settled: a filter the URL cannot express
Ruling: never let the full list show rows the tab did not. Writing only the expressible subset of an AND is always a wider (or, behind a pinning read gate, a different) list, so there is no safe subset: either every condition is written or the link is withheld.
exactRecordListFilterwrites exactly the route's grammar (equality to a non-empty string or finite number, the four range bounds,$null/$exists/$emptywith a boolean, conjunction) and answersnullfor anything else:$orand other combinators,$in,$ne, text operators, a boolean /null/Datecomparand, an empty string (the read side skips an empty param), two different comparands in one param (including a filter that contradicts the relationship), a filter the lowering refuses, and any condition on a field the viewer cannot read (ObjectDataPagedrops such a condition). Each accepted condition is proved by its own round trip: written alone it must read back throughparseUrlFilterTriplesas exactly one triple. Placeholders ({current_user_id},{record_id}, the date macros) are resolved before this point, by the renderer, against the list's own scope.Tests
RelatedRecordActionsBridge.viewAllFilter-25888.test.tsx(real bridge +SchemaRenderer+RelatedList, oracleparseUrlFilterTriples): the repro carries bothrecord_idandobject_name; CONTROL with nofiltercarries the relationship alone; a{current_user_id}filter is written resolved; range + is-null travel; anorfilter draws no link; a filter field the viewer cannot read draws no link; CONTROL denying an unrelated field still draws and carries both.exactRecordListFilter-25888.test.ts: every spelling round-trips, agreeing duplicates collapse, nine inexpressible shapes refuse,canReadrefuses for the relationship and the filter field (control: an unrelated field), a lowering refusal isnull, never "no filter".RelatedRecordActionsBridge.viewAll-12741.test.tsx(existing) stays green.Verification (head
a281cecb, after mergingorigin/main)filterargument removed (ablation-replace, anchor 1 -> 0, restored to the HEAD blob,git diff HEADempty) the 25888 bridge suite went 6 failed / 1 passed, the one survivor being the no-filter CONTROL.useOpenRecordList/ drill / View all, every plugin-detail related-list file, every react file readingRelatedRecordActionsoruseResolvedFilter): 73 files, 455/455 passed.type-checkfor@object-ui/react,@object-ui/plugin-detail,@object-ui/app-shell: exit 0, after building the app-shell dependency closure on the merged tree.dispatch-gates --commands, no paths): 46 run, every one exit 0, pluscheck-changeset-fixedexit 0;--ranreconciles 46/46 with 0 NOT-MEASURED.Build & E2E UIandTest Corerun them.Acceptance notes
scripts/sdui-manifest.record.jsonis re-recorded (registry source hash only;check-sdui-manifestreds on anyui/packages/**source edit). It was outside the claim's declared file surface; it is a regenerated artifact,node scripts/gen-sdui-manifest-node.mjs, 106 components unchanged.done = true) now draws no "View all". The URL carries text, and whetherfilter[done]=truereads back as the list's boolean comparison is unmeasured, so the fail-closed answer is taken. Widening the route's grammar (booleans,$in,$or) is a separate change to the/dataURL contract, not this fix.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01HYEgis8rPZK6Dr3G5Jas4q