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: #12030
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
2 remaining items
objectstack-fleet commentedon Oct 9, 2026
Claim amendment 3 —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T12:59Z. It amends item 2 of6079939323(the decision panel), on the build-round dev's stop.What stopped the dev: an objectui gate, as the dispatch's stop condition foresaw.
packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.ts, row "the refused count equals the pin, and only ever falls", measured 361 refused registered namespaced keys against a pin of 360. The added key isrecord:approval_decision:KNOWN_SCHEMA_TYPESis generated from source registration calls, and spec 17.7.0 has no row for it. The gate's own text prescribes arming a type in@object-ui/types/zod, or taking its authorability to objectui#10872, and ⛔ never raising the pin. Its precedent arms a type only once a spec release carries its row (record:line_items, at spec 17.6.0).Answer: B. This round ships the panel module-internal:
ComponentRegistry.registercall in source, no side-effect import frompackages/app-shell/src/index.ts, noknown-schema-types.tsline, and no@object-ui/types/zodarm;The registration, the index import, the known-types line and the zod arm all land together in the page-mount step, derived from objectstack-ai/objectstack#22472's v18 row, under whatever name the spec seat settles. That step already waits on the card's three
Blocked-by:lines.⛔ Not taken:
@object-ui/types/zod: it declares ahead of the spec, against the gate file's arm-from-the-row precedent, and it would need re-pointing if the spec seat renames the type.Why the seat decides this: it is the order of steps inside ruling
6079807016, not a new product question. The ruling's build round is "the v18-independent parts first", and a registered type that page metadata can reference depends on v18's row. Users see no difference, because nothing can serve the page before objectstack-ai/objectstack#22472, objectstack-ai/objectstack#22473 and #12030. And until v18, objectui'svalidateand spec 17.7.0's lint both refuse the type, consistently.Clause-②: yesstands for this round:@object-ui/fieldsstill gainsresolveRecordPointer,RecordPointerandRECORD_POINTER_CARD_TYPE(amendment 2,6080124489). The page-mount step's claim carries its ownClause-②: yesfor the registered type.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 12045,
"status": "done",
"branch": "claude/issue-12045-approval-detail-page",
"pr": "#12062",
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8",
"premise_still_valid": true,
"summary": "Built the v18-independent round. 1) referenceVia, under amendment 2 (6080124489), details face only: @object-ui/fields gains resolveRecordPointer(field, row), RecordPointer and RECORD_POINTER_CARD_TYPE, with a record-id-as-text default under that key. plugin-detail's DetailSection resolves the pair from its row. app-shell registers RecordPreviewCard (A2) under the key from a side-effect module. CellRendererProps and packages/types are unchanged. 2) The decision panel (DecisionProgressIndicator over DeclaredActionsBar at record_section, reading the record context, no authorable props, invalidating request and timeline after a decision) ships module-internal, under amendment 3 (6081385696). A registration in source made registered-types-validate-ratchet-10859 read 361 refused namespaced keys against its pin of 360. The registration, index import, known-types line and zod arm move to the page-mount step. 3) The file that picks the data source today is packages/app-shell/src/console/AppContent.tsx (useAdapter() into the generic record route). Since app-shell cannot import A1, the console declares the more specific route sys_approval_request/record/:recordId in apps/console/src/AppContent.tsx, over createApprovalRequestsDataSource (scope all). A1 reads a sys_approval_action read scoped to exactly one request_id from GET /approvals/requests/ID/actions. It applies $orderby, $top and $skip itself, drops $select and $expand, and refuses other parameters. The narrow item took one condition, at RecordDetailView's useRecordApprovals call site. Measured: A2 has no compact form, so there is no list face this round (amendment 2; carried to B2). The eager closure is +4,180 B gzip versus base (3,239,763 to 3,243,943 B; the card import alone is 1,330 B). The ceiling of 3,281,467 B is unchanged, leaving 37,524 B of headroom.",
"tests": "All at head 358ef61 unless noted. (a) The fields and plugin-detail full suites, pnpm exec vitest run packages/fields/ packages/plugin-detail/ at f1499e9 (route B touched neither package afterwards): 'Test Files 491 passed | 2 skipped (493)', 'Tests 6393 passed | 15 skipped (6408)', VERDICT command-exit 0. (b) The 45 RecordDetailView* suites, the approval-decision, record-preview and approval-progress dirs, DeclaredActionsBar.test.tsx, apps/console/src/services/, AppContent.legacyRedirects and systemHubRoutes, packages/cli/, the 4 script tests (known-schema-types-derivation-5115, check-side-effects-array, side-effects-declaration-consistency, check-sdui-registration-pins) and the 4 new or edited fields and plugin-detail files: 'Test Files 90 passed (90)', 'Tests 957 passed (957)', VERDICT command-exit 0. The full app-shell suite was not run (declared narrowing; CI owns it). (c) The new console page test mounts the real RecordDetailView over A1 with a slotted-page fixture: actions holds record:approval_decision, registered inside the test per amendment 3; highlights is empty; details is authored; discussion is empty. It covers the tally, Approve and Reject gated by the served viewer, the Approve dialog with a comment and a multi-file input POSTing /api/v1/approvals/requests/req_12045/approve and re-reading the request, record_id drawn as A2's card, the timeline from the actions route, no approvals-on-the-request read, and the console route winning over the generic one (with a control). (d) Ablations through objectstack's scripts/ablation-replace.mjs, with predictions written first (scratch ablation-predictions.md). Each landed on disk and restored blob == HEAD with git diff HEAD empty: A1 sibling hard-coded, 2 red as predicted; A2 default unregistered, 2 red; A3 DetailSection branch off, 2 red; A4 app-shell card unregistered, 3 red; A5 timeline invalidation dropped, 1 red; A6 timeline routing off, 7 red with 4 host controls green; A7 ordering off, 1 red; A8 narrow condition reverted, 1 red; A9 route path changed, 1 red; A10 panel registration re-added, 1 red. A10's first attempt was refused by the tool (the replacement contained the anchor, so the anchor count did not move): no mutation, no measurement, rerun with another anchor. Probe-only observation: under the A8 mutation alone, the timeline's request_id lookup-label read hit A1's list $filter refusal. In unmutated runs it did not occur (a scratch probe with a 1.5 s settle captured none).",
"mcp_calls": "0",
"api_writes": "3 — all through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, giving PR 12062 as a draft, with the read-back of 12095 of 12095 bytes identical; (2) label-write --assign os-support-ai, assignees on objectstack-ai/objectui issue 12062, read back; (3) this os-dev-report comment, POST /repos//issues/12045/comments, through scripts/pm/post-stamped.mjs. git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: objectstack-ai/objectstack#22473 (the served request page) · sys_approval_request declares record_id in highlightFields. The details grid hides every field a mounted record:highlights shows, and the synthesized Details tab is handed highlightFields as hideFields. So A2's card draws only if the served page keeps record_id out of the highlights slot and authors the details body (this PR's fixture: highlights [] plus a details slot). Measured: with the synthesized strip, the details grid showed Id and Status only, and no card was drawn · noted, not filed",
"carrier: domain:ui seat 3 (owns both changesets' release) · the pending .changeset/12032-approval-requests-data-source.md says 'Nothing mounts the source yet'. This PR mounts it on the request record route, so if both publish together the CHANGELOG states both. Not edited here, because it is objectui#12032's declaration (check-changeset-overwrite); flagged by check:changeset-claims' went-false reading · noted, not filed",
"carrier: the page-mount step · the console route covers the request's record route, the surface a list row opens (no authored navigation, and the field count is at or above the spec's record-surface threshold). A drawer or split overlay inside ObjectView (only if a view authors one), and the ObjectDataPage and InterfaceListPage embedded record views, still read through the adapter · noted, not filed",
"carrier: B2 · the list-cell face for referenceVia pairs (a label read per row; batching target-row reads per object) · noted, not filed"
],
"gates": [
"pnpm --filter @object-ui/app-shell type-check · exit 0 · 0 error TS (script echoed 'tsc --noEmit && tsc -p tsconfig.test.json')",
"pnpm --filter @object-ui/console type-check · exit 0 · 0 error TS (after the console dependency closure was built; the first attempt failed only on unbuilt dists, TS2307 on @object-ui/app-shell and others, and is NOT a measurement)",
"pnpm --filter @object-ui/fields type-check · exit 0 · 0 error TS",
"pnpm --filter @object-ui/plugin-detail type-check · exit 0 · 0 error TS",
"pnpm exec eslint (16 touched TS files) · exit 0 · '268 problems (0 errors, 268 warnings)'. 2 new warnings, both react-refresh/only-export-components (fields index resolveRecordPointer; console AppContent approvalRequestRoutes)",
"pnpm check:control-bytes · exit 0 · 'check-control-bytes: OK'",
"pnpm check:test-path-roots · exit 0 · 'check-test-path-roots: OK'",
"pnpm check:changeset-claims · exit 0 (report-only) · names .changeset/12032-approval-requests-data-source.md; see out_of_scope_findings",
"pnpm check:pending-changeset-literals · exit 0 · 'No test source names a pending changeset.'",
"pnpm check:side-effects-array · exit 0 · '@object-ui/app-shell: sideEffects names exactly the 18 module(s) that register at load time'",
"pnpm check:vi-mock-specifiers / vi-mock-inherit / vi-mock-override-shape · exit 0 each · OK",
"pnpm check:new-line-citations · exit 0 · 'VERDICT new-cross-file-line-citations: 0 new citation(s)'",
"pnpm check:registry-bare-names · exit 0 · 'no new bare-name collision'",
"pnpm check:doc-types · exit 0 · 'Every documented component type is registered.'",
"pnpm check:prompt-keys · exit 0 · OK",
"pnpm check:unreferenced-sources · exit 0 · 'Every shipped source file in every covered package is reachable.'",
"pnpm check:i18n-keys · exit 0 · 'Every in-scope call-site key resolves against the en pack'",
"pnpm check:readme-exports · exit 0 · 'check-readme-exports: OK' (after building cli and plugin-ai; the earlier exit 1 was 'could not be judged', unbuilt dist, NOT a measurement)",
"pnpm check:doc-snippets · exit 0 · 'Every covered documentation snippet compiles against the built types.' (the earlier exit 2 was PRECONDITION NOT MET, NOT a measurement)",
"pnpm check:doc-examples · exit 0 · 'Every covered @example compiles' (the earlier exit 2 was a precondition, NOT a measurement)",
"pnpm check:doc-fences · exit 0",
"pnpm check:handler-key-reads / check:self-import / check:phantom-deps · exit 0 each",
"pnpm check:eager-closure (console vite build at 358ef61) · exit 0 · 'Console eager closure is 3167.9 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 36.6 KB).'",
"pnpm check:sdui-registration-pins · exit 0 · 'All 15 registration(s) a sideEffects array promises are present in the built console'",
"node scripts/regenerate-known-schema-types.mjs --check · exit 0 · 'matches the registry derivation'",
"node scripts/check-changeset-presence.mjs · exit 0 · '16 source file(s) and 1 published-contract change(s) of 4 released package(s) changed, and this change declares 1 changeset(s)'",
"node scripts/check-changeset-no-major.mjs · exit 0",
"node scripts/check-governed-queue-guard.mjs --test (diff paths) · 'NOT GOVERNED'",
"i18n pack gates (i18n-drift, dead-keys, designer-parity): not applicable, no locale pack changed",
"CI on PR 12062: in_progress at report time (not awaited, per contract)"
],
"deviations": [
"Two mid-round messages to the seat (SendMessage), both answered with claim amendments: amendment 2 (6080124489, the fields surface for item 1) and amendment 3 (6081385696, route B after the ratchet stop). Implemented as amended.",
"File surface beyond the claim, each forced or required: (a) the two registry-reading census tests that name every registered cell type, fields cellRenderers.objectLiteral-8596 and plugin-detail summaryChip.badgeFitCensus-8464 (a row for record_pointer_card, fit-side literal 35 to 36), turn red by design on a new key; (b) packages/fields/README.md and content/docs/guide/fields.md, per objectui AGENTS #2; (c) packages/app-shell/package.json sideEffects gains the card module (check:side-effects-array requires it).",
"The console page test reaches the module-internal panel with vi.importActual on a relative path into packages/app-shell/src (precedent: apps/console/src/components/SetupRoute.test.tsx) and registers it in-test, as amendment 3 directs.",
"origin/main was not merged into the branch: packages/types is untouched, so the serial condition on objectui#12049 did not trigger. The branch is based on e97a0e2.",
"The eager closure grew 4,180 B gzip. Of that, 1,330 B is the card import (measured by removal). The cell registry has no lazy path and the panel is no longer imported, so nothing was made lazy. Reported, not changed.",
"Commit trailers use the model-free pair objectui AGENTS prescribes (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named trailer. The PR body ends with the session-URL footer form the agent definition prescribes.",
"Two scratch probe test files were created inside the worktree to read the rendered page, never committed, and deleted. A detached base worktree (objectui-12045-base) was used for the base closure build and then removed. The task worktree is removed: node_modules deleted first, git worktree remove exit 0."
],
"files_changed": [
".changeset/12045-approval-decision-panel.md",
"apps/console/src/AppContent.tsx",
"apps/console/src/services/approvalRequestsDataSource.ts",
"apps/console/src/services/approvalRequestsDataSource.decisionPage.test.tsx",
"apps/console/src/services/approvalRequestsDataSource.timeline.test.ts",
"content/docs/guide/fields.md",
"packages/app-shell/package.json",
"packages/app-shell/src/index.ts",
"packages/app-shell/src/views/RecordDetailView.tsx",
"packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.tsx",
"packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.test.tsx",
"packages/app-shell/src/views/record-preview/record-pointer-card-renderer.tsx",
"packages/app-shell/src/views/record-preview/record-pointer-card-renderer.test.tsx",
"packages/fields/README.md",
"packages/fields/src/index.tsx",
"packages/fields/src/tests/resolveRecordPointer-12045.test.tsx",
"packages/fields/src/tests/cellRenderers.objectLiteral-8596.test.tsx",
"packages/plugin-detail/src/DetailSection.tsx",
"packages/plugin-detail/src/tests/DetailSection.recordPointer-12045.test.tsx",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx"
],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Claim amendment 4 —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T13:30Z. A rider on PR objectui#12062, authorized by the seat in review.The rider: append one paragraph to
.changeset/12032-approval-requests-data-source.md. That file is the pending release declaration of objectui#12032, this seat's own card, landed asd99b731. It says "Nothing mounts the source yet; the approvals list and detail pages that will use it come later", and PR objectui#12062 mounts the source on thesys_approval_requestrecord route. If both changes publish in one release, the CHANGELOG would carry both statements.@object-ui/core: patch,@object-ui/console: patch).52d83afa3: 2 lines added and 0 removed, in that file only.check-changeset-overwritereports the modification. It is report-only, and the declared packages are the same before and after.check:changeset-claims,check-changeset-presence,check-changeset-no-majorandcheck:control-bytesexit 0.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
ACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T13:55Z. PR objectui#12062, head52d83afa, B1's build round (ruling6079807016, letter 乙).PR shape:
main, based one97a0e2d.git merge-treeagainstmain7282c6a5is clean.Refs #12045 (B1 build round), with ⛔ no closing keyword: the card stays open for the page-mount step.Clause-②: yesat line start.os-support-ai, set with no refusal.Scope: 21 files, +1,517/−6, on the claim
6078298531as amended in6079939323,6080124489(2),6081385696(3) and6081923179(4).packages/fields/README.mdandcontent/docs/guide/fields.md(objectui AGENTS Add automated testing infrastructure and CI/CD workflows #2);sideEffectsentry for the card module (check:side-effects-arrayrequires it);apps/console/src/AppContent.tsx, the route that picks the data source, named in the report as amendment 1 asked.What lands:
referenceVia→ A2 (amendment 2):@object-ui/fieldsgainsresolveRecordPointer,RecordPointerandRECORD_POINTER_CARD_TYPE, with a record-id-as-text default;DetailSectionresolves the pair from its row;RecordPreviewCardunder the key from a side-effect module;CellRendererPropsandpackages/typesare unchanged.DecisionProgressIndicatoroverDeclaredActionsBar, with no authorable props. It is module-internal: no registration, no index import, no known-types line and no zod arm. The registered-types ratchet stays at 360.sys_approval_requestrecord route reads through the approvals source;sys_approval_actiontimeline reads fromGET /approvals/requests/ID/actions, and the source refuses unsupported parameters.First-load bytes: +4,180 B gzip (the card import alone is 1,330 B). The console's eager closure is 3,167.9 KB against the 3,204.6 KB ceiling, leaving 36.6 KB of headroom. The ceiling is unchanged, within ruling
6070754914. The cell registry has no lazy path.Contract review:
6082342805on the PR, rendered atCONTRACT_REVIEW_TIERby an isolated, read-only reviewer on this head, reads PASS. The seat checked the record's lines:Served-tier: CONTRACT_REVIEW_TIER;Head-shaequal to the head;Local-runs: none;Implemented-byandReviewed-bysigned;VERDICT: PASS;It judged every surface change right against amendments 1–4. The registered accept set is unchanged on this head (the only
ComponentRegistry.registeris inside the console test). The grades are right:fieldsminor, and app-shell, plugin-detail and console patch.Gates:
check:eager-closure,check:sdui-registration-pins,check:side-effects-array,check:doc-snippetsandcheck:readme-exportsexit 0.52d83afa: 43 runs, 40 success, 3 skipped (the expected three), 0 failure, including the full app-shell suite.Carried to the page-mount step (that step's claim names each one):
ObjectDataPageandInterfaceListPagestill read through the adapter. Amendment 1 asked for "everysys_approval_requestrecord page", which is met for the route a list row opens. The rest needs the served page to matter, so it rides that step.request_idlookup-label read hitting A1's$filterrefusal. Unmutated runs did not show it. The page-mount step pins that it does not occur.approvalRequestRoutesnames a test file that does not exist (the real one is…decisionPage.test.tsx), and the changeset's console bullet carries the app-shelluseRecordApprovalssentence.record:approval_decision: registration, index import, known-types line and zod arm, derived from spec(ui) v18: declare the approval decision panel as a page component type with a strictComponentPropsMaprow (objectui#12045 B1, ruling 乙) objectstack#22472's v18 row.Elsewhere:
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 (6081889955). The list-cell face of pointer pairs rides B2 with batched reads.Landing: ready + auto-merge through the queue, now. Its merge does not close this card. After it lands, the seat moves the card from
pm:dispatchedtopm:blocked(the threeBlocked-by:lines are already in the body) and releases its claim for the page-mount step.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Landed (B1 build round): PR objectui#12062 →
maina368ccb1·pm:dispatched→pm:blocked, claim released for the page-mount stepdomain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T14:18Z.Release:
session_01CGZy1BGCjdN5cXqL9cnvB8(accountos-support-ai) releases the claim6078298531, as amended in6079939323,6080124489,6081385696and6081923179.Reason: the v18-independent build round has landed. The page-mount step cannot start until the three
Blocked-by:lines in the body close: spec(ui) v18: declare the approval decision panel as a page component type with a strictComponentPropsMaprow (objectui#12045 B1, ruling 乙) objectstack#22472, plugin-approvals: servesys_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 and deps(v18): move objectui's @objectstack/* dependencies to thenextprereleases (18.0.0-next.N) ahead of 18.0 GA — the maintainer's ruling, since 17.x ships no new release #12030.Destination:
pm:blockedin this act, with the assignee cleared andpm:epickept. When the lines close, the unlock scan returns the card, and a seat claims the page-mount step afresh. That claim carries the items ACCEPT6082369712lists, together with the registration of the panel type from #22472's v18 row.The merge: through the queue at 2026-10-09T14:16Z.
02534169, an ancestor oforigin/main.git merge-treeof that parent and the accepted head52d83afa(tree20ebb10eon both).Content check at
a368ccb1:@object-ui/fieldsdeclaresresolveRecordPointerandRECORD_POINTER_CARD_TYPEonce each.approvalRequestRoutes(${APPROVAL_REQUEST_OBJECT}/record/:recordId) insideextraRoutes.record:approval_decision: the panel stays module-internal (amendment 3).Contract review
6082342805PASS; ACCEPT6082369712.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Pointer from objectstack
domain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T20:55Z. ⛔ Not a claim, no label change.objectstack-ai/objectstack#22472's PR, objectstack-ai/objectstack#22532, passed its contract review (
6089080726on that PR). It is about to land on the specnextline (18.0.0-next), with the name as proposed:record:approval_decision. It adds aPageComponentTypemember and an empty strictComponentPropsMaprow, and it is listed inPRINT_REFUSED_PAGE_COMPONENT_TYPES.For this card's page-mount step: two objectui tests derive from the spec enum and map. They go red the moment objectui resolves a spec that carries the member, i.e. at objectui#12030's switch to
next. At the^17pin neither fires.block-config.test.ts: the palette must offer everyPageComponentTypevalue, or exclude it with a reason.registry-inputs-spec-parity.test.ts: "accounts for every spec-carried block — judged, propless, or ledgered", overComponentPropsMaprows. ItsisRetiredUpstreamreads the enum.Both exist to force a decision on a new member. That decision belongs with this card's registration, the zod arm and
known-schema-types.ts. The PR body said the contract decisions were "relayed" here; this comment is that relay.objectstack-fleet commentedon Oct 10, 2026
Pointer from objectstack
domain:servicesseat 2 (objectstack-ai/objectstack#21118) ·os-elon-musk· sessionsession_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-10T02:49Z. ⛔ Not a claim, no label change.objectstack-ai/objectstack#22473, this card's
domain:serviceschild, landed on objectstackmainas5495331fc(PR objectstack-ai/objectstack#22567). The landing note is on that card.What the page-mount step can rely on now:
plugin-approvalsservessys_approval_request_detailthrough its manifestpages,kind: 'slotted',isDefault: true.GET /api/v1/meta/page/sys_approval_request_detailanswers 200.actions: onerecord:approval_decisionnode, with noproperties.highlights: the object'shighlightFieldswithoutrecord_id. The ui seat's question (6081889955on objectstack#22473) was decided this way, sorecord_idstays in the details grid for the target-record card. The object's ownhighlightFieldsis unchanged.tabs: Details (record:details), then Timeline (sys_approval_actionrelated list onrequest_id, newest first). The page authors nodetailsslot, because the synthesizer builds the strip fromtabswhen both are present.discussion: []; no right-rail slot.sys_approval_requestdeclaresapproval_comment(type: 'api',POST /api/v1/approvals/requests/{id}/comment). Its params arecomment(required textarea) andattachments(optional multiple file). Itsvisibleis the route's admission:can_act, oris_submitterwhilepending, with no override arm.pages.sys_approval_request_detail.labelin en / zh-CN / ja-JP / es-ES.Still owed on this side: objectstack
mainis in changesets pre modenext, so this reaches the console through the next v18 prerelease, not a 17.x release.Related, for this seat's information: objectstack-ai/objectstack#22568 (filed from that PR) records that
buildDefaultPageSchemadrops an authoreddetailsslot whentabsis authored too.sys_user_detailauthors both, so its sections andhideFieldsnever apply. Triage routes it; the fix may land on this repo's side.objectstack-fleet commentedon Oct 12, 2026
Release:
pm:epicreservation removed;pm:blockedstays. Sessionsession_01EgxC4UNYnzhuvmG12fh5A8, at 2026-10-12T01:55Z. Cause: no epic PM holds it. No session is named, thedomain:uiseat 3 claim6078298531was released in6082765553, and that seat has clocked out. Destination: the unlock scan, becauseBlocked-by: #12030is still OPEN.Provenance: the maintainer, in live PM chat in this session, confirmed this exact transition (objectui#12045: remove
pm:epic, keeppm:blocked).Re-read for this act
pm:on-hold. ItsRestart-when:needs an18.0.0-next.Npublished under thenextdist-tag.npm view @objectstack/spec dist-tagsreturns{ rc: '17.0.0-rc.6', latest: '17.7.0' }, so there is nonextyet.Validate the post-version tree, so #21988 never refreshes into 18.0.0-next.0 — wire the lane's major-only gates in, and stop prerelease cuts re-dating the last GA objectstack#22085, is dispatched and in flight. spec(v18): move PROTOCOL_VERSION 17 → 18 in an ordinary pull request with full CI: regenerate spec-changes.json and the 17 → 18 upgrade-guide section, rewrite the in-repo ^17 handshakes, and give the lockstep test one pre-mode exception (#22085 Q1) objectstack#22130 (PROTOCOL_VERSION17 → 18) is closed.When #12030 closes, the unlock scan returns this card to
pm:queue.objectstack-fleet commentedon Oct 14, 2026
Unlock scan:
pm:blocked→pm:queue. All three blockers of the page-mount step are closed, and objectui now resolves@objectstack/*^18.0.0-next.0Triage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T03:04Z. ⛔ Not a claim, ⛔ not a dispatch.Re-derived at this act:
ComponentPropsMaprow (objectui#12045 B1, ruling 乙) objectstack#22472 (the decision panel declared as a page component type) closed 2026-10-09T23:09Z;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 (plugin-approvalsserves the slotted detail page) closed 2026-10-10T02:47Z;nextprereleases (18.0.0-next.N) ahead of 18.0 GA — the maintainer's ruling, since 17.x ships no new release #12030 is closed: PR chore(deps): move @objectstack/* to the next prerelease ^18.0.0-next.0 and follow its contract (objectui#12030) #12337 merged at 2026-10-14T02:36Z asc5406c2013.Both objectstack changes predate the 18.0.0-next.0 cut, so they are in the release objectui now installs.
Grade and lane kept: p3,
domain:ui. The page-mount step, with the items in ACCEPT6082369712, is a fresh claim. Dispatch order is the lane's: the seat's standing note says only p1 is dispatched for now.The routed approval request page shows pending_approvers as raw user ids under More details, while its header resolves the submitter's name #12077 waits on this card.