Skip to content

Approvals: Requests and Action History show record names, step labels, submitter and the current approver #12608

Open

Description

@objectstack-fleet

Source

Walked on objectui main 8415d9f11 with HotCRM's seed data: Approval Requests (sys_approval_request/view/all_requests, 53 rows) shows Source flow:opportunity_approval, Object crm_opportunity, Record ID sg5RyCYRV5K9Udnk, Status Pending, Current Step manager_review, Submitter "—", Updated At; Action History (sys_approval_action) shows Request flow:opportunity_approval · sg5RyCYRV5K9Udnk, Step manager_review, Action Submit, Actor "—". An admin cannot tell which opportunity is waiting, on whom, since when. The Approvals Inbox says "You're all caught up — nothing is waiting on you" while 53 requests are pending on manager_review: correct for the caller, but nothing on either page says who the 53 are waiting for.

Target

  1. Requests: columns Record (the object's icon + the record's name field, linking to the record), Flow (the flow's label, api name in the tooltip), Step (the step's label), Waiting on (the approver the step resolves to: user, position or team label), Submitter (user name), Submitted / Updated (relative time with the absolute in the tooltip), Status chip. The raw ids move to the row's expanded detail.
  2. Action History: Record and Flow as above, Step label, Action chip, Actor name, Acted as, Comment; "Via admin override" as a chip only when true.
  3. The inbox's empty state for a caller with no pending items but pending requests in the environment adds one muted line: "53 requests are waiting on other approvers · Browse all requests".
  4. Where the record name cannot be resolved (deleted record, no read access), the cell shows the object label + "record unavailable", never the id alone.

File surface

apps/console/src/registerApprovalsComponents.tsx and the approvals views it registers, the sys_approval_request / sys_approval_action view definitions the console ships (or the platform ships: if the columns come from objectstack-ai/objectstack's platform-objects views, the card says so and files the view change there), packages/i18n/src/locales/*.ts, tests beside each.

Acceptance

  1. Tests: a request row renders the record name and the step label; the inbox empty state renders the "waiting on other approvers" line when the environment count is non-zero.
  2. Screenshots of both lists and the inbox at 1280 px, light and dark.
  3. pnpm --filter @object-ui/app-shell test green.

Constraints

  • The resolver that turns a step into its approver is the engine's; the console reads it, never re-derives it.

Evidence

Screenshots on the temporary branch claude/console-home-login-design-20261016 of objectstack-ai/objectui (commit 01244b886, never merged): setup-approval-requests.png.

Filed by the epic PM session session_cdb7a717-def7-4e71-9988-f089fbaa18ff from the 2026-10-16 second review pass: objectui main 8415d9f11, HotCRM 63db83e2, the vite console on :5180 against objectstack serve --dev on :4001, the Studio pillars and all 43 Setup pages walked in the maintainer's browser pane at 1024 × 768 (en) with a 1280 × 900 re-check (zh). The maintainer asked for every finding to be filed, verbatim: 「全部立卡」.

Activity

  1. added
    enhancementNew feature or request
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:workflowApprovals and automation — the work that runs without a person driving it
    and removed on Oct 16, 2026
  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p2; the epic PM session's 2026-10-16 second review pass, 「全部立卡」)
    Session: session_01SJn1GEkrbbBQkhGC26wEDw
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12608-approvals-names
    Worktree: objectui-issue-12608 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: the list grid's cell for a referenceVia pointer field (packages/fields and the packages/plugin-grid cell path that draws it), apps/console/src/pages/system/ApprovalsInboxPage.tsx (the empty state only), apps/console/src/registerApprovalsComponents.tsx if it owns a reached path, packages/i18n/src/locales/*.ts (new keys only), tests beside each, one .changeset/12608-*.md (stop on breach; explain in the report)
    Container & model: M, mode:cloud, model: opus (cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 5 on 「上限提到 5,提速」, this session's chat, 2026-10-15; slots refilled on the maintainer's 「补」, 2026-10-16)
    Clause-②: no
    Responsibility: n/a — names in place of ids on shipped lists
    Thread-read: none
    Serial constraints cleared: the Requests and Action History columns are declared by objectstack's plugin-approvals objects (sys-approval-request.object.ts listViews, read in this act), so the column set and the step-label source are producer work. This card builds only the objectui half: the pointer cell and the inbox line. The dev reports the producer half for this seat to file on objectstack. No open objectui PR touches ApprovalsInboxPage.tsx or the pointer cell; #12636 edits other packages/fields widgets.

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · amended (Clause-② corrected to yes) (priority:p2; the epic PM session's 2026-10-16 second review pass, 「全部立卡」)
    Session: session_01SJn1GEkrbbBQkhGC26wEDw
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12608-approvals-names
    Worktree: objectui-issue-12608 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: the list grid's cell for a referenceVia pointer field (packages/fields and the packages/plugin-grid cell path that draws it), apps/console/src/pages/system/ApprovalsInboxPage.tsx (the empty state only), apps/console/src/registerApprovalsComponents.tsx if it owns a reached path, packages/i18n/src/locales/*.ts (new keys only), tests beside each, one .changeset/12608-*.md (stop on breach; explain in the report)
    Container & model: M, mode:cloud, model: opus (cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 5 on 「上限提到 5,提速」, this session's chat, 2026-10-15; slots refilled on the maintainer's 「补」, 2026-10-16)
    Clause-②: yes
    Clause-② reason: the new language-pack keys land in @object-ui/i18n's en pack, the source of the exported TranslationKeys type (src/index.ts re-exports en), so the published type surface widens by those keys. That is the reading domain:ui seat 1 applied to objectui#12602. The seat's TIER_DEFAULT review comes before ready.
    Responsibility: n/a — names in place of ids on shipped lists
    Thread-read: 6211310929
    Serial constraints cleared: the Requests and Action History columns are declared by objectstack's plugin-approvals objects (sys-approval-request.object.ts listViews, read in this act), so the column set and the step-label source are producer work. This card builds only the objectui half: the pointer cell and the inbox line. The dev reports the producer half for this seat to file on objectstack. No open objectui PR touches ApprovalsInboxPage.tsx or the pointer cell; #12636 edits other packages/fields widgets.

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12608,
      "status": "done",
      "branch": "claude/issue-12608-approvals-names",
      "pr": 12658,
      "session": "session_018mfTYw2pCvEqHeGZDvybj8",
      "premise_still_valid": true,
      "summary": "The objectui half is built. (1) In the list grid, a text field that declares referenceVia now shows the target object's icon and the record's name, with the raw id in the tooltip. The pair is resolved per row by the existing resolveRecordPointer from @object-ui/fields; there is no second resolver. The page's targets are read in one batch per object. The cell is wired into all four ObjectGrid column paths, so it is generic: sys_activity.source_id and the sharing, audit and mail pointers get it too. (2) A target that cannot be read shows 'OBJECT_LABEL · record unavailable', never the bare id. (3) When the caller's pending tab in the Approvals Inbox is empty, the page sends one count query (listRequests with status pending and limit 1, which returns total). If the total is above zero, a muted line reads 'N requests are waiting on other approvers · Browse all requests'. Both assumptions held on 726d8c9: the grid drew the pointer as plain text (only DetailSection resolved it), and the inbox knew only the caller's own pending count. One gap: names are not links in console lists. The list host's recordHref answers only for the object being listed (listRecordActionsValue in app-shell ObjectView.tsx, outside the claimed files), so the cell draws the name without a link; it links whenever a host answers, and a test pins that. The shared checkout /home/user/objectui held a local branch of this name with no commits of its own. I detached it at origin/main and deleted the branch, as the dispatch instructed.",
      "tests": "All on the final source unless noted. New pins: packages/plugin-grid/src/__tests__/ObjectGrid.recordPointerCell-12608.test.tsx, 3/3 passed. It checks that the name renders with the briefcase icon and an href to the record, that the id appears only in the tooltip, and that one find with an id $in filter is sent for the page. It also checks the 'Opportunity · record unavailable' cell, plus a half-addressed-row control. apps/console/src/pages/system/ApprovalsInboxPage.waitingOnOthers-12608.test.tsx, 3/3 passed: the line appears for total 53, with exactly one count query and one 'Browse all requests' control; nothing appears for total 0; nothing appears when the count read fails. Ablations: each mutant was written with a python replace that asserts the anchor matches once, checked on disk with grep -c, and restored by git checkout HEAD, a blob-hash compare and an empty git diff HEAD. A1, the first attempt, was INVALID: an undefined identifier threw a ReferenceError and the control went red too. A1 again ('true ||' disables the pointer face): 2 failed, 1 passed (the two pointer pins red, the control green). A2 (the unreadable cell prints the id): 1 failed, 2 passed. A3 (inbox line never drawn): 1 failed, 2 passed. A3b (line drawn even for zero or null): 2 failed, 1 passed. No build or dist step was needed: both test files import the subject from source by relative path. Suites, all under os-verify-lock: (a) vitest over packages/plugin-grid, packages/fields and packages/i18n: 560 files passed, 1 skipped; 7431 tests passed, 20 skipped; vitest exit 0. (b) pnpm --filter @object-ui/app-shell test, in full, once: 1437 files passed, 1 skipped; 13596 tests passed, 9 skipped; VERDICT command-exit 0. (c) vitest over apps/console: 206/206 files, 2191/2191 tests; VERDICT command-exit 0. Suites a and b ran on 7de86d8. The only later commit, b04cf2e, adds an eslint-disable line and a 3-line comment in RecordPointerCell.tsx; suite c, the type-checks, lint and build ran on b04cf2e.",
      "gates": "Every exit code was captured before any pipe. pnpm check:i18n-keys exit 0. pnpm check:i18n-drift exit 0. pnpm check:i18n-designer-parity exit 0. check:i18n-dead-keys not run: no keys retired. node scripts/check-changeset-presence.mjs exit 0 (16 source files in 4 released packages, 1 changeset). node scripts/check-changeset-no-major.mjs exit 0. pnpm check:new-line-citations exit 0 (0 new). type-check exit 0 for plugin-grid, fields and i18n (each runs tsc --noEmit plus tsconfig.test.json) and for console (tsc --noEmit plus tsconfig.node.json). eslint on the touched files: exit 0, 0 errors. Its 6 new no-explicit-any warnings in ObjectGrid.tsx come from row: any written next to the file's existing value: any, row: any closures (220 warnings against 214 on base). Build: pnpm --filter '@object-ui/console^...' build && pnpm --filter @object-ui/console build gave VERDICT command-exit 0; the log shows plugin-grid, fields, i18n and console building. Not run locally and left to CI: the repo-wide pnpm lint and pnpm test. There are no PR check-runs to cite because no PR is open yet.",
      "mcp_calls": "2 GitHub MCP calls, both reads: issue_read get and issue_read get_comments on objectui#12608. Also 2 claude-code-remote add_repo calls with access read; both answered read_available and attached nothing. No write tools.",
      "api_writes": "0 REST writes. git push only: the empty feature branch, then 7de86d8 and b04cf2e to claude/issue-12608-approvals-names, and d2979a6 to claude/issue-12608-evidence.",
      "pr_title_draft": "feat(plugin-grid,console): list cells show the record a referenceVia pointer names; the empty inbox says who else is waiting",
      "pr_body_draft": "(elided: it is the body of objectstack-ai/objectui#12658, opened from it verbatim by the seat, except line 2, which the seat corrected to `Clause-②: yes` per the amended claim 6214494863)",
      "deviations": [
        "Names are not links in console lists. The list host's recordHref (listRecordActionsValue, app-shell ObjectView.tsx) answers only for the object being listed. Fixing it is outside the claimed files and would also turn every lookup cell in every list into a link. The cell links whenever the host answers, and a test pins that.",
        "Browse all requests keeps its existing destination (the inbox's All tab), not the sys_approval_request object list.",
        "Clause-② is for the seat to judge: new language-pack keys (grid.recordUnavailable and the approvalsInbox.waitingOnOthers family) can be reached from the @object-ui/i18n entry. No code export, prop or type member was added.",
        "The vitest suites ran with --maxWorkers=3 instead of 2 on this 4-core box, and ran detached under os-verify-lock with a foreground tail --pid wait, because app-shell's 1438 files run past the 10-minute foreground cap.",
        "The seed carried 0 approval requests, so the 53 were created through the HotCRM flows (details in the PR body).",
        "The mobile card layout of the grid was not measured."
      ],
      "files_changed": [
        "packages/plugin-grid/src/RecordPointerCell.tsx (new, internal)",
        "packages/plugin-grid/src/ObjectGrid.tsx (recordPointerFace, wired into the four column paths, dataSource added to the columns memo deps)",
        "packages/plugin-grid/src/__tests__/ObjectGrid.recordPointerCell-12608.test.tsx (new)",
        "packages/fields/src/index.tsx (doc comment only: names the list grid as the second call site)",
        "apps/console/src/pages/system/ApprovalsInboxPage.tsx (count query in load, empty-state line)",
        "apps/console/src/pages/system/ApprovalsInboxPage.waitingOnOthers-12608.test.tsx (new)",
        "packages/i18n/src/locales/{en,zh,ja,ko,de,fr,es,pt,ru,ar}.ts (new keys only)",
        ".changeset/12608-record-pointer-names.md"
      ],
      "screenshots": [
        "https://github.com/objectstack-ai/objectui/blob/d2979a6e56cfe7fc3df5537e01c0023664c28212/docs/design/findings/12608/approval-requests-light.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/d2979a6e56cfe7fc3df5537e01c0023664c28212/docs/design/findings/12608/approval-requests-dark.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/d2979a6e56cfe7fc3df5537e01c0023664c28212/docs/design/findings/12608/action-history-light.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/d2979a6e56cfe7fc3df5537e01c0023664c28212/docs/design/findings/12608/action-history-dark.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/d2979a6e56cfe7fc3df5537e01c0023664c28212/docs/design/findings/12608/approvals-inbox-light.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/d2979a6e56cfe7fc3df5537e01c0023664c28212/docs/design/findings/12608/approvals-inbox-dark.png?raw=true"
      ],
      "findings": [
        "Step label. Object: plugin-approvals, sys-approval-request.object.ts, sys_approval_request. current_step stores the node id ('manager_review'). Proposal: approval-service writes a current_step_label text snapshot of the node's label when it opens or advances a step, and the all_requests, my_pending and submitted_by_me list views show current_step_label instead of current_step. Do the same for sys_approval_action.step_name with a step_label column in recent, all_actions and by_actor. Why: the label is the engine's fact at that moment; the console must not read flow metadata to re-derive it.",
        "Flow label. Object: sys_approval_request. process_name is 'flow:NAME'. Proposal: an engine-written process_label snapshot of the flow's label at open, shown in every list view, with process_name kept for the api name (detail and tooltip).",
        "Waiting on. Object: sys_approval_request. pending_approvers is a textarea of slot addresses (user ids, emails, position:NAME). Proposal: a display column served by the approvals service (for example pending_approver_labels), resolved by the same resolver decide-time re-resolution uses (user name, position label, team label), added to the all_requests and my_pending columns. Why: the card's constraint is that the engine resolves the approver and the console reads the answer.",
        "Action History record and flow. Object: sys-approval-action.object.ts. request_id is a lookup that displays the request's display_title formula, process_name + ' · ' + record_id, so the raw id leaks into the Request column (measured: 'flow:opportunity_approval · 28W5lzLQhZs_53LL'). Proposal: add the pointer pair to sys_approval_action (object_name text plus record_id text with referenceVia 'object_name'), copied from the request when the action is written, and show record_id in recent and all_actions. This PR's cell then draws the name.",
        "Action History acted_as. Object: sys_approval_action. acted_as is a text slot address, and the Acted As column shows the raw user id 'AEC5i0zIm1UJz7HTTU3uTMUQUC5Hrd4g'. Proposal: a served display label for the slot, from the same resolver as Waiting on.",
        "Empty submitter_id: not reproduced. On a fresh HotCRM e50d048 boot, flow-opened requests carry submitter_id (the user whose write fired the trigger). The card's '—' was measured on HotCRM 63db83e2. The seat should measure there, or on a system-triggered open, before filing."
      ],
      "line_budget": "n/a",
      "open_questions": [
        {
          "question": "Should the console list host link pointer names (and lookup values) to records of other objects?",
          "options": [
            "A: in a new card, extend listRecordActionsValue (app-shell ObjectView.tsx) to answer any routable object, reusing the any-object builder from the detail page's bridge",
            "B: keep the deliberate own-object limit; pointer names stay unlinked in lists"
          ],
          "recommendation": "A. The four axes: (real business need) the ruling asks for the link, and admins on Approval Requests need to open the waiting record; (long-term soundness) one host-built builder, contract-first, no URL assembly in cells; (hard for AI to get wrong) the cell already links through the host, so this is one host change rather than per-cell tolerance; (startup focus) it is a small change, but it also links every lookup in every list, which is a product decision the seat should confirm."
        },
        {
          "question": "Where does the list face live: plugin-grid internal or the @object-ui/fields registry?",
          "options": [
            "A (taken): RecordPointerCell is internal to plugin-grid. No new export, every ObjectGrid list is covered, and the claim's code surface stays Clause-② no.",
            "B: a registry key in fields, reusable by other list surfaces such as RelatedList, but it needs a new exported key and so a Clause-② amendment"
          ],
          "recommendation": "A for now: there is no measured second list surface (zero pull), so implementation comes first."
        },
        {
          "question": "Contract conflict: os-dev.md tells the dev to open the draft PR and post the os-dev-report comment, but this dispatch's degraded channel forbids both.",
          "options": [
            "A (taken): follow the dispatch. The relay's repository_dispatch is refused with a 403 in this container, so the route os-dev.md assumes does not exist here.",
            "B: follow os-dev.md"
          ],
          "recommendation": "A. The PM opens the PR from pr_body_draft and posts this report. Separately, commits end with the model-free trailer pair that objectui AGENTS.md requires, not the harness's model-named trailer."
        }
      ],
      "out_of_scope_findings": [
        "class: none (a gap between spec and the installed pin, not filed) · objectstack main declares referenceDeletedWhen on sys_approval_request.record_id (cancel_reason = record_deleted), but objectui's installed @objectstack/spec 18.0.0-next.1 does not declare the key and no renderer reads it. The cancelled row therefore shows 'Opportunity · record unavailable' (target 4 is met) instead of the cancel reason's label. · carrier: objectui#12489, which the spec source cites as ruling 6203737086, letter A",
        "noted, not filed · RecordPointerCell caches answers per data source for the session, so a renamed or deleted target re-reads only on reload. The details-grid card re-reads on data invalidation. Recorded in the PR's Acceptance notes."
      ]
    }

    Relayed by seat domain:ui#4 (session_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final message (degraded channel: the session pushed and returned pr: null; the seat opened #12658 from pr_title_draft and pr_body_draft, and set pr above).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #12658 merged as f78efdd on main · read 2026-10-16T12:09Z

    probe on origin/main f78efdd reading control
    packages/plugin-grid/src/RecordPointerCell.tsx present the pointer face for text fields that declare referenceVia
    RecordPointerCell in packages/plugin-grid/src/ObjectGrid.tsx 2 mentions wired into the grid's column paths
    resolveRecordPointer in packages/plugin-grid/src 6 mentions the resolver @object-ui/fields already ships; no second resolver
    recordUnavailable / waitingOnOthers in packages/i18n/src/locales/*.ts 10 / 10 locale packs all ten shipped locales
    waitingOnOthers in apps/console/src/pages/system/ApprovalsInboxPage.tsx present the empty-state line, target 3
    ObjectGrid.recordPointerCell-12608.test.tsx present the pins from the ACCEPT
    .changeset/12608-record-pointer-names.md present —

    State change: this card stays open. The PR reads Part of #12608, because the producer half belongs to objectstack, filed as objectstack-ai/objectstack#23889 (step label, flow label, Waiting on, the Action History pointer pair, acted_as). pm:dispatched is removed in this act. The cloud session session_018mfTYw2pCvEqHeGZDvybj8 is archived. The contract review of record is TIER_DEFAULT PASS at b04cf2e (6214541723), under the amended claim 6214494863 (Clause-②: yes).

    Held for the maintainer: names in console lists are not links yet, because listRecordActionsValue in app-shell ObjectView.tsx answers recordHref only for the listed object. No card is filed until the maintainer answers.


    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:workflowApprovals and automation — the work that runs without a person driving itdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions