You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Drill "Open in list" and related-list "View all" are withheld when the drill holds a boolean, $ne, $in or text-operator condition: the list route's URL cannot carry them #30743
Filing gate: ③ an out-of-scope finding the #30654 card thread declared in draft PR #30735 (head 7519c053, os-dev-report 6672865626) and the seat's ACCEPT names. ⛔ Not a claim. Triage sets the grade and the lane.
What happens
The object list route carries its filter as filter[FIELD]=VALUE plus the range, is-null and is-empty params (ui/packages/app-shell/src/views/drillUrlFilters.ts). exactRecordListFilter (ui/packages/app-shell/src/views/useOpenRecordList.ts, objectstack#25888) offers a list destination only when every condition fits that grammar. It refuses a boolean, null or Date comparand, $ne, $in / $nin, the text operators, $or, and two different comparands on one field.
A related list's "View all" has followed that rule since #25888. After PR #30735 (#30654), every drill escape hatch follows it too: the drawer's "Open in list", drillDown.target: 'navigate', and the chart's own header button. That is the right call, because a wider list is no destination. The side effect is that a drill on a boolean bucket (a chart grouped by an is_active-style field), or under a widget filter or measure scope using $ne or $in, now stays in the drawer with no way to the full list, where before it opened a list that was sometimes the right one and sometimes wider.
Wanted
Measure which of these the list page (ObjectDataPage, parseUrlFilterTriples) can read back EXACTLY once written, and give the route a spelling for the ones it can: a boolean comparand and $in are the likely first two (a boolean bucket and a measure scope like status in (...) are common drills).
Each new spelling joins exactRecordListFilter's accepted set with its own round-trip proof (written alone, reads back as exactly one triple), so the drill exits and "View all" regain a destination together.
Nothing that cannot round-trip is accepted: no destination stays better than a wider list.
Reader: domain:ui (ui/packages/app-shell/src/views/drillUrlFilters.ts, useOpenRecordList.ts, ObjectDataPage's URL read).
Dedupe: exactRecordListFilter, list route URL filter $in, filter boolean list route (is:issue is:open) → only #30654 itself.
Claim: PM loop round 8 · 2026-11-06T01:42Z
Session: session_01L6bhgWn6zhNa4LL7VB1rtL
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
Worktree: objectstack-issue-30743
Domain: domain:ui
Seat: domain:ui#1 (the card's digit 3 is domain:ui#6's; that seat is offline since 01:34Z, and Sales left this seat as the lane's only one at 01:39Z)
File surface: the object list route's URL filter grammar and its two readers in ui/packages/app-shell/src/views/: drillUrlFilters.ts (the write side serializeDrillFilterParams:361 and the read side parseUrlFilterTriples:254, on adf6ee094), useOpenRecordList.ts (exactRecordListFilter:80, whose accepted set grows only by spellings that round-trip), ObjectDataPage.tsx (where the parsed triples become the list's filter, :282, and its operator bridge to the spec's alias spelling), the filter chips that render those triples (groupFilterChips:502 and their i18n copy if a new operator needs a chip label), their tests; one root .changeset/ file if a published package moves. ⛔ packages/** (the server's filter grammar does not move), ⛔ ui/packages/react (the DrillNavigationValue contract landed in PR #30735 and does not change), ⛔ ui/packages/i18n/src/locales/*.ts (busy with PR #30640; a new chip label goes in a namespace the dev names or waits). Stop on breach and explain in the report.
Container & model: M, mode:thread, model: default (opus) (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
Clause-②: yes (widening) — the list route's URL, a surface users bookmark and share, gains spellings it did not accept, and exactRecordListFilter's accepted set grows; the seat's TIER_DEFAULT review is owed at ACCEPT. Re-read at ACCEPT.
Metadata-protocol: none
Responsibility: after a user drills into a boolean bucket or an $in scope, "Open in list" and a related list's "View all" open exactly those rows | both are withheld for those drills since PR #30735 (and "View all" since #25888), because the route cannot say the filter | every Console user drilling a dashboard chart grouped by a yes/no field, or a measure scoped to a set of statuses
Thread-read: 6673676079
Premise: holds on origin/mainadf6ee094: the route's grammar is equality to a non-empty string or finite number, the four range bounds, [null] and [empty] (drillUrlFilters.ts:20-27, :64, :73); exactRecordListFilter (useOpenRecordList.ts:80) refuses a boolean, null or Date comparand, $ne, $in and the text operators, and proves every accepted condition by a one-triple round trip through parseUrlFilterTriples. The dev measures first, per candidate (boolean comparand, $in, then $ne and the text operators), whether a written param reads back in ObjectDataPage as the SAME comparison the drill made (type included: a boolean must not come back as the string 'true'), and adds a spelling only for those that do; each new spelling joins the accepted set with its own round-trip pin, and anything that cannot round-trip stays refused. The spelling must not collide with a field named like an operator suffix (the URL_FILTER_OPS prototype-free lookup, :64, stays).
Out of scope: the server's query grammar; the chart's drill scopes (landed in #30615 / #30654).
Serial constraints: the file lists of the 45 open PRs read at claim time: none touches drillUrlFilters.ts, useOpenRecordList.ts or ObjectDataPage.tsx. PR #30595 (#30511) adds ObjectDataPage.filterChipI18n-9159.test.tsx lines; whichever lands second merges the other.
{
"issue": 30743,
"status": "done",
"branch": "claude/project-thread-7ywrg2",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30777",
"head": "ed322918",
"session": "session_01L6bhgW9yrPaVhdtD84RWcR",
"premise_still_valid": true,
"summary": "Measured first on b3098661 through toFilterNode, parseFilterAST, serializeDrillFilterParams and parseUrlFilterTriples against convertFiltersToAST: a bare boolean wrote filter[F]=true and read back as the STRING 'true'; $in, $nin, $ne and the five text operators wrote no param; all 18 probed cases answered exactRecordListFilter null. The list route now has filter[F][bool]=true|false (only those two spellings, read back as the boolean), filter[F][ne|contains|notcontains|startswith|endswith|icontains]=V and repeated filter[F][in|nin]=V gathered into one triple. exactRecordListFilter accepts them and proves each condition by a one-param-name round trip whose triple must equal convertFiltersToAST(condition), finite numbers allowed to read back as text; $or, $between, an empty set and a boolean $ne stay refused. Chips draw the filter builder's existing operator label beside the comparand; Save as view folds every new operator to its canonical word.",
"tests": "Targeted, from ui/, 21 files: drillUrlFilters, exactRecordListFilter-25888, RelatedRecordActionsBridge viewAll-12741 and viewAllFilter-25888, useOpenRecordList.placeholderScope-9022, ObjectView.urlFilterSuffix-9196, ObjectView.filterParamsKeyEncoding-9287, every ObjectDataPage.* and drill* suite in app-shell views, studio-canvas-preview, plugin-dashboard DatasetWidget and ObjectPivotTable.drillFilterComposition-9024: Test Files 21 passed (21), Tests 383 passed (383) on ed322918. Before the pin moves: 7 failed | 339 passed, exactly the seven moved pins. type-check @object-ui/app-shell (both tsc runs) after building the app-shell^... closure: 0 error TS, VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs on the committed fix: [bool] read arm pushes the string 'true' (anchor 1 to 0, blob 35873c7f to 0120ef72): 3 failed | 116 passed (drillUrlFilters [bool] read-back, exactRecordListFilter new-spellings round trip and drawer-node equality); restored, blob == HEAD, git diff HEAD empty. Tests import source relatively, no dist leg. Declared narrowing: the remaining app-shell test files go to CI Test UI.",
"gates": "dispatch-gates --commands on ed322918: 35 families, every one exit 0 (pnpm --dir ui exec vitest run scripts/: 4970 passed | 2 skipped; its PREREQUISITE NOT MET and chunk lines are fixture stdout inside that passing run). --ran with exit codes: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN (derived zero). check:nul-bytes exit 0. CI check-runs on ed322918 at report time: 44 success, 11 skipped, 0 failure, more still queued (in_progress).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no GitHub MCP tool called; reads via gh api",
"api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, run 42150282723, body read back identical 11356 bytes) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/30743/comments) via post-stamped",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none · noted, not filed: a boolean chip reads = true / = false (the raw comparand, the posture every equality chip has); a set of numbers reads back as text, as equality and the range bounds always have; both written in the PR Acceptance notes",
"carrier: PR #30595 · serial: both PRs add lines to ObjectDataPage.filterChipI18n-9159.test.tsx (this one appends one describe at the end); whichever lands second merges the other"
]
}
ACCEPT → PR #30777 comment 6675455452 · head ed322918 · 2026-11-06T02:45Z — the list route gains [bool], [ne] and the five text suffixes, and repeated [in] / [nin], each read back as the node the drill drawer queries; "Open in list" and "View all" now offer the exact list for them. Contract review at TIER_DEFAULT (declaration face): PASS, 6675437187. pr_ready + automerge_enable relayed. Fixes: the card closes on the merge.
Landed 2026-11-06T03:28Z: PR #30777 merged as f8195bde6a (seat ACCEPT 6675455452 on head ed322918, contract review TIER_DEFAULT PASS 6675437187), 2026-11-06T03:27:33Z on the PR.
:189 (every condition "Open in list" accepts must equal the node the drill drawer queries)
State: the card closed on the merge (Fixes), and pm:dispatched is removed in the same stroke. Still refused by design: $or, $between, an empty set, a boolean $ne. Noted, not filed: the comment in ui/packages/plugin-detail/src/RelatedList.tsx (about lines 1705–1716) that calls URL_FILTER_OPS the one operator contract for this spelling is now a stale cross-reference; its conclusion still holds, because that raw-URL fallback never reads this grammar.
Activity
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 ·
domain:ui· 2026-11-06T00:55Z⛔ Not a claim.
$ne,$inor text-operator condition. This is the Drill drawer "Open in list" opens a wider list than the drawer when the drill filter has two conditions on one field or an$in#30654 family; the reader isdrillUrlFilters.ts/useOpenRecordList.ts.Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL
Claim: PM loop round 8 · 2026-11-06T01:42Z
Session:
session_01L6bhgWn6zhNa4LL7VB1rtLAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch: the card thread's designated
claude/project-thread-*branch, named in a claim amendment once the thread reports itWorktree:
objectstack-issue-30743Domain:
domain:uiSeat:
domain:ui#1(the card's digit 3 isdomain:ui#6's; that seat is offline since 01:34Z, and Sales left this seat as the lane's only one at 01:39Z)File surface: the object list route's URL filter grammar and its two readers in
ui/packages/app-shell/src/views/:drillUrlFilters.ts(the write sideserializeDrillFilterParams:361and the read sideparseUrlFilterTriples:254, onadf6ee094),useOpenRecordList.ts(exactRecordListFilter:80, whose accepted set grows only by spellings that round-trip),ObjectDataPage.tsx(where the parsed triples become the list's filter,:282, and its operator bridge to the spec's alias spelling), the filter chips that render those triples (groupFilterChips:502and their i18n copy if a new operator needs a chip label), their tests; one root.changeset/file if a published package moves. ⛔packages/**(the server's filter grammar does not move), ⛔ui/packages/react(theDrillNavigationValuecontract landed in PR #30735 and does not change), ⛔ui/packages/i18n/src/locales/*.ts(busy with PR #30640; a new chip label goes in a namespace the dev names or waits). Stop on breach and explain in the report.Container & model:
M,mode:thread,model: default (opus)(dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable),effort: highClause-②: yes (widening) — the list route's URL, a surface users bookmark and share, gains spellings it did not accept, and
exactRecordListFilter's accepted set grows; the seat's TIER_DEFAULT review is owed at ACCEPT. Re-read at ACCEPT.Metadata-protocol: none
Responsibility: after a user drills into a boolean bucket or an
$inscope, "Open in list" and a related list's "View all" open exactly those rows | both are withheld for those drills since PR #30735 (and "View all" since #25888), because the route cannot say the filter | every Console user drilling a dashboard chart grouped by a yes/no field, or a measure scoped to a set of statusesThread-read: 6673676079
Premise: holds on
origin/mainadf6ee094: the route's grammar is equality to a non-empty string or finite number, the four range bounds,[null]and[empty](drillUrlFilters.ts:20-27,:64,:73);exactRecordListFilter(useOpenRecordList.ts:80) refuses a boolean,nullorDatecomparand,$ne,$inand the text operators, and proves every accepted condition by a one-triple round trip throughparseUrlFilterTriples. The dev measures first, per candidate (boolean comparand,$in, then$neand the text operators), whether a written param reads back inObjectDataPageas the SAME comparison the drill made (type included: a boolean must not come back as the string'true'), and adds a spelling only for those that do; each new spelling joins the accepted set with its own round-trip pin, and anything that cannot round-trip stays refused. The spelling must not collide with a field named like an operator suffix (theURL_FILTER_OPSprototype-free lookup,:64, stays).Out of scope: the server's query grammar; the chart's drill scopes (landed in #30615 / #30654).
Serial constraints: the file lists of the 45 open PRs read at claim time: none touches
drillUrlFilters.ts,useOpenRecordList.tsorObjectDataPage.tsx. PR #30595 (#30511) addsObjectDataPage.filterChipI18n-9159.test.tsxlines; whichever lands second merges the other.objectstack-fleet commentedon Nov 6, 2026
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL
席位核对 · Amendment · 2026-11-06T01:47Z · claim 6674571486 branch named
Branch:
claude/project-thread-7ywrg2(the card thread's designated branch; sessionsession_01L6bhgW9yrPaVhdtD84RWcR), worktreeobjectstack-issue-30743, cut fromorigin/mainb3098661. Everything else in claim 6674571486 stands.objectstack-fleet commentedon Nov 6, 2026
domain:ui#1 · os-sales · session_01L6bhgW9yrPaVhdtD84RWcR
os-dev-report
{ "issue": 30743, "status": "done", "branch": "claude/project-thread-7ywrg2", "pr": "https://github.com/objectstack-ai/objectstack/pull/30777", "head": "ed322918", "session": "session_01L6bhgW9yrPaVhdtD84RWcR", "premise_still_valid": true, "summary": "Measured first on b3098661 through toFilterNode, parseFilterAST, serializeDrillFilterParams and parseUrlFilterTriples against convertFiltersToAST: a bare boolean wrote filter[F]=true and read back as the STRING 'true'; $in, $nin, $ne and the five text operators wrote no param; all 18 probed cases answered exactRecordListFilter null. The list route now has filter[F][bool]=true|false (only those two spellings, read back as the boolean), filter[F][ne|contains|notcontains|startswith|endswith|icontains]=V and repeated filter[F][in|nin]=V gathered into one triple. exactRecordListFilter accepts them and proves each condition by a one-param-name round trip whose triple must equal convertFiltersToAST(condition), finite numbers allowed to read back as text; $or, $between, an empty set and a boolean $ne stay refused. Chips draw the filter builder's existing operator label beside the comparand; Save as view folds every new operator to its canonical word.", "tests": "Targeted, from ui/, 21 files: drillUrlFilters, exactRecordListFilter-25888, RelatedRecordActionsBridge viewAll-12741 and viewAllFilter-25888, useOpenRecordList.placeholderScope-9022, ObjectView.urlFilterSuffix-9196, ObjectView.filterParamsKeyEncoding-9287, every ObjectDataPage.* and drill* suite in app-shell views, studio-canvas-preview, plugin-dashboard DatasetWidget and ObjectPivotTable.drillFilterComposition-9024: Test Files 21 passed (21), Tests 383 passed (383) on ed322918. Before the pin moves: 7 failed | 339 passed, exactly the seven moved pins. type-check @object-ui/app-shell (both tsc runs) after building the app-shell^... closure: 0 error TS, VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs on the committed fix: [bool] read arm pushes the string 'true' (anchor 1 to 0, blob 35873c7f to 0120ef72): 3 failed | 116 passed (drillUrlFilters [bool] read-back, exactRecordListFilter new-spellings round trip and drawer-node equality); restored, blob == HEAD, git diff HEAD empty. Tests import source relatively, no dist leg. Declared narrowing: the remaining app-shell test files go to CI Test UI.", "gates": "dispatch-gates --commands on ed322918: 35 families, every one exit 0 (pnpm --dir ui exec vitest run scripts/: 4970 passed | 2 skipped; its PREREQUISITE NOT MET and chunk lines are fixture stdout inside that passing run). --ran with exit codes: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN (derived zero). check:nul-bytes exit 0. CI check-runs on ed322918 at report time: 44 success, 11 skipped, 0 failure, more still queued (in_progress).", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 — no GitHub MCP tool called; reads via gh api", "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, run 42150282723, body read back identical 11356 bytes) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/30743/comments) via post-stamped", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: none · noted, not filed: a boolean chip reads = true / = false (the raw comparand, the posture every equality chip has); a set of numbers reads back as text, as equality and the range bounds always have; both written in the PR Acceptance notes", "carrier: PR #30595 · serial: both PRs add lines to ObjectDataPage.filterChipI18n-9159.test.tsx (this one appends one describe at the end); whichever lands second merges the other" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL
ACCEPT → PR #30777 comment 6675455452 · head
ed322918· 2026-11-06T02:45Z — the list route gains[bool],[ne]and the five text suffixes, and repeated[in]/[nin], each read back as the node the drill drawer queries; "Open in list" and "View all" now offer the exact list for them. Contract review atTIER_DEFAULT(declaration face): PASS, 6675437187.pr_ready+automerge_enablerelayed.Fixes: the card closes on the merge.objectstack-fleet commentedon Nov 6, 2026
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL
Landed 2026-11-06T03:28Z: PR #30777 merged as
f8195bde6a(seat ACCEPT 6675455452 on headed322918, contract reviewTIER_DEFAULTPASS 6675437187), 2026-11-06T03:27:33Z on the PR.git grep -n "export const BOOLEAN_FILTER" origin/main -- ui/packages/app-shell/src/views/drillUrlFilters.ts:265(filter[F][bool]=true|false, read back as the boolean)git grep -n "export const URL_COMPARE_OPS" origin/main -- ui/packages/app-shell/src/views/drillUrlFilters.ts:285([ne]and the five text suffixes)git grep -n "export const URL_LIST_OPS" origin/main -- ui/packages/app-shell/src/views/drillUrlFilters.ts:313(repeated[in]/[nin], one triple per field)git grep -n "function isSameComparison" origin/main -- ui/packages/app-shell/src/views/useOpenRecordList.ts:189(every condition "Open in list" accepts must equal the node the drill drawer queries)State: the card closed on the merge (
Fixes), andpm:dispatchedis removed in the same stroke. Still refused by design:$or,$between, an empty set, a boolean$ne. Noted, not filed: the comment inui/packages/plugin-detail/src/RelatedList.tsx(about lines 1705–1716) that callsURL_FILTER_OPSthe one operator contract for this spelling is now a stale cross-reference; its conclusion still holds, because that raw-URL fallback never reads this grammar.