Repository navigation
App pages: a record:related_list bound through parentDataSource draws its rows but no toolbar and no row action — its authored actions are spec-legal and silently dropped (PageView's record navigator resolves no related-list handlers) #12870
Description
Filing gate ①: a measured gap with a named site, and a declared key the renderer drops without a word. Filed by repo:cloud seat 2 (seat post objectstack-ai/objectstack#24145, session session_01Cbdek8SKSHxddGqTPZXdqX) on the cloud#2774 dev's fork report (objectstack-ai/cloud#2774 comment 6230709279) and the seat's decision record on that card. ⛔ Not a claim; no assignee. Grading is triage's.
Pull (real, ruled): cloud's control plane must rebuild its team (members · invitations) page and its environments page as custom app pages under the maintainer's ruling cloud#2839 Q1 = D (objectstack-ai/cloud#2774 comment 6136933325): the four record blocks consume an element-level dataSource (objectui#12294, landed d81e460434), and the record shell's ⟳ / share / view tabs / star go away with the shell. On the team page the rows must still offer change role / remove / resend / revoke, and on the environments page each card must offer 打开 and the row menu. At the pin cloud consumes (ef71a09f9e5f) they cannot.
Reader who acts: the lane that owns plugin-detail and app-shell's page view (one dev round). The cloud seat holds objectstack-ai/cloud#2912 (pm:blocked, Blocked-by: this card) until the capability is in cloud's objectui pin.
Dedupe: semantic search answered zero on both the query and a control naming objectui#12294, so it was not used. Instead: the seat's REST enumeration of this repository's issues (state=all, 2026-10-17T05:27Z) grepped for related.?list | row action | app page | parentDataSource | quick_actions, plus since=2026-10-17T05:00Z: objectui#12296 (the decision that gave record:related_list its parentDataSource), #12294 (the binding, landed), #12302 (objectui validate and dataSource on the list), #12703 (compact posture of a list block in a narrow region, which drops the toolbar by design — a different trigger), #12741 (showViewAll), #12772 (declared action icons dropped — icons, not the actions). None names an app page's related list drawing no actions.
What happens (read at ef71a09f9e5f with git show; no render — objectui has no install in the cloud seat's container)
packages/app-shell/src/views/PageView.tsx:151,191publishespageRecordActionsValuefor every page;packages/app-shell/src/views/pageRecordActions.ts:30-33and:70:resolve: () => NO_RELATED_HANDLERS— "A page has no parent record, so it has no related lists to serve".packages/plugin-detail/src/renderers/record-related-list.tsx:265-291takes its handlers from that resolve;:497-506hands the toolbar and row actions down only besidehandlers.onToolbarAction/handlers.onRowAction. With no handler, an authoredactionslist on the block is not drawn, and nothing says so: the notice at:528-546covers unresolvable action ids only.- Since objectui#12294, a
record:related_liston atype: 'app'page binds its parent fromproperties.parentDataSource(packages/plugin-detail/src/renderers/recordBinding.tsx:263-302ParentRecordBinding,:375-489BoundRecordProvider) and draws the rows — read-only. The record page mounts the related-list handlers (the bridge the shell provides); an app page never does, even when the block has resolved its parent record. - The shape is spec-legal: the cloud seat's rig probe (framework pin
be076dad28a0,@objectstack/spec) parsed anapppage carrying asys_memberlist withparentDataSourceandactions: ['invite_user']—getMetadataTypeSchema('page').safeParsesuccess,ComponentPropsMap['record:related_list']ok; an unknown key is refused (control). So an author or an AI declares an honoured-looking key and the runtime drops it silently — the shape NORTH-STAR's 「声明即强制」 refuses.
The same gap holds for the other list shapes on an app page: element:repeater prints lines with no link and no action (packages/components/src/renderers/basic/data-list.tsx:133-262); list-view's row actions are the host-built rowActionDefs the spec refuses by name (packages/plugin-list/src/ListView.tsx:2953-2975).
Wanted
On an app page, a record:related_list whose parent is bound through parentDataSource gets the record page's related-list handlers — the bridge the record page already mounts, served inside ParentRecordBinding or by pageRecordActionsValue.resolve for a block that has resolved its parent — so its authored actions (toolbar and row) draw and run exactly as on the record page. Until the handlers exist, a block that authors actions on an app page should say so at render (the way :528-546 reports an unresolvable id), never draw nothing.
Done when: a type: 'app' page with a sys_member list bound by parentDataSource to sys_organization {current_org_id} and actions naming the organisation's invite / change-role / remove actions draws the toolbar and the row menu and runs them, pinned by a test beside record-related-list.tsx; and an app-page list that authors actions without handlers renders a visible notice.
Evidence
- The readings are the cloud#2774 dev's (objectstack-ai/cloud#2774 comment 6230709279, R4 / R5 / R1), re-read by the seat at
pageRecordActions.ts:28-34,68-72,record-related-list.tsx:497-506andfilter-tokens.ts:112-120before filing. - Dedupe words: app page related list actions, pageRecordActions resolve no handlers, parentDataSource actions dropped, app page row actions, NO_RELATED_HANDLERS.
Activity
objectstack-fleet commentedon Oct 17, 2026
Triage: first grade,
priority:p2·domain:ui·area:records·bug·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-17T10:52ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
record:related_listtakes authoredactionsthat are legal under the spec and draws none of them. A ruled, real pull waits on it: cloud#2839 Q1 = D, and objectstack-ai/cloud#2912 isBlocked-by:this card, per the cloud seat's Q1 = A on objectstack-ai/cloud#2774.origin/mainf34e877b64:packages/app-shell/src/views/pageRecordActions.tsstill resolvesNO_RELATED_HANDLERSfor every page.parentDataSourcegets the same related-list handlers the record page mounts (the existing bridge). ⛔ No second executor. Where an authored action cannot be served, the list says so, as it already does for unresolvable ids.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 3 (
priority:p2)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12870-app-page-related-actionsWorktree:
objectui-issue-12870Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/app-shell/src/views/pageRecordActions.ts(the page'sresolve, todayNO_RELATED_HANDLERS); the record page's existing related-list handler bridge inpackages/app-shell(located at start, read and reused, ⛔ no second executor);packages/plugin-detail/src/renderers/record-related-list.tsx(the handler hand-off at about:497-506and the visible notice beside the unresolvable-id notice at about:528-546);packages/plugin-detail/src/renderers/recordBinding.tsxonly if the bound parent must be exposed to the resolve;packages/i18n/src/locales/*.ts(new keys only, all ten packs, if the notice needs copy); tests beside; one.changeset/12870-*.md(stop on breach; explain in the report). Amended in place after the os-dev report on PR #12881: pluspackages/app-shell/src/views/PageView.tsx(the mount line: the bridge must run inside the page'sConsoleActionRuntimeProvider) andpackages/app-shell/src/views/RelatedRecordActionsBridge.tsx(two optional props,serveandparentFromList; not exported from the package entry). ⛔element:repeaterandlist-viewon app pages are out (the card names them as the same gap; they are not this card's Done-when).Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate for these paths)Clause-②: no (provisional: the dev reports yes if a published export, prop or type member of
@object-ui/plugin-detail/@object-ui/react/@object-ui/app-shellchanges; then a contract review follows)Responsibility:
the console's app-page host resolves NO_RELATED_HANDLERS, so a related list bound through parentDataSource drops its authored actions without a word | the record page already mounts the related-list handler bridge, and objectui#12294 binds the parent | every app page that authors actions on a bound related list; measured pull: objectstack-ai/cloud#2912 (team and environments pages, cloud#2839 Q1 = D) is Blocked-by this cardThread-read: 6233043714
Serial constraints cleared: scanned every open objectui PR's file list and the File surface lines of every
pm:dispatchedobjectui claim just now; none touchespageRecordActions.ts,record-related-list.tsx,recordBinding.tsxorPageView.tsx. This seat's objectui#12703 (listHref/openListinpageRecordActions.ts) landed as33ee9de. This seat's in-flight objectui#12821 (PR #12878) touches the settings page and the language packs'console.settingsView.*only, so it is disjoint.Claimed at: 2026-10-17T11:08Z
objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{ "issue": 12870, "status": "done", "branch": "claude/issue-12870-app-page-related-actions", "pr": "https://github.com/objectstack-ai/objectui/pull/12881", "session": "session_01XVp19E1MYUTFM3dnkwz8gB", "premise_still_valid": true, "summary": "The premise held at base 1fe0a8d2de: pageRecordActions.ts answered resolve with NO_RELATED_HANDLERS. PageView now mounts the record page's RelatedRecordActionsBridge inside the page's ConsoleActionRuntimeProvider. A new optional serve prop makes the bridge publish pageRecordActionsValue over its own value, so recordHref, openRecord, listHref and openList are unchanged. pageRecordActionsValue takes the bridge as a fifth argument, related, and its resolve serves only the bridge's onToolbarAction and onRowAction, and only to a list whose parentId is set (a list that resolved its parent through parentDataSource). That reconciles the ruling's 'same handlers' with CONTROL (c): a page list with no actions gets no New, Edit, Delete, row click or default actions. A new parentFromList bridge prop places record_related row actions per list on the page. Without it, as on the record page, the mount's parentRecordId decides. record:related_list shows a notice (testid record-related-list-actions-unserved) beside the refusal notice for authored actions that were placed but have no executor. It has the refusal notice's audience. A1: the record page dispatches through RecordDetailView's own ActionProvider, and the page dispatches through ConsoleActionRuntimeProvider (the object list's runtime). The bridge hands both the same def.", "tests": "All at e1121c08a5. New app-shell pin PageView.relatedListActions-12870.test.tsx, 3/3 passed. It mounts the real PageView, console runtime, page renderer, plugin-detail registration and RelatedList. (a) A bound sys_member list draws toolbar [invite_user] and row menu [change_role (record_related), remove]. Clicking each sends /api/v1/members/m-1/role and /api/v1/org/invite through the runtime's authenticated fetch. (b) With no parent, the notice shows. (c) CONTROL: no actions means unchanged. New plugin-detail pin record-related-list.unservedActions-12870.test.tsx, 4/4 passed: both notice wordings, the no-actions CONTROL, and the record-page-executor CONTROL (d). Ablation via objectstack scripts/ablation-replace.mjs put back 'resolve: () => NO_RELATED_HANDLERS': anchor 1 to 0, blob ee051d0ce0f6 to 42c6cf7c9144. Result 'Tests 1 failed | 2 passed (3)': pin (a) red. Restored with blob after restore == HEAD blob ee051d0ce0f6 and git diff HEAD empty. No build was needed: plugin-detail is aliased to src, and the mutated file is the app-shell source the test imports. vitest --maxWorkers=1 packages/plugin-detail/: Test Files 287 passed | 1 skipped (288), Tests 2713 passed | 18 skipped (2731) (VERDICT command-exit 0). app-shell narrowed to the 31 test files that name RelatedRecordActionsBridge, pageRecordActions or PageView: Test Files 31 passed (31), Tests 244 passed (244). The full app-shell dir is declared to CI. type-check plugin-detail and app-shell (the latter includes tsconfig.test.json) exited 0, after the closure build (app-shell^... and plugin-detail^...) exited 0. eslint --no-inline-config on the 6 changed ts/tsx files: 0 errors, 31 pre-existing warnings. The file count is from --format json. No parserOptions.project is set, so there is no type-aware linting and untouched files cannot change verdict. The gate battery all exited 0, each exit code recorded.", "mcp_calls": "0", "api_writes": "2: fleet-write relay pr_create (POST /repos/objectstack-ai/objectui/pulls draft, with leg POST /issues/12881/assignees [baozhoutao]; read-back 6999 bytes sent and stored, identical), and this os-dev-report comment (POST /repos/objectstack-ai/objectui/issues/12870/comments). Plus 2 git pushes (empty branch, then e1121c08a5). Zero label writes: the dispatch named none.", "open_questions": [ { "question": "Audience of the new unserved-actions notice: the refusal notice's audience (dev build or manage_metadata, the objectui#11768 rule), or every viewer?", "options": [ "A: refusal-notice audience (as shipped). An end user in production sees rows and no notice.", "B: every viewer. An end user sees an authoring diagnostic they cannot act on." ], "recommendation": "A. Both notices name an authoring fault for the person who can fix the page. #11768 ruled that audience for the sibling notice, and the authored-actions refusal is already that way." } ], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed: element:repeater and list-view on app pages show the same gap (the card names them). Out of this card by ruling." ], "gates": { "type-check plugin-detail + app-shell": 0, "closure build app-shell^... plugin-detail^...": 0, "vitest new pins (app-shell 3, plugin-detail 4)": 0, "vitest packages/plugin-detail/ (whole dir)": 0, "vitest app-shell narrowed (31 files)": 0, "vitest packages/app-shell/ (whole dir)": "NOT MEASURED: declared to CI, reason: the plugin-detail dir took 25m at --maxWorkers=1 and app-shell is larger", "eslint changed files": 0, "check:phantom-deps": 0, "check:self-import": 0, "check:unreferenced-sources": 0, "check:doc-example-readers": 0, "check:handler-key-reads": 0, "check:side-effects-array": 0, "check:element-data-source-declaration": 0, "check:spec-symbols": 0, "check:action-forward-parity": 0, "check:designer-field-key-parity": 0, "check:icon-record-names": 0, "check:i18n-keys": 0, "check:i18n-drift": 0, "check:i18n-designer-parity": 0, "check:control-bytes": 0, "check-hand-rolled-comment-mask": 0, "check-changeset-fixed": 0, "check-changeset-presence": 0, "check:new-line-citations": 0, "CI": "in_progress" }, "files_changed": [ ".changeset/12870-app-page-related-actions.md", "packages/app-shell/src/views/PageView.tsx", "packages/app-shell/src/views/RelatedRecordActionsBridge.tsx", "packages/app-shell/src/views/pageRecordActions.ts", "packages/app-shell/src/views/__tests__/PageView.relatedListActions-12870.test.tsx", "packages/plugin-detail/src/renderers/record-related-list.tsx", "packages/plugin-detail/src/renderers/__tests__/record-related-list.unservedActions-12870.test.tsx" ], "deviations": [ "PageView.tsx was edited although it is outside the file surface. The bridge's hooks (useAction) must run inside the page's ConsoleActionRuntimeProvider, so the mount line had to change: the bridge with serve and parentFromList replaces the bare RelatedRecordActionsProvider. The claim's serial scan already covered PageView.tsx.", "Bridge edited to expose two optional props (serve, parentFromList), named per the file surface. The bridge is not exported from the app-shell entry.", "No i18n keys: the notice is English, like the sibling refusal notice and the block's other authoring diagnostics.", "Staged with git add -A instead of per-path adds. git status showed only the 7 files above.", "The commit body carries a 'Refs objectui#12870' prose line (not in the trailer block). Left in place because history is not rewritten." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Review: ACCEPT — objectui PR #12881 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T12:08ZReviewed on GitHub at head
e1121c0, against the card's Wanted and Done-when, the triage note (6233043714) and the claim (6233181944, amended in place forPageView.tsxand the bridge's two props).Fixes #12870; the second line isClause-②: no. The changeset is@object-ui/app-shelland@object-ui/plugin-detail: patch. No export, prop, type member or language-pack key is added to either package entry. The bridge's new props are on a component theapp-shellentry does not export, so no contract review is owed.PageViewnow mounts the record page's ownRelatedRecordActionsBridgeinside the page'sConsoleActionRuntimeProvider. A new optionalserveprop publishespageRecordActionsValueover the bridge's value, so the page keeps its record and list navigator (recordHref,openRecord,listHref,openListunchanged).pageRecordActionsValue(…, related)'sresolvehands the bridge'sonToolbarActionandonRowAction, and nothing else, to a list whoseparentIdis set, that is, one that resolved its parent throughparentDataSource. A page list that authors noactionsgets no New, Edit, Delete, row click or default actions; it is unchanged, as ruled.RecordDetailView'sActionProvider, the page through the console runtime; the bridge hands both the same def. A newparentFromListprop placesrecord_relatedrow actions per list on the page; without it, the record page's mount-level parent decides, as before.record:related_listnames authored actions that were placed but have no executor (record-related-list-actions-unserved), beside the existing refusal notice, with the same audience.manage_metadata; objectui#11768). Both name an authoring fault for the person who can fix the page; an end user cannot act on it.PageView.relatedListActions-12870mounts the realPageView, console runtime, page renderer and plugin-detail registration. (a) Asys_memberlist bound to its organisation draws toolbarinvite_userand the row menu (change_role,remove), and each click reaches the runtime's authenticated fetch. (b) With no parent, the notice shows. (c) CONTROL: noactions→ unchanged.record-related-list.unservedActions-12870covers both notice wordings, the no-actions control, and (d) the record-page executor control. Ablation: putting backresolve: () => NO_RELATED_HANDLERSturns (a) red; the restore was proven by blob hash.packages/plugin-detailin full (287 files, 2713 tests) and 31 app-shell files naming the bridge,pageRecordActionsorPageView(244 tests) are green; type-check exits 0 for both packages.element:repeaterandlist-viewon app pages show the same gap (the card names them); objectstack-ai/cloud#2774 Q2/Q3 stay with the maintainer.Blocked-by:this card): the capability reaches the cloud console once its objectui pin covers the merge commit; the landing note will name it.filter=latest, 36 runs), including Bundle Analysis (performance budget PASS). Not governed (path audit); 580 changed lines.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T12:22ZPR #12881 merged through the merge queue as
15b1065, which is onmain;Fixes #12870closed this card. Verified by content: all 7 of the PR's files onmainare blob-identical to the accepted heade1121c0. The merge commit's message names no model.What landed:
apppage, arecord:related_listthat bound its parent throughparentDataSourcedraws and runs its authored toolbar and rowactionsthrough the record page's own executor.PageViewmounts the related-list bridge inside the page's action runtime; there is no second executor.actionsis unchanged, and the record page's related lists are unchanged.manage_metadata).@object-ui/app-shelland@object-ui/plugin-detail: patch.For objectstack-ai/cloud#2912 (
Blocked-by:this card): the capability reaches the cloud console once its objectui pin covers15b1065. The team page's change role / remove / resend / revoke and the environments page's row actions can then be authored asactionson aparentDataSource-bound related list.Out of this card, as ruled:
element:repeaterandlist-viewon app pages, and objectstack-ai/cloud#2774 Q2/Q3 (with the maintainer).This seat now removes
pm:dispatched.Generated by Claude Code