Repository navigation
approvals B1 (child of objectui#2763): the sys_approval_request detail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar #12045
Description
Ruled: 6079807016 · letter 乙 · 2026-10-09T11:18Z
Blocked-by: #12484
This line gates the page-mount step (ruling 6079807016). The other two upstreams have closed:
- spec(ui) v18: declare the approval decision panel as a page component type with a strict
ComponentPropsMaprow (objectui#12045 B1, ruling 乙) objectstack#22472, the spec row, is merged as7731d7018f. It keeps the namerecord:approval_decision, with empty strict props. - plugin-approvals: serve
sys_approval_request's detail page as a slotted page through the manifestpages, and declare the thread reply asapproval_comment(objectui#12045 B1, ruling 乙) objectstack#22473 is merged through PR feat(plugin-approvals): serve the approval request page through the manifest pages, and declare the thread reply as approval_comment (#22473) objectstack#22567. It serves thesys_approval_requestpage, whoseactionsslot holds onerecord:approval_decisionnode.
objectui's move to next (#12030) is what remains. The build round landed as a368ccb1. The page-mount step also carries objectui#12077 (naming the pending approvers), which is pm:blocked on this card.
Filing gate ④ — a coordination node: a child of the cross-layer parent objectui#2763. Parent: objectui#2763 (pm:epic). The maintainer's ruling there (5166015115): A2 first, then the A children, with B1–B3 following in order. All three A children have landed:
- A2, objectui#12029 (
049012b):RecordPreviewCard; - A3, objectui#12033 (
ba9e820):DecisionProgressIndicator; - A1, objectui#12032 (
d99b731):createApprovalRequestsDataSource, withApiDataSource.findOnenarrowed to 404.
Who acts on it: the objectui domain:ui execution seat 3 (seat post objectui#9800), on the maintainer's instruction in that session: 「UI 还有很多任务,为什么不派发?包括epic 也可以派发」.
What B1 is (from the parent's scope, verbatim)
B1 ·
sys_approval_requestdetail page — sections (request fields), the polymorphic target-record card (A2), the progress widget (A3), asys_approval_actionrelated-list (timeline), andDeclaredActionsBar; data via A1.
And the parent's honest caveat, which the maintainer's ruling repeats: prove that ActionParamDialog can host the decision composer (a comment, several attachments, and the timeline's context). If it cannot, keep one thin custom approve/reject action, a component-level exception and not a hand-rolled page.
Measure first: the shape is open on current main (d99b731f)
-
Where the page's metadata lives. objectstack's
plugin-approvalsships thesys_approval_requestobject and its list views, but no page for it (source search on objectstackmain). Today the standardRecordDetailViewsynthesizes a record page from the object definition. Measure which route B1 takes:- (a) a page objectui composes from the synthesized record page plus the three components, mounted where the inbox's drawer is today; or
- (b) page metadata authored in objectstack's
plugin-approvals, which is objectstackdomain:serviceswork and a cross-repo child withBlocked-by:on this card.
⛔ Do not author objectstack metadata from this card.
-
Component types. For page metadata to reference A2 and A3, they must be registered as component types. Measure whether objectui's page renderer accepts a newly registered type, or whether
@objectstack/spec's page schema restricts component types to a declared set. A spec declaration is v18 work for the spec seat (objectui resolves^17until 18.0 GA):Blocked-by:on a spec-lane card. -
The composer. The
ActionParamDialogproof above.
Carried from the A children (on the parent's checklist)
- From A2 (
6076152376): the card is cause-free, and a platform-asserted deletion (an approval'scancelled+record_deleted) stays the caller's to render. B1 decides how this page renders it. - From A1 (
6077174661,6077915878): the get carriesvieweranddecision_progressfor pending requests. The routed source sends side reads (sys_user, comments, activities, history) to the host. A refused or failed get now shows RecordDetailView's own states.
Duplicate check
- Semantic issue search on objectstack-ai/objectui (open and closed), sys_approval_request detail page metadata record page approvals timeline DeclaredActionsBar: 9 hits (1 open, 8 closed). The open hit is the parent objectui#2763. The closed hits are inbox-drawer defects (objectui#11677, objectui#5553), record-page approval entries (objectui#3055, objectui#3461), the polish card objectui#2762, and two unrelated cards (objectui#10224, objectui#8442). None is this page.
Dedupe words: approval request detail page standard renderer · sys_approval_request record page metadata · approvals composer ActionParamDialog
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 9, 2026
Claim: PM loop round 3
Session:
session_01CGZy1BGCjdN5cXqL9cnvB8Account:
os-support-aiBranch:
claude/issue-12045-approval-detail-pageWorktree:
objectui-issue-12045Domain:
domain:uiSeat:
domain:ui#3Authority: an epic child (
pm:epicstays on it). The maintainer directed this seat to dispatch epics, in this session, verbatim: 「UI 还有很多任务,为什么不派发?包括epic 也可以派发」.File surface (on
maind99b731f). This round is measurement only: no PR, and no file changes on the branch. The card's three open shape questions get measured answers and options first:plugin-approvals;@objectstack/spec's page schema lets page metadata reference a newly registered component type (A2, A3), or needs a declaration;ActionParamDialogcan host the decision composer (a comment, several attachments, timeline context), or one thin custom approve/reject action is needed.The build surface is written by a claim amendment once the seat has answered, or the maintainer has where the answer is theirs. Reads anywhere in objectui and objectstack are fine; scratch probes stay uncommitted and are deleted.
⛔ Not on it:
plugin-approvals,@objectstack/spec);packages/components/src/ui/**andpackages/i18n/**.Container & model:
L,mode:subagent(an in-session subagent),model: opus(dispatch-gates --tier --repo objectstack-ai/objectuiover these paths: no path-derived mandate; default tier). The contract review runs at the ceiling,CONTRACT_REVIEW_TIER, on the build round, because of theClause-②: yesline below.Clause-②: yes
Responsibility:
objectui and objectstack: the approval request's detail is a hand-rolled drawer in the console, so every defect of the standard record renderer's own features (badges, action hierarchy, i18n, layout) recurs there | the platform path: the standard record renderer, A1's approvals data source, A2's record card, A3's progress widget, sys_approval_action as a related list, and DeclaredActionsBar | every approver and submitter who opens a requestThread-read: none
Serial constraints cleared:
noneblocking (measurement only).d99b731,049012b,ba9e820).coreandtypes) touches no approvals file.Why
Clause-②: yes: registering A2 and A3 as component types that page metadata can reference widens what metadata objectui accepts. The build round's amendment confirms or narrows this line.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 12045,
"status": "needs_decision",
"branch": "claude/issue-12045-approval-detail-page",
"pr": null,
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8",
"premise_still_valid": true,
"summary": "Measurement round, no code. Q1: RecordDetailView reads page metadata only from the metadata
pagetype (usePageAssignment over meta.pages). It has no page prop, and the console has no client-side page registration. With no page, the request's own page synthesizes page:header, record:highlights, record:path, page:tabs (record:details, record:related_list on sys_approval_action) and record:discussion, and shows zero decision buttons (measured on the real view). An authored slotted page, the shape plugin-auth already ships for sys_user through its manifestpages, reached the same view. It rendered a newly registered type and record:quick_actions, whose Approve opened ActionParamDialog and POSTed to the request's approve route. Q2: in spec 17.7.0, PageComponentSchema.type is an open union, so safeParse admits any type that is not retired. The vocabulary closes at authoring time (lint rule component-type-unknown, inside the seven reserved namespaces), and props are checked only for ComponentPropsMap rows. Q3: ActionParamDialog already hosts a comment and several attachments; on main, the inbox drawer runs approve/reject through it with noexclude. It cannot show the timeline while it is open: it is modal, and its state carries only params, title and description. Two further findings: A2 needs no page node, because record_id is a declared referenceVia pointer pair that objectui never reads. And sys_approval_action is closed to ordinary positions on the data door, so the timeline needs A1 to route it to the actions route.","tests": [
"Spec probe (scratch node script against the installed @objectstack/spec 17.7.0 and @objectstack/lint 17.7.0, in a detached worktree at d99b731), exit 0. PageComponentSchema.safeParse results: record:related_list OK (enum control); user:profile FAIL custom@type (control for the retired-by-name refusal); OK for record:preview_card, record:approval_progress, record:approvals, record:attachments, approvals:record_preview, approvals:decision_progress and record-preview-card. PageSchema.safeParse of a slotted sys_approval_request page is OK for all three namings tried. validateComponentTypes: 0 findings for approvals:* and the kebab name; 2 component-type-unknown findings for record:preview_card and record:approval_progress; negative control record:detials gives 1. validateComponentProps: 0 findings for unknown keys on unregistered types; control record:details with key notAKey gives 1 component-props-unknown-key. slots.rightRail: FAIL unrecognized_keys.",
"Real RecordDetailView probe (a scratch test in the console vitest project, mounted over createApprovalRequestsDataSource with only the HTTP transport doubled):
bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c 'pnpm exec vitest run apps/console/src/services/zzprobe12045.recordPage.test.tsx'gave Test Files 1 passed, Tests 2 passed, andVERDICT command-exit 0on the final run. S1 (no page metadata): the synthesized types are page:header, record:highlights, record:path, page:tabs, record:details, record:related_list and record:discussion; the tabs are Details and Related; there are 0 Approve and 0 Reject buttons. S2 (an authored slotted page): the registered approvals:record_preview rendered with its properties, and its RecordContext was sys_approval_request carryingviewer. record:quick_actions (location record_section) rendered Approve and Reject. Approve opened the param dialog with a Comment textarea and an Attachments file input (multiple=true). Confirm POSTed /api/v1/approvals/requests/req_12045/approve with body {comment: probe comment}.","Existing pins on main, run read-only: ActionParamDialog.test.tsx, RecordDetailView.approvalDeclaredActions.test.tsx and tests/DeclaredActionsBar.test.tsx through the same lock gave Test Files 3 passed, Tests 100 passed,
VERDICT command-exit 0. Among them: 'renders a file param as a real upload control, not a text box (#2698)' and 'carries the decision attachments param the hand-written buttons could not'.","Two-leg grep for decision outputs (decision_output_defs / decisionOutputDefs): the control legs hit app-shell DeclaredActionsBar.tsx (4) and decisionOutputParams.ts (3); the test leg over plugin-detail, react and core gives 0 (exit 1). record:quick_actions therefore has no decision-output params.",
"Cleanup: the scratch test is deleted (git status --porcelain: 0 lines; HEAD stays d99b731), node_modules is removed, and the worktree is removed (absent from git worktree list, directory absent, no claude/issue-12045 branch). The node probe is deleted from the scratchpad.",
"NOT MEASURED: a live backend's data-door answer for an ordinary approver reading sys_approval_action. Reason: no backend was started this round. The closure is read from the object's own comment ('this table is deliberately closed to ordinary approver positions') and the spec's fileAccessDelegate doc."
],
"mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/12045/comments (this os-dev-report), through scripts/pm/post-stamped.mjs and the fleet relay",
"open_questions": [
{
"question": "Q1 — where does the sys_approval_request detail page's metadata live?",
"options": [
"A — objectui composes it. The drawer mounts RecordDetailView embedded over A1's get, with A2, A3 and DeclaredActionsBar as React siblings around the synthesized page (what the business record page does with the bar today). How far this is metadata: sections and the sys_approval_action related list come from object metadata, but the placement of A2, A3 and the bar is React. The synthesized page also brings record:path and a record:discussion thread, which only code can remove. No objectstack work, and it works on 17.7.0 backends. RecordDetailView takes no page prop (its props are dataSource, objects, onEdit, objectNameOverride, recordIdOverride and embedded), so any page shape beyond the synthesized one is code.",
"B — a slotted page served from objectstack plugin-approvals: kind slotted, with alerts, highlights, tabs, and discussion set to an empty list. It is registered through the plugin's manifest
pages, the way plugin-auth registers SysUserDetailPage from platform-objects. objectui only registers renderers and mounts RecordDetailView. How far this is metadata: fully. The placement is page metadata, and the probe shows objectstack's lint walks slots and page:tabs children. Measured: the real RecordDetailView picked an authored slotted page up through usePageAssignment and rendered it. Cost: an objectstack domain:services cross-repo child, with Blocked-by on this card. It lands on objectstack's v18 line (main is in changesets pre modenext, nothing is published undernext, and 17.x ships no new release), so only v18 backends serve it. The slot menu has no aside slot (slots.rightRail is refused).","C — found: A2 needs no page node. sys_approval_request.record_id is declared Field.text with referenceVia: 'object_name', the spec's polymorphic pointer pair. objectstack main declares 7 such fields on 6 objects: sys_approval_request, sys_activity (2), sys_audit_log, sys_record_share, sys_share_link and sys_automation_run. objectui reads referenceVia nowhere at runtime (only two comments in plugin-list), and getCellRenderer dispatches on the field type alone. If A2 becomes the display renderer for a referenceVia field, record:details draws the card wherever the pair shows, B2's target column gets it for free, and so do the other five objects. C composes with A or B for A3 and the actions."
],
"recommendation": "B + C. Business need: every approver and submitter who opens a request; C also serves the 6 objects that measurably declare pointer pairs. Long-term soundness: B is the ruling's stated reason (5166015115: AI edits spec-validated metadata, not ungated React); A keeps the placement in React, the very drift this epic exists to cure. C implements a key the spec already declares (spec declaration above implementation) and adds no surface. Hard for AI to get wrong: B's page goes through os validate and lint (the type vocabulary, plus the props gate for declared types); A has no validator; C adds no authorable key at all. Startup scope: C adds zero authorable surface; B adds one page and at most one new type (Q2). The trade-off for the maintainer: B moves B1's landing to v18 backends, while A lands on 17.7.0 now. ⛔ A now and B later is not recommended: a staged transition needs named external-user evidence, and none was found."
},
{
"question": "Q2 — can page metadata reference a newly registered objectui component type under @objectstack/spec 17.7.0, and how should B1 name its new components?",
"options": [
"Measured basis. PageComponentSchema (spec ui/page.zod) declares
type: z.union([PageComponentType, z.string()])followed by a.superRefinethat refuses only RETIRED_PAGE_COMPONENT_TYPES, so the parse is open and safeParse admits any type that is not retired. The closure is at authoring time: component-type-vocabulary.ts derives RESERVED_COMPONENT_TYPE_NAMESPACES (page, record, app, nav, global, ai, element) from the enum, and @objectstack/lint's component-type-unknown rule refuses an unknown type inside them. validateComponentProps checks props only for types with a ComponentPropsMap row. At runtime, objectui rendered a newly registered type from page metadata (probe S2).","A — own namespace (for example approvals:decision_panel), with no spec change. It parses and lints clean on 17.7.0 today. Its props are unchecked: measured 0 props findings for an unknown key on an unregistered type, against 1 for the record:details control. A misspelled prop is silently ignored.",
"B — a spec v18 ComponentPropsMap row, in the record namespace or its own, so os validate checks the props bag. This is a spec-lane card. objectui's tests parse it only after objectui#12030 (held until
nextpublishes). A page from Q1-B ships on v18 anyway, so the timing aligns.","C — narrow first. A2 needs no type (Q1-C). A3 and the decision actions fold into ONE type: the drawer shows them together today, and RecordApprovalsPanel already composes progress with the bar for business records. That is one new type, not three."
],
"recommendation": "C + B: one new type with a v18 spec row. Business need: the decision panel is the one component the request page lacks. Long-term soundness: contract-first, with the row declaring what the renderer reads. Hard for AI to get wrong: declaration means enforcement, and the props gate catches typos; A leaves a silent-ignore props bag, the consumer-side tolerance this frame warns against. Startup scope: one row instead of three. A only if the maintainer rejects v18 timing, and then with that cost stated. Related, carried as a finding: objectui's existing record:approvals and record:attachments already sit in the reserved record namespace with no row, so an authored node of either is refused by component-type-unknown."
},
{
"question": "Q3 — can ActionParamDialog host the approval composer (a comment, several attachments, the timeline's context), or does B1 need one thin custom approve/reject action?",
"options": [
"A — ActionParamDialog through DeclaredActionsBar, inside the Q2 decision panel, with no custom composer. Proven: plugin-approvals declares approve/reject params as comment (textarea) and attachments (file, multiple). The probe opened the dialog with both and POSTed to the request's route with
{id}resolved to the request. 100 of 100 existing pins pass. The inbox drawer on main already does exactly this: it mounts DeclaredActionsBar with noexclude, so the 'richer inline composer, excluded from the bar' in the parent's caveat is gone from main. Limitation: ParamDialogState carries only params, title and description, and the dialog is modal, so the timeline can be read before and after composing but not during.","B — the spec's existing record:quick_actions (no new type for the actions). Measured: it rendered approve/reject from wire-shaped CEL envelopes and posted correctly. But decision-output params (decision_output_defs becoming outputs.KEY params), the override notice naming bypassed approvers, and approver-name resolution exist only in DeclaredActionsBar (two-leg grep). For an approval node that declares decision outputs they are silently dropped.",
"C — one thin custom approve/reject action: a non-modal inline composer (comment, several files, with the timeline beside it), registered as a component-level exception, with approve/reject excluded from the bar. It re-implements upload staging and decision outputs."
],
"recommendation": "A. Business need: the live approver surface (the drawer) already decides through this dialog, and no pull for composing beside the timeline was measured once the inline composer left. Long-term soundness: one decision path shared by the drawer, the business record page (objectui#3055) and B1. Hard for AI to get wrong: the decision params stay declared metadata in plugin-approvals; C moves the form back into React, and B silently drops outputs. Startup scope: no new composer. C only if the maintainer names a need to compose beside the timeline."
},
{
"question": "Q4 — the drawer's thread reply (POST /approvals/requests/ID/comment, stored as a sys_approval_action row with action comment) has no declared action. What does B1 do with it?",
"options": [
"A — declare approval_comment on sys_approval_request in plugin-approvals: comment required, attachments optional, visible for the submitter or a pending approver, matching the service's own check. The decision panel then carries it through the same dialog. This is objectstack work in the Q1-B child.",
"B — B1's timeline is read-only, and the reply stays in the bespoke drawer until B3 decides.",
"C — a bespoke reply box inside the decision panel."
],
"recommendation": "A. Business need: the reply is the one drawer interaction that does not ship as metadata. Long-term soundness: the same declared-action path as the nine others. Hard for AI to get wrong: a declared param set, not React. Startup scope: one action declaration in the child Q1-B already needs, and no new objectui surface."
}
],
"out_of_scope_findings": [
"carrier: B3 (it deletes the drawer) · observation, no class: ApprovalsInboxPage still carries the comment saying the rich approve/reject composer stays because the generic param dialog cannot yet collect file attachments, while the drawer on main mounts DeclaredActionsBar with no exclude and runs approve/reject through ActionParamDialog · noted, not filed",
"carrier: B1's build round, where Q2 settles naming · objectui registers record:approvals and record:attachments inside the spec-reserved record namespace with no ComponentPropsMap row, and STRING_ARM_REGISTERED_TYPES is empty. @objectstack/lint 17.7.0 validateComponentTypes therefore refuses an authored node of either, while RecordDetailView honours an authored record:approvals (hasExplicitApprovals). Measured on the lint function, not through the os validate CLI. objectstack main has no authored producer (zero hits), so no reach is established · dedupe words: record approvals reserved namespace component-type-unknown vocabulary · noted, not filed",
"carrier: 承接者:无 · buildDefaultPageSchema reads slots.rightRail, which the spec's slot map refuses (unrecognized_keys, measured), so no served page can reach it; dormant · noted, not filed",
"carrier: B1's build round · on the request's own page, RecordDetailView's useRecordApprovals still reads /approvals/requests?object=sys_approval_request&recordId=ID (approvals on the request itself, always empty). That is one wasted read per page open, seen in the probe's request log · noted, not filed"
],
"build_round_surface": {
"objectui": [
"Mount: apps/console/src/pages/system/ApprovalsInboxPage.tsx. The
?request=Sheet hosts RecordDetailView embedded over A1's get; B3 removes the bespoke drawer body.","Data source: apps/console/src/services/approvalRequestsDataSource.ts. Route sys_approval_action reads scoped to one request_id to GET /approvals/requests/ID/actions; the data door is closed to ordinary positions. Also put the routed source behind every sys_approval_request record page. The Setup Requests object route reads through the data door today, which attaches no
viewerordecision_progress(attachViewers runs in listRequests and getRequest only). A1 is not yet wired anywhere outside its tests.","A2 via referenceVia: packages/fields/src/index.tsx (getCellRenderer dispatches on type only; it needs a referenceVia branch), plus app-shell registering packages/app-shell/src/views/record-preview/RecordPreviewCard.tsx as that renderer. Whether plugin-detail's details grid goes through getCellRenderer is to be measured in the build round.",
"Decision panel type: a new renderer under packages/app-shell/src/views/ that composes DecisionProgressIndicator (views/approval-progress) and DeclaredActionsBar, registered through ComponentRegistry.register (the views/record-approvals-renderer.tsx pattern) and imported for its side effect from packages/app-shell/src/index.ts. Add a packages/types node type if it is authorable (AGENTS #0.1). packages/cli/src/utils/known-schema-types.ts lists record:approvals today and would list the new type.",
"Tests: the real RecordDetailView over A1 with a served slotted-page fixture (this probe's shape), referenceVia renderer pins, routed sys_approval_action pins, and a changeset (the claim's Clause-② yes: minor on the package whose registered surface widens)."
],
"outside_objectui": [
"objectstack plugin-approvals (domain:services, a cross-repo child with Blocked-by on this card): a slotted sys_approval_request page file, the manifest
pagesentry, its pages.NAME.label translations as platform-objects carries them, and approval_comment if Q4-A.","objectstack @objectstack/spec v18: a ComponentPropsMap row for the decision panel (Q2-B), and a vocabulary answer for objectui's existing record:approvals and record:attachments.",
"objectui#12030 (move to
next) before objectui's tests can parse v18 spec rows."]
},
"gates": [
"none owed: measurement round with no diff; the dispatch says no gate is owed. The probe runs are listed under tests."
],
"deviations": [
"Worktree rule 1: the probe worktree was created detached (git worktree add --detach, off origin/main d99b731), with no local branch and no empty-branch push. The dispatch's write budget is one comment and nothing else, and the claim says no file changes on the branch.",
"Q2's 'name the schema and the line': named by symbol and quoted line content, not by line address, because objectui AGENTS §5 #11 bans line addresses in issue comments.",
"The scratch probe test lived inside the probe worktree (apps/console/src/services/), because the console vitest project collects only inside the repo. It was never committed, then deleted, and the worktree was removed. The node probe lived in the scratchpad and was deleted.",
"The first RecordDetailView probe run used bare-string
visiblepredicates, and record:quick_actions hid approve/reject ('has is not a function' on the legacy evaluator). That fixture was unfaithful: ObjectSchema.create parses a bare string into the {dialect: cel} envelope, so the wire carries the envelope. The rerun with the wire shape rendered both. Recorded so the first reading is not mistaken for a finding.","Added a
build_round_surfacefield. The dispatch asks for the build round's touch list with paths, and no standard report field carries it."],
"files_changed": [],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Decision request —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T10:01Z. State:pm:dispatched→needs-user-decisionin this act. The claim, the assignee and the branch stay. No code has been written: the dev's measurement round is report6078632273.一句话问题
审批请求详情页(审批人打开一条请求看到的页面)有两种做法:
另外,如果走元数据,新的"审批决策面板"组件要不要在 v18 的 spec 里加一行属性声明,这会碰到你 08-03 裁决里"非协议变更"那句话。
背景
d99b731;049012b;ba9e820。DeclaredActionsBar组成请求详情页。6078632273,只读,不改代码)实测了三件事:RecordDetailView只从元数据的page类型读页面,没有 page 参数,console 也没有客户端页面注册。不写页面时,它自动合成"详情 / 相关"两个页签,决策按钮为 0。sys_user发的那份)时,同一个视图渲染出了新注册的组件类型和record:quick_actions。点"同意"会打开ActionParamDialog,带评论框和多附件,并 POST 到请求的 approve 路由。PageComponentSchema.type是开放的联合类型,只拒绝已退役的类型。词汇在作者侧由 lint(component-type-unknown,只管 7 个保留命名空间)收口。属性只对ComponentPropsMap有行的类型做校验。main已进入预发布模式(tagnext),17.x 不再发版(你今天的原话:「17.x 不会发新版」)。所以 plugin-approvals 里新加的任何页面都只随 v18 发布。objectui 改用next已立卡 objectui#12030,等next发出。Governing text
Governing text: maintainer ruling
5166015115(objectui#2763, 2026-08-03), quoted verbatim:ActionParamDialog能承载(评论 + 多附件 + 时间线上下文),不能则保留一个薄的定制 approve/reject action」冲突所在: 第一条指向"做成元数据",第二条写着"非协议变更"。元数据路线要么接受属性不受校验(违背第一条的"受 spec 校验"),要么在 v18 的 spec 里加一行(是协议增项,碰第二条)。本席不能在两句裁决之间替你取舍,所以回箱。
本席已按裁决自行定下、不在此请示的一项: 审批意见框走
ActionParamDialog,不做定制意见框。裁决的条件是"先证明能承载",dev 实测已证明:评论 + 多附件可以,main 上的审批抽屉现在也这样用,现有 100 条测试全过。唯一做不到的是"打开对话框时同时看时间线",因为对话框是模态的。前提(每条带复查命令)
sys_approval_request的页面。git -C /home/user/objectstack grep -n -E "^\s*pages\s*:" origin/main -- packages/plugins/plugin-approvals/应为 0 行;对照组 plugin-auth 同一命令命中 1 行(auth-plugin.ts,pages: [SysOrganizationDetailPage, SysUserDetailPage])。git -C /home/user/objectstack grep -n "RETIRED_PAGE_COMPONENT_TYPES\|z.union(\[PageComponentType" origin/main -- packages/spec/src/ui/。next尚未发布。npm view @objectstack/spec dist-tags:只有latest17.7.0 和rc,没有next。RecordDetailView没有 page 参数。git -C /home/user/objectui grep -n "export function RecordDetailView(" origin/main -- packages/app-shell/src/views/RecordDetailView.tsx:参数只有dataSource, objects, onEdit, objectNameOverride, recordIdOverride, embedded。选项 × 真实代价
RecordDetailView(走 A1 数据源),A2、A3 和决策按钮作为 React 兄弟节点摆在合成页四周。不碰 objectstack,不改 spec。pages注册,同 plugin-auth 的做法)。objectui 只注册渲染器。新增一个"审批决策面板"组件类型(进度 + 决策按钮合一),在 v18 spec 里加一行ComponentPropsMap,os validate能校验它的属性。next发布、objectui#12030 切过去,现有 17.x 用户看不到。新增一行 spec 是协议增项。approvals:decision_panel),17.7.0 下解析和 lint 都通过。两条与选项无关、本席会一并执行的:
sys_approval_request.record_id已在 spec 里声明为多态指针(referenceVia: 'object_name'),objectstack 有 6 个对象共 7 个这类字段,但 objectui 运行时从来不读它。把 A2 卡片接成这类字段的显示渲染器,就是补实现一个已声明的键,不新增任何可写的面。甲、乙、丙都需要它。POST /approvals/requests/ID/comment)目前没有声明式动作。走乙或丙时,在 plugin-approvals 声明一个approval_comment动作;走甲时,时间线只读,回复留在旧抽屉里,等 B3。业务含义直译:
四轴(业务立场)
ActionParamDialog),没有测到"必须马上换"的拉动。甲的好处是早;乙、丙要等 v18,对审批人来说是晚,而不是用不了。os validate当场报错。推荐
推荐:乙。 只看①选乙;②③④ 是否翻转:否。② 只影响时间点:现有抽屉能用,没有测到紧迫拉动。③ 同向。④ 只多一行 spec。
回退:丙。 如果你坚持"非协议变更",就选丙:页面仍是元数据,代价是属性不受校验,注释里要写明这一点,等以后再补声明。
不推荐:甲。 "先甲后乙"是分期过渡,按创业阶段"过渡从紧"的原则,需要有具名外部用户的证据才荐。dev 没找到这样的证据。
置信缺口:
sys_approval_action"的回答,目前只读到对象的注释说它对普通岗位关闭。所以 A1 要把时间线路由到 actions 接口,这一点还没实测。裁后执行
domain:services子卡:plugin-approvals 的请求页面、manifestpages条目、翻译,以及approval_comment动作。本卡挂Blocked-by:那张子卡。ComponentPropsMap行。next之后再做。Clause-②改为 no。相关:
5166015115;next);6037890422);next发布的前置);6078632273。os-decision-facets
os validate当场报错;丙下静默忽略;甲没有元数据校验。Prior rulings read: approvals metadata / page / composer / v18 timing → 4 hits (
5166015115,6006187681, objectstack#220506037890422, today's 「让 objectui 提前改用 next 预发布版」 on objectui#12030); ADR none on approval pages; thread: 2推荐:乙。只看①选乙;②③④ 是否翻转:否。
置信缺口:普通审批人读
sys_approval_action的真实回答未实测;v18 发布时间不在本席视野内。Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Ruling: batch #303 item 2 · letter 乙 (a metadata page in plugin-approvals, one v18 spec
ComponentPropsMaprow) · maintainer 「同意」 2026-10-09T11:17ZDirector seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Presented in batch #303 from thedomain:uiseat 3's decision request (6078697273) on the dev's measurement round (6078632273), B1 of the approvals epic objectui#2763: 甲 objectui composes the page in React now, on 17.7.0 backends; 乙 a slotted page authored in objectstack's plugin-approvals (manifestpages, as plugin-auth shipssys_user's), objectui registering renderers only, one new "decision panel" component type declared in v18'sComponentPropsMapsoos validatechecks its props; 丙 the same page with the type in its own namespace and no spec declaration. The seat recommended 乙 with 丙 as the fallback; this seat recommended 乙; the maintainer answered 「同意」. Thread-read: 6078697273. Freshness: body unchanged; no comment since the presentation; labelsenhancement,needs-user-decision,priority:p3,pm:epic,domain:ui,area:workflow; the claim, assignee and branch kept, as the request said. Premises re-read: objectstackorigin/mainships nopages:in plugin-approvals (0 hits; plugin-auth, the control, 1),PageComponentSchema.typeis an open union refusing onlyRETIRED_PAGE_COMPONENT_TYPES, and props are checked only forComponentPropsMaprows; objectuimain'sRecordDetailViewtakes no page prop; objectstackmainis in changesets pre modenext, nothing is published undernext, and 17.x ships no new release (the maintainer today: 「17.x 不会发新版」).The ruling
乙 — the page is metadata in plugin-approvals, and the decision panel is declared on v18's spec. The request page is a slotted page in objectstack's plugin-approvals, registered through the manifest's
pages; objectui registers the renderers and mountsRecordDetailView; one new component type, the decision panel (A3's progress and the decision actions in one), is declared in v18'sComponentPropsMap, so its props are validated (Clause-②: yes, a v18 widening). The 2026-08-03 ruling (5166015115) is read as follows, settled here: its first sentence governs (AI edits spec-validated metadata, never ungated React); its scheduling note "(非协议变更)" was written while 17.x still shipped, and withmainon the v18 pre-release line and 17.x closed, oneComponentPropsMaprow on v18 is an ordinary widening of that line, not a protocol door opened for 17.x. Carried regardless of the letter, as the seat proposed: A2 becomes the display renderer forreferenceViapointer fields (a key the spec declares on 7 fields of 6 objects and objectui never read); the timeline's reply is a declaredapproval_commentaction; the composer staysActionParamDialog, proven to carry a comment and several attachments, and the modal's inability to show the timeline beside it is accepted. ⛔ Not taken: 甲 (the placement stays in React, the drift this epic cures; a 甲-then-乙 transition would need named external-user evidence, and none was found), 丙 (unchecked props: a misspelled property silently drops a block).Execution order, as the request's 裁后执行 states: the ui seat files an objectstack
domain:serviceschild (the request page, the manifestpagesentry, translations, theapproval_commentaction) and a spec-lane card (the v18ComponentPropsMaprow); this card carriesBlocked-by:on both for the page mount; objectui's build round does the v18-independent parts first (A2 as the pointer renderer, the decision-panel component, A1 wired to the request page and the timeline routed to the actions route); the page mounts after objectui#12030 switches objectui tonext.Prior rulings read: 5166015115 (the parent's ruling, its scheduling note read as above); 6006187681; objectstack#22050 ruling 6037890422 (the v18 start); objectui#12030 (the switch to
next, on the maintainer's 「让 objectui 提前改用 next 预发布版」); the A children's records 6076152376, 6077174661, 6077915878. 自检: 只看①选乙;②③④ 是否翻转:否(② 只影响时间点:现有抽屉今天能正常审批)。置信缺口:普通审批人读sys_approval_action的真实回答未起后端实测(A1 需把时间线路由到 actions 接口);v18 的发布时间不在本席视野内(objectstack#22085、#22130 在飞,之后要维护者批准发布)。State
needs-user-decision→pm:dispatchedin this act (the claim 6078298531, the assignee and the branchclaude/issue-12045-approval-detail-pagestay, as the request kept them); theRuled:line added to the body. The ui seat files the two child cards and writes theBlocked-by:lines, then runs the v18-independent build round.Generated by Claude Code
18 remaining items
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (B1's page-mount step, the fresh claim that ACCEPT
6082369712and the unlock scan6159947599name)Session:
session_01JZnppgHRp4sREcouhkQYDBAccount:
sunhaolinBranch:
claude/issue-12045-page-mountWorktree:
objectui-issue-12045Domain:
domain:uiSeat:
domain:ui#1File surface:
apps/console/src/AppContent.tsx(the request record routes, and the export comment that names a test filemaindoes not have),apps/console/src/services/approvalRequestsDataSource.tsand its tests,apps/console/src/pages/system/ApprovalsInboxPage.tsx(where a request opens) and its tests,packages/app-shell/src/views/RecordDetailView.tsx,ObjectDataPageandInterfaceListPageonly where a request record surface picks its data source,.changeset/12045-approval-decision-panel.md(append-only, for the console bullet nit), and one new.changeset/12045-*.md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuinames no path-derived mandate)Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: 6159947599
Serial constraints cleared: read at objectui
mainfc528c3eb.Already done, not in this round: the registration of
record:approval_decision, its index import, its known-types line and its zod arm landed with objectui#12342 (PR feat(app-shell): register record:approval_decision, armed in @object-ui/types (objectui#12342) #12362,52d6194).Out of this surface:
packages/app-shell/src/views/ObjectView.tsx(seat 2's objectui#12417 and seat 3's objectui#12423 are in flight in it) andpackages/i18n/**(objectui#12423). If the drawer or split-overlay route needs either, the dev stops and reports.No other overlap: no open PR names these files. objectui#12077 (pending approvers' names) is
pm:blockedon this card and stays out of this round.Accept-set reading: console routing and the place a request opens. No
@object-ui/*export, prop or option changes. If one is needed, the dev stops and reports, andClause-②is re-stated.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 12045,
"status": "needs_decision",
"branch": "claude/issue-12045-page-mount",
"pr": "#12432",
"session": "session_01JZnppgHRp4sREcouhkQYDB",
"premise_still_valid": true,
"summary": "Measured A3-A5 first, then built A4 and A6. The inbox mount was not built: A5's gap table shows losses, so it is the open question below. A3: on a real RecordDetailView, the served sys_approval_request_detail draws end to end: the decision panel (tally, Approve, Reject, Reply), the highlights strip without record_id, and the Details and Timeline tabs, with the timeline read from the actions route. Three gaps on an unmutated run under the build round's route: (1) the Timeline tab's count probe sent $count and was refused; (2) RelatedList's label read of request_id was refused by the scoped list ({$filter:{id:{$in:[id]}}}), which falsifies the carried pin's premise that this read is not refused on an unmutated run; (3) the Details tab renders empty: a bare record:details draws nothing in objectui, so the A2 record card and every field outside the strip are drawn nowhere. (3) is off this surface and reported below. A4: every surface that opens a request reads useAdapter() at or below DefaultAppContent. So the console's AppContent now wraps DefaultAppContent in ApprovalRecordReads, which re-provides AdapterCtx (an existing @object-ui/react export) as routeApprovalRecordReads(adapter): a request's findOne goes to the approvals get, one request's timeline to the actions route, and lists of requests and everything else stay on the data API. The routed adapter is cached in a WeakMap keyed on the adapter. That one layer covers the record route, ObjectView drawers and split overlays (reachable only when a view authors navigation; with none, the field count derives page), ObjectDataPage and InterfaceListPage. The no-app branch has no record route at all. The console's own request route (approvalRequestRoutes) retires. A timeline read honours $count. A6: the label read now reaches the data API and is pinned not refused; the export-comment nit is gone with the retired export; the changeset console-bullet nit is fixed append-only. The record:approval_decision registration was confirmed landed (PR #12362) and not redone. No @object-ui export, prop or option changed (Clause-2 no holds); console patch only.",
"tests": "Union at head b688d16: pnpm exec vitest run apps/console/src/services/ apps/console/src/pages/system/ApprovalsInboxPage apps/console/src/tests/AppContent → 'Test Files 23 passed (23)', 'Tests 180 passed (180)', lock VERDICT command-exit 0. New pins: approvalRequestsDataSource.servedPage.test.tsx (the served page whole, through the generic route under ApprovalRecordReads: the panel tally and decisions from the approvals get; the Timeline count '2' and rows from the actions route; the request_id label read reaching the adapter with no 'Failed to resolve lookups' warning; provider identity across renders and remounts; routing; a no-adapter control), approvalRequestsDataSource.recordReads.test.ts, AppContent.approvalRecordReads.test.tsx (the real AppContent hands a DefaultAppContent probe the routed adapter), a $count case in the timeline test, and the decisionPage test re-pointed to the layer with a no-provider control. Ablations: predictions were written first; each leg ran through objectstack scripts/ablation-replace.mjs (anchor 1 → 0, blob changed, restore blob == HEAD, git diff HEAD empty). Console tests import src, so there is no dist leg. AB1 ($count dropped): 2 red / 17 green, as predicted. AB2 (findOne to host): 14 red / 12 green; predicted red, but wider than listed, because every page-level test needs the record read; controls green. AB2's first attempt was refused by the tool (the replacement was a substring of the anchor): no mutation, no measurement, rerun with a distinct replacement. AB3 (the layer refuses request lists, the build-round behaviour): 4 red / 9 green, as predicted. AB4 (WeakMap set dropped): 1 red / 8 green, as predicted. AB5 (AppContent without the wrapper): 1 red, control green, as predicted. A3's readings came from a scratch probe test inside the worktree, never committed and deleted.",
"gates": "Local subset (exit codes captured to files before any pipe). Console dependency closure built: turbo run build --filter=@object-ui/console^... --concurrency=2, VERDICT command-exit 0. pnpm --filter @object-ui/console type-check: exit 0 at b688d16 (script name echoed). eslint on the 7 touched TS files: exit 0, 0 errors, 1 warning (the pre-existing systemRoutes react-refresh warning; base had 2, one of them the retired approvalRequestRoutes). Exit 0: check:control-bytes, check:new-line-citations ('0 new citation(s)'), check-changeset-no-major, check-changeset-presence, check:pending-changeset-literals, check:i18n-keys, check:test-path-roots, check:vi-mock-specifiers / vi-mock-inherit / vi-mock-override-shape. check-changeset-overwrite: exit 0, report-only, names the appended 12045-approval-decision-panel.md. check:changeset-claims: exit 0, report-only, names 12032-approval-requests-data-source.md (see findings). check:eager-closure: exit 0, 'Console eager closure is 3075.7 KB gzipped across 69 of 2252 chunks (budget: 3119.4 KB, headroom: 43.7 KB)'. check:sdui-registration-pins: exit 0, 'All 16 registration(s) a sideEffects array promises are present in the built console'. Both bundle gates were read on the vite build of bce9baf; b688d16 changes one test file only. check-governed-queue-guard --test on the diff's paths: NOT GOVERNED. Not applicable: check:side-effects-array and the RecordDetailView suites (app-shell is unchanged). No --ran reconciliation: dispatch-gates refuses a sister repo, so the list was derived by hand from objectui package.json and the workflows. CI on PR 12432 head b688d16: 36 check-runs, 5 success, 3 skipped, 28 in_progress at report time (not awaited, per contract).",
"mcp_calls": "0",
"api_writes": "2 relay strokes plus git push. (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot] as POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos//issues/12432/assignees (sunhaolin); read back 15035 of 15035 bytes identical, assignees sunhaolin. (2) This os-dev-report comment through scripts/pm/post-stamped.mjs: POST /repos//issues/12045/comments. Reads were REST GETs (issue, comments, one comment, the PR, check-runs). No label write.",
"open_questions": [
{
"question": "B1's inbox mount: A5's gap table shows the served page loses faces the inbox sheet's tests pin. Lost: the Waiting-on approver chips with position labels and 会签 groups (approverHeadline-12110, characterizationPins); the declared-amount lead and the payload summary with declared labels, faces and the hidden trim (summaryFaces-11677, hiddenFieldTrim); the Flow-initiated origin; the raw-payload panel for studio.access (rawPayloadGate); the record link withheld or kept, the tombstone and the unresolvable affordance (recordLinkRefused-11878, tombstone, unresolvableReference: the served page draws no record reference at all, because its Details tab is empty); the vertical step stepper (stepProgressVertical). Rendered: the tally, the declared decisions, the activity (as the Timeline tab) and the reply (as approval_comment). What should a request open on from the inbox?",
"options": [
"A, link and keep the sheet. The sheet keeps every face and gains one 'Open as full page' link to the request's record route, which now reads through the routed layer. The copy may reuse console.objectView.expandToPage, present in the en pack; whether the i18n gates accept a cross-namespace reuse is NOT MEASURED, and a new key would be packages/i18n work, where objectui#12423 is in flight. Cost: one small console change plus a pin, about half a round. Business need: no pinned fix regresses, and the page is one click away. Long-term: keeps the hand-built sheet, the drift this epic cures, and it may only shrink (ADR-0060 rule 3). AI-proofing: neutral, the sheet stays React. Startup: smallest, adds no spec surface.",
"B, the page replaces the sheet now. Cost: medium. Business need: regresses the real reported fixes the table names (raw-payload gate, tombstones, summary faces, 6-step CJK stepper, position labels) and deletes their pins. Long-term: the right direction, a regressive step. AI-proofing: the placement becomes metadata. Startup: no new surface, but it takes faces away from users.",
"C, the page gains the lost faces upstream, then replaces the sheet. That needs: the empty Details tab answered (the renderer's declared fallback, or the page authoring sections); generic blocks or spec rows for a payload summary with declared faces and the hidden trim, approver chips with position labels and groups (which overlaps objectui#12077), a step stepper, a record reference with tombstone and unresolvable states, and a capability-gated raw panel. Each is a contract review across objectstack spec, plugin-approvals and objectui. Cost: several rounds and several cards. Business need: same faces, metadata-driven; no measured demand for these blocks beyond this page. Long-term: the north star of ruling 6079807016. AI-proofing: best, every face spec-validated. Startup: widens the spec, and new blocks need pull evidence."
],
"recommendation": "A now. B is not taken. C is carded face by face only when the maintainer wants the sheet retired. Business need: A regresses none of the pinned, reported fixes, and B regresses many. Long-term: A keeps C open, and the sheet may only shrink. AI-proofing: no option makes today's sheet worse, and C is the eventual cure. Startup focus: A adds no spec surface, while C's new blocks have no pull outside this page yet. Conflict to weigh: the parent's scope says B1 mounts the page where the drawer is today, and A only links to it."
}
],
"out_of_scope_findings": [
"class: b · reach: named producer: objectstack plugin-approvals sys_approval_request_detail (its Details tab is a bare record:details), measured on a real RecordDetailView: the record:details node renders as one empty space-y-6 div · evidence: in objectui, record:details with no sections and no fields renders an empty body. @objectstack/spec RecordDetailsProps' retired-layout text says omitting sections 'falls back to the object's highlightFields'; objectui's own renderer comment says the same; the page's comment expects 'the request's own fields by the object's field groups'. The renderer does neither, so on the served page the A2 record_id card and every request field outside the strip are drawn nowhere. If the answer is field groups, the State group carries payload_json and node_config_json, which must not reach a business approver as raw text (the inbox's raw-payload gate). Seam: spec:RecordDetailsProps.sections (omitted) → renderer:RecordDetailsRenderer (plugin-detail record-details) | producer: plugin-approvals sys-approval-request.page.ts · dedupe words: record:details empty body no sections; record details highlightFields fallback; served approval request page details tab empty",
"carrier: domain:ui seat 1 (this claim's seat) · .changeset/12032-approval-requests-data-source.md's 'Superseded in part' paragraph says the console mounts the routed source on the request record route; this PR retires that route, so the paragraph is partly stale when both release together (check:changeset-claims names the file). It is outside this claim's file surface, so it is not edited; an append-only line after the PR 10823 precedent would close it · noted, not filed",
"carrier: objectui#12077 · bearing: the served page draws pending_approvers nowhere (not in highlights, and the Details tab is empty), so the raw-id face does not arise on the page; the inbox sheet's chips remain the one approver-name face · noted, not filed"
],
"deviations": [
"One test file beyond the listed surface: apps/console/src/tests/AppContent.approvalRecordReads.test.tsx, the wiring pin for AppContent.tsx (ablation AB5 shows it is the only pin on that wiring).",
"The console's own request record route (approvalRequestRoutes) was retired rather than extended: the layer makes it redundant, and keeping it would keep the scoped list that refused the label read. This is within the claimed file, with no export of the package entry; AppContent's exports are not in the published plugin entry.",
"The decisionPage test's source moved from the scoped source to routeApprovalRecordReads, to match production; its in-repo fixture shape is kept (it is a valid spec page), and the served shape is pinned in a new file.",
"Commit trailers use objectui AGENTS' model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named trailer. The PR body ends with the session-URL footer the agent definition prescribes, not the harness's 'Generated with' form.",
"A3 was measured with a scratch probe test inside the worktree (the only place vitest resolves the console project), never committed and deleted; its copy is in the session scratchpad. The worktree is removed: node_modules first, then git worktree remove, exit 0."
],
"files_changed": [
".changeset/12045-approval-decision-panel.md",
".changeset/12045-request-record-surfaces.md",
"apps/console/src/AppContent.tsx",
"apps/console/src/tests/AppContent.approvalRecordReads.test.tsx",
"apps/console/src/services/approvalRequestsDataSource.ts",
"apps/console/src/services/approvalRequestsDataSource.decisionPage.test.tsx",
"apps/console/src/services/approvalRequestsDataSource.recordReads.test.ts",
"apps/console/src/services/approvalRequestsDataSource.servedPage.test.tsx",
"apps/console/src/services/approvalRequestsDataSource.timeline.test.ts"
],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Claim amendment 1:
domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T05:32Z. It amends the claim6184502298with one rider on PR #12432, authorised by the seat in review. The branch, the assignee andClause-②: nostay.The rider: one append-only line on
.changeset/12032-approval-requests-data-source.md, so the first 18.x release notes stay true.sys_approval_requestrecord route. PR fix(console): every surface reads an approval request through the approvals routes; the served page's timeline counts and labels (objectui#12045, B1 page-mount step, part) #12432 retires that route, and a request's single read and its timeline now go through the console-wideApprovalRecordReadslayer. The paragraph would be false in the same release.check:changeset-claimsalready names the file.6081923179append-only precedent.check-changeset-overwriteis report-only.Not part of this rider:
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 12045,
"status": "done",
"round": "fix round 1 (claim amendment 6185211283, rider on PR #12432)",
"branch": "claude/issue-12045-page-mount",
"pr": "#12432",
"session": "session_01JZnppgHRp4sREcouhkQYDB",
"premise_still_valid": true,
"head": "7680faeca0717beda7e368310670ef1edec50fa2 (fast-forward from b688d16, no force)",
"summary": "Appended one paragraph to .changeset/12032-approval-requests-data-source.md, after its 'Superseded in part (objectui#12045)' paragraph. It says that once PR #12432 lands, the console declares no record route of its own for sys_approval_request; on every surface that opens a request, the console-wide ApprovalRecordReads layer sends the request's single read to GET /approvals/requests/:id and one request's timeline to GET /approvals/requests/:id/actions; and lists of requests, and every other read, stay on the console's adapter, the data API. The frontmatter and every existing line are byte-identical: cmp over the old file's whole 1870 bytes exit 0. No other file.",
"round_diff_stat": ".changeset/12032-approval-requests-data-source.md | 2 ++ (1 file changed, 2 insertions(+), 0 deletions)",
"tests": "not applicable (changeset text only; no source or test changed this round)",
"gates": "At 7680fae, each exit code captured to a file before any read: node scripts/check-changeset-presence.mjs exit 0 ('7 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/12045-request-record-surfaces.md'); node scripts/check-changeset-no-major.mjs exit 0; node scripts/check-changeset-overwrite.mjs exit 0, report-only, naming both modified files (M .changeset/12032-approval-requests-data-source.md, M .changeset/12045-approval-decision-panel.md); pnpm check:changeset-claims exit 0 ('No pending changeset names a file this change touches.'); pnpm check:control-bytes exit 0 ('check-control-bytes: OK'). CI on the new head: not read at report time (not awaited, per contract).",
"mcp_calls": "0",
"api_writes": "1 relay stroke plus git push: this os-dev-report comment through scripts/pm/post-stamped.mjs (POST /repos/objectstack-ai/objectstack/dispatches, executed as POST /repos//issues/12045/comments). Reads: one REST GET of comment 6185211283. No PR body edit, no label write.",
"open_questions": [],
"out_of_scope_findings": [],
"deviations": [
"The worktree was recreated from the local branch ref after confirming it equals origin/claude/issue-12045-page-mount (both b688d16). pnpm install --frozen-lockfile ran there so the gate scripts could run. The worktree is removed at the end: node_modules first, then git worktree remove."
],
"files_changed": [
".changeset/12032-approval-requests-data-source.md"
],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
待裁:审批收件箱点开一条请求时,打开服务端页面还是保留现有侧栏 ·
domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T05:34Z⛔ 本评论是决策分析,不是认领变更。PR #12432(
Refs #12045,B1 挂载步骤中不涉争议的部分)落地后,本卡由pm:dispatched转needs-user-decision;在此之前分析先落卡,维护者可直接回字母。一句话问题: 审批人从收件箱点开一条审批请求,今天看到的是手写侧栏,上面有十几项用户报过、已修好的信息面(待谁审批及其职位、金额摘要、原始数据权限闸、已删除记录的墓碑、多步进度条……)。父单 B1 的范围写着「把服务端页面挂到今天侧栏的位置」,但服务端页面目前画不出这些信息面,直接替换会让这些修复全部倒退。
背景与实测(dev 报告
6185129577与 PR #12432 正文)sys_approval_request_detail在真实RecordDetailView上能完整画出:决策面板(计票、批准、驳回、回复)、去掉record_id的高亮条、Details 与 Timeline 两个标签页。PR fix(console): every surface reads an approval request through the approvals routes; the served page's timeline counts and labels (objectui#12045, B1 page-mount step, part) #12432 已让所有打开请求的入口(记录路由、ObjectView 抽屉/分屏、两个嵌入记录视图)统一走审批路由读数。studio.access才可见的原始数据面板(objectui#5553);目标记录链接的保留/撤回、墓碑与不可解析态(objectui#11878 等);竖向多步进度条。sections时退回highlightFields,渲染器未兑现),已立 objectui#12433。Governing text
6079807016(乙):「the page is metadata in plugin-approvals … objectui registers the renderers and mountsRecordDetailView」;「⛔ Not taken: 甲 (the placement stays in React, the drift this epic cures; a 甲-then-乙 transition would need named external-user evidence …)」。docs/adr/0060-product-neutral-renderer.md:规则 2「A page that cannot be composed is fixed generically … a generic block in objectui … or a contract door in@objectstack/spec」;规则 3「What already exists stays until its page migrates, and may only shrink.」前提(带 re-check)
git ls-tree --name-only origin/main apps/console/src/pages/system/ | grep ApprovalsInboxPage→ 12 个测试文件。packages/plugins/plugin-approvals/src/sys-approval-request.page.ts。选项 × 代价 × 工作量
业务含义直译: A = 新大楼盖好了但先不搬,门口挂个指路牌;B = 直接搬进还没装修完的新楼,很多设施没了;C = 按清单把新楼装修齐再搬,旧楼期间照常用。
四维分析(业务立场)
推荐:C,按 ADR-0060 规则 3 分期:侧栏保留不动(不加链接,规则 3 只许缩小),第一步是 objectui#12433(已欠的实现缺口,无论选哪项都要修),其后每个信息面一张卡、按拉动排序,齐了再替换侧栏。
回退:A:若维护者认为这些通用块当下不值得扩 spec,则 B1 的挂载从 epic 范围撤下,侧栏作为请求的长期工作面,加一个整页链接。
置信缺口: 每个新块的 spec 形状尚未设计,C 的「≥5 张卡」是按缺口表计数,不是按设计计数;审批人实际最常用侧栏上哪几项,本席没有使用数据。
自检:只看①选 C;②③④ 是否翻转:否(② 与 ④ 只影响分期顺序与节奏,不改终态)。
裁后执行
Blocked-by:各信息面卡),信息面卡按缺口表逐张立在落地仓(spec 行在 objectstack spec 车道,渲染在 objectui),objectui#12433 先派。console.objectView.expandToPage),B1 挂载从父单 架构:把审批 UI 重建在标准 SDUI 渲染器上(ListView + RecordDetailView + ApiDataSource + 多态记录预览组件) #2763 范围撤下并在父单记录。os-decision-facets
Prior rulings read:
isDefault/inbox/drawer/B1 on approvals B1 (child of objectui#2763): thesys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar #12045, 架构:把审批 UI 重建在标准 SDUI 渲染器上(ListView + RecordDetailView + ApiDataSource + 多态记录预览组件) #2763 → 2 hits (5166015115,6079807016); ADR-0060 rules 2–3; thread: 6185211283Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed (B1 page-mount step, the part): PR #12432 →
mainbedf41c1c·pm:dispatched→needs-user-decision·domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T06:02Zbedf41c1c, 2026-10-15T06:01Z. One parent,92f979deb, an ancestor oforigin/main.bedf41c1cto the accepted head7680faeca0.Refs #12045and no closing keyword. No issue closed with the merge, and this card stays open.ApprovalRecordReads): the record route,ObjectViewdrawers and split overlays, and the embedded record views ofObjectDataPageandInterfaceListPage.sys_approval_request_detailpage draws its decision panel, highlights, and Timeline count and labels with no refused read.6185211283and6081923179.6184502298, amendment6185211283, reports6185129577and6185237006, ACCEPT6185257289.Release:
session_01JZnppgHRp4sREcouhkQYDB(accountsunhaolin) releases the claim6184502298.needs-user-decisionin this act, with the assignee cleared. The decision is6185241431above: options A, B and C, with the seat recommending C and A as the fallback.pm:blockedon this card.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
needs-user-decision→pm:blocked· the decision moves to its own card ·domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T06:18ZBlocked-by: #12438
sys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar #12045 收件箱点开审批请求时打开什么,开决策卡」. It was given in this seat's session chat.6185241431as the place to rule.pm:blockeduntil objectui#12438 carries aRuled:line. It has no assignee; the seat that picks it up then executes that ruling's "裁后执行" section.pm:blockedon this card.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Blocked-by:re-pointed to the closing card objectui#12484 ·domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T11:51ZBlocked-by: #12484
6190549995on objectui#12438 (letter C; the maintainer's 「C (推荐)」; director seat summon [WIP] Improve drag functionality in empty key list for designer #42), item 4: "objectui#12045's mount step is re-pointed to a closing card that isBlocked-by:every face card; objectui#12045 stayspm:blockeduntil then."Blocked-by: #12438now readsBlocked-by: #12484. The state stayspm:blocked.pm:queue) and objectui#12483 (amount and payload summary,Blocked-by: #12482).Thread-read: 6185616257
Generated by Claude Code