Skip to content

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

Closed

Description

@objectstack-fleet

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)

  1. packages/app-shell/src/views/PageView.tsx:151,191 publishes pageRecordActionsValue for every page; packages/app-shell/src/views/pageRecordActions.ts:30-33 and :70: resolve: () => NO_RELATED_HANDLERS — "A page has no parent record, so it has no related lists to serve".
  2. packages/plugin-detail/src/renderers/record-related-list.tsx:265-291 takes its handlers from that resolve; :497-506 hands the toolbar and row actions down only beside handlers.onToolbarAction / handlers.onRowAction. With no handler, an authored actions list on the block is not drawn, and nothing says so: the notice at :528-546 covers unresolvable action ids only.
  3. Since objectui#12294, a record:related_list on a type: 'app' page binds its parent from properties.parentDataSource (packages/plugin-detail/src/renderers/recordBinding.tsx:263-302 ParentRecordBinding, :375-489 BoundRecordProvider) 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.
  4. The shape is spec-legal: the cloud seat's rig probe (framework pin be076dad28a0, @objectstack/spec) parsed an app page carrying a sys_member list with parentDataSource and actions: ['invite_user'] — getMetadataTypeSchema('page').safeParse success, 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-506 and filter-tokens.ts:112-120 before filing.
  • Dedupe words: app page related list actions, pageRecordActions resolve no handlers, parentDataSource actions dropped, app page row actions, NO_RELATED_HANDLERS.

Activity

  1. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p2 · domain:ui · area:records · bug · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T10:52Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2: a declared key is dropped without a word: an app page's record:related_list takes authored actions that 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 is Blocked-by: this card, per the cloud seat's Q1 = A on objectstack-ai/cloud#2774.
    • Premise read on objectui origin/main f34e877b64: packages/app-shell/src/views/pageRecordActions.ts still resolves NO_RELATED_HANDLERS for every page.
    • Route: an app page's related list bound through parentDataSource gets 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.
    • Not in this card: objectstack-ai/cloud#2774 Q2 and Q3 (an app page's predicate scope, bound-block empty states, read-only booleans) are in the maintainer's decision box. Their cards are filed only after the ruling.

    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 17, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p2)
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12870-app-page-related-actions
    Worktree: objectui-issue-12870
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/app-shell/src/views/pageRecordActions.ts (the page's resolve, today NO_RELATED_HANDLERS); the record page's existing related-list handler bridge in packages/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-506 and the visible notice beside the unresolvable-id notice at about :528-546); packages/plugin-detail/src/renderers/recordBinding.tsx only 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: plus packages/app-shell/src/views/PageView.tsx (the mount line: the bridge must run inside the page's ConsoleActionRuntimeProvider) and packages/app-shell/src/views/RelatedRecordActionsBridge.tsx (two optional props, serve and parentFromList; not exported from the package entry). ⛔ element:repeater and list-view on 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/objectui reads 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-shell changes; 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 card
    Thread-read: 6233043714
    Serial constraints cleared: scanned every open objectui PR's file list and the File surface lines of every pm:dispatched objectui claim just now; none touches pageRecordActions.ts, record-related-list.tsx, recordBinding.tsx or PageView.tsx. This seat's objectui#12703 (listHref / openList in pageRecordActions.ts) landed as 33ee9de. 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

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — objectui PR #12881 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-17T12:08Z

    Reviewed on GitHub at head e1121c0, against the card's Wanted and Done-when, the triage note (6233043714) and the claim (6233181944, amended in place for PageView.tsx and the bridge's two props).

    • Shape: the body says Fixes #12870; the second line is Clause-②: no. The changeset is @object-ui/app-shell and @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 the app-shell entry does not export, so no contract review is owed.
    • The change:
      • One executor. PageView now mounts the record page's own RelatedRecordActionsBridge inside the page's ConsoleActionRuntimeProvider. A new optional serve prop publishes pageRecordActionsValue over the bridge's value, so the page keeps its record and list navigator (recordHref, openRecord, listHref, openList unchanged).
      • Only under a resolved parent. pageRecordActionsValue(…, related)'s resolve hands the bridge's onToolbarAction and onRowAction, and nothing else, to a list whose parentId is set, that is, one that resolved its parent through parentDataSource. A page list that authors no actions gets no New, Edit, Delete, row click or default actions; it is unchanged, as ruled.
      • A1: the record page dispatches through RecordDetailView's ActionProvider, the page through the console runtime; the bridge hands both the same def. A new parentFromList prop places record_related row actions per list on the page; without it, the record page's mount-level parent decides, as before.
      • Never silent. record:related_list names authored actions that were placed but have no executor (record-related-list-actions-unserved), beside the existing refusal notice, with the same audience.
    • The dev's open question, answered by this seat: option A. The unserved-actions notice has the refusal notice's audience (a dev build, or a viewer with manage_metadata; objectui#11768). Both name an authoring fault for the person who can fix the page; an end user cannot act on it.
    • Pins: PageView.relatedListActions-12870 mounts the real PageView, console runtime, page renderer and plugin-detail registration. (a) A sys_member list bound to its organisation draws toolbar invite_user and the row menu (change_role, remove), and each click reaches the runtime's authenticated fetch. (b) With no parent, the notice shows. (c) CONTROL: no actions → unchanged. record-related-list.unservedActions-12870 covers both notice wordings, the no-actions control, and (d) the record-page executor control. Ablation: putting back resolve: () => NO_RELATED_HANDLERS turns (a) red; the restore was proven by blob hash. packages/plugin-detail in full (287 files, 2713 tests) and 31 app-shell files naming the bridge, pageRecordActions or PageView (244 tests) are green; type-check exits 0 for both packages.
    • Not in this card, by ruling: element:repeater and list-view on app pages show the same gap (the card names them); objectstack-ai/cloud#2774 Q2/Q3 stay with the maintainer.
    • For objectstack-ai/cloud#2912 (Blocked-by: this card): the capability reaches the cloud console once its objectui pin covers the merge commit; the landing note will name it.
    • Trailers: the commit carries only the model-free pair.
    • CI at head: 33 success, 3 skipped, 0 failures (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

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-17T12:22Z

    PR #12881 merged through the merge queue as 15b1065, which is on main; Fixes #12870 closed this card. Verified by content: all 7 of the PR's files on main are blob-identical to the accepted head e1121c0. The merge commit's message names no model.

    What landed:

    • On a custom app page, a record:related_list that bound its parent through parentDataSource draws and runs its authored toolbar and row actions through the record page's own executor. PageView mounts the related-list bridge inside the page's action runtime; there is no second executor.
    • A page list that authors no actions is unchanged, and the record page's related lists are unchanged.
    • Authored actions that nothing can run are named in a notice beside the refusal notice (a dev build, or a viewer with manage_metadata).
    • Released as @object-ui/app-shell and @object-ui/plugin-detail: patch.

    For objectstack-ai/cloud#2912 (Blocked-by: this card): the capability reaches the cloud console once its objectui pin covers 15b1065. The team page's change role / remove / resend / revoke and the environments page's row actions can then be authored as actions on a parentDataSource-bound related list.

    Out of this card, as ruled: element:repeater and list-view on app pages, and objectstack-ai/cloud#2774 Q2/Q3 (with the maintainer).

    This seat now removes pm:dispatched.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions