Repository navigation
feat(app-shell): approval request B1 build round: referenceVia record card, module-internal decision panel, routed request page (objectui#12045) - #12062
Conversation
… card, routed request page (objectui#12045) B1 build round of the approvals epic: the parts that do not wait on v18. - record:approval_decision: one panel that composes DecisionProgressIndicator (A3) with sys_approval_request's own declared actions through DeclaredActionsBar, over the record context, with no authorable props. - @object-ui/fields owns the referenceVia pointer-pair rule (resolveRecordPointer, RecordPointer, RECORD_POINTER_CARD_TYPE) and a record-id-as-text default; the details grid resolves the pair from its row, and app-shell registers RecordPreviewCard (A2) under the key. - The console mounts every sys_approval_request record page over the routed approvals source, and the source reads one request's sys_approval_action timeline from GET /approvals/requests/:id/actions. - RecordDetailView skips the approvals-on-this-record read on a request's own page. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…B1 build round (objectui#12045) Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
… until its spec row (objectui#12045) Claim amendment 3 (route B): no registration of record:approval_decision in source, no barrel side-effect import, no known-schema-types line and no zod arm this round. The registered-types ratchet pin stays at 360. The console's slotted-page test registers the shipped renderer itself under the proposed name; the registration, index import, known-types line and arm land together in the page-mount step, derived from the v18 spec row. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read ✅ Nothing to re-readNo pending changeset names a file this change touches, no address in this pull request's own prose points at a line this change moves, and no changeset here negates a package it declares. An earlier revision did. That request to re-read does not apply to the current diff. This comment is updated in place on every re-run rather than posted again, so the thread does not grow one per push. Compared the checked-out tree with |
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…erseded by objectui#12045 (seat-authorized rider) Append-only: the frontmatter and every existing line are unchanged. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs read: card objectui#12045 (body and all nine comments: claim ① Derived judgments
Nits (not verdict-bearing): (i) the export comment above ② Semver level
Clause-②: yes — on the PR body, and it matches the claim ( ③ Boundary flagsDev flags from the PR's Acceptance notes and the done report's
Implemented-by: VERDICT: PASS |
Refs #12045 (B1 build round)
Clause-②: yes
The v18-independent build round of objectui#12045 (B1 of the approvals epic objectui#2763), under ruling
6079807016(letter 乙) and claim amendments6079939323,6080124489(amendment 2) and6081385696(amendment 3). This PR does not close the card. The page-mount step stays open on the card's threeBlocked-by:lines.What changed
referenceViapointer pairs draw A2's record card in the details grid (amendment 2: the details face only).@object-ui/fieldsowns the rule.resolveRecordPointer(field, row)returns{ objectName, recordId }(the newRecordPointertype) for atextfield that declares a non-emptyreferenceVia, read from the row; otherwise it returnsnull.RECORD_POINTER_CARD_TYPEis the registry key a pair is drawn with. The package registers a default under it that draws the record id as text.@object-ui/plugin-detail'sDetailSectionis the call site. It holds the row, readsreferenceViaoff the object field and draws the pair through the key.@object-ui/app-shellregistersRecordPreviewCard(A2) under the key with the existingregisterFieldRenderer, from a side-effect module imported bysrc/index.ts.fieldsdoes not import app-shell.CellRendererProps,packages/types, and the authorable keys.packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.tsxdraws A3'sDecisionProgressIndicatorfrom the request's serveddecision_progress, aboveDeclaredActionsBar. The bar showssys_approval_request's own declared actions atrecord_section, and decisions go throughActionParamDialog.record:approval_decisionregistration, its side-effect import fromsrc/index.ts, itsknown-schema-types.tsline and its@object-ui/types/zodarm. They land together there, 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.packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.tsmeasured 361 refused namespaced keys against its pin of 360 once the type was registered. The gate forbids raising the pin. The pin stays at 360.packages/app-shell/src/console/AppContent.tsx.DefaultAppContenthandsuseAdapter()to the generic:objectName/record/:recordIdroute, and ObjectView forwards the same adapter to its drawer and split overlays.sys_approval_request/record/:recordIdinapps/console/src/AppContent.tsx. It is passed only asextraRoutes. The route mounts the sameRecordDetailViewovercreateApprovalRequestsDataSource({ host: adapter, scope: { kind: 'all' } }).apps/console/src/services/approvalRequestsDataSource.ts, asys_approval_actionread whose$filteris exactly{ request_id: ID }readsGET /approvals/requests/ID/actions. The source applies the read's$orderby(the object-array form, by stored value),$topand$skipitself, and reports the whole timeline astotal. It drops$selectand$expand, and refuses any other parameter withUNSUPPORTED_QUERY_PARAM. Any othersys_approval_actionread goes to the host unchanged.RecordDetailViewpasses no object name touseRecordApprovals, so the read of approvals opened on the request itself is not sent.Docs:
packages/fields/README.mdandcontent/docs/guide/fields.mddescribe the pointer pair. Changeset:.changeset/12045-approval-decision-panel.md, with@object-ui/fieldsminor and app-shell, plugin-detail and console patch.Measurements
RecordPreviewCardhas only the card). Under amendment 2 the list face is not in this round.apps/console/dist/eager-closure.json:e97a0e2dmeasured 3,239,763 B gzipped, and this head measured 3,243,943 B, so the closure grew by 4,180 B.src/index.ts(a measured mutation, restored) gave 3,242,613 B, so the card costs 1,330 B.pnpm check:eager-closureprinted "Console eager closure is 3167.9 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 36.6 KB)".Tests (all at head
358ef618aunless noted)packages/fields/src/__tests__/resolveRecordPointer-12045.test.tsx;packages/plugin-detail/src/__tests__/DetailSection.recordPointer-12045.test.tsx, which pinssys_approval_request.record_idand the non-approval pairsys_activity.source_id;packages/app-shell/src/views/record-preview/record-pointer-card-renderer.test.tsx, the realDetailSectionwith A2's card for both pairs;packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.test.tsx;apps/console/src/services/approvalRequestsDataSource.timeline.test.ts, the routedsys_approval_actionpins;apps/console/src/services/approvalRequestsDataSource.decisionPage.test.tsx.RecordDetailViewover A1 with an in-repo slotted-page fixture:actionsholdsrecord:approval_decision(registered by the test itself, per amendment 3),highlightsis empty, adetailsslot is authored, anddiscussionis empty. It asserts:viewer;ActionParamDialogwith a comment and a multi-file input, POSTs/api/v1/approvals/requests/ID/approve, and re-reads the request;record_iddraws A2's card for the target invoice;/approvals/requests?object=sys_approval_requestread is sent;cellRenderers.objectLiteral-8596(fields) andsummaryChip.badgeFitCensus-8464(plugin-detail; its fit-side count moved from 35 to 36).pnpm exec vitest run packages/fields/ packages/plugin-detail/atf1499e95e: Test Files 491 passed, 2 skipped; Tests 6393 passed, 15 skipped; exit 0. Route B touched neither package after that.358ef618a, the 45RecordDetailView*suites, the panel, record-preview and approval-progress suites,DeclaredActionsBar.test.tsx,apps/console/src/services/, the twoAppContentroute suites,packages/cli/, the four script tests for known-schema-types and sideEffects, and the four new or edited fields and plugin-detail files: Test Files 90 passed (90), Tests 957 passed (957), exit 0.358ef618a, exit 0 with 0error TSeach:pnpm --filter @object-ui/app-shell type-check,pnpm --filter @object-ui/console type-check,pnpm --filter @object-ui/fields type-checkandpnpm --filter @object-ui/plugin-detail type-check.pnpm exec eslinton the 16 touched TS files: 0 errors and 268 warnings. Two warnings are new, bothreact-refresh/only-export-components, the class of their neighbours: one forresolveRecordPointerinpackages/fields/src/index.tsx, and one forapprovalRequestRoutesbesidesystemRoutesinapps/console/src/AppContent.tsx.358ef618a:check:control-bytes,check:test-path-roots,check:changeset-claims(report-only; see Acceptance notes),check:pending-changeset-literals,check:side-effects-array,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:new-line-citations,check:registry-bare-names,check:doc-types,check:prompt-keys,check:unreferenced-sources,check:i18n-keys,check:readme-exports,check:doc-snippets,check:doc-examples,check:doc-fences,check:handler-key-reads,check:self-import,check:phantom-deps,check:eager-closure,check:sdui-registration-pins,node scripts/regenerate-known-schema-types.mjs --check,node scripts/check-changeset-presence.mjsandnode scripts/check-changeset-no-major.mjs. Locale packs are untouched, so the i18n pack gates do not apply.Reverse checks. Each was run through
scripts/ablation-replace.mjs, with the prediction written first. The tool proved the anchor landed on disk, and that the restore left the blob equal to HEAD andgit diff HEADempty.object_name. Predicted and observed: 2 red (the non-approval pair in fields and in plugin-detail); the approval pair stayed green.DetailSectionbranch disabled. 2 red (both probe pairs); the control and the default stayed green.Acceptance notes
record:approval_decisionregistration, its barrel import, its known-types line and its zod arm.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's page:sys_approval_requestdeclaresrecord_idin itshighlightFields. The details grid hides every field a mountedrecord:highlightsshows, and the synthesized Details tab is also handed the object'shighlightFieldsashideFields. So the card draws on the served page only if that page keepsrecord_idout of the highlights strip and authors the Details body: this PR's fixture useshighlights: []plus adetailsslot. Measured: with the synthesized strip,record_idsat in the highlights strip and no card was drawn.RECORD_SURFACE_PAGE_THRESHOLDauthorable fields and declares no list-viewnavigation, so its record opens as a page. A drawer or split overlay inside ObjectView (only if a view authors one), and the record views embedded byObjectDataPageandInterfaceListPage, still read through the adapter. The carrier is the page-mount step..changeset/12032-approval-requests-data-source.mdsays "Nothing mounts the source yet". This PR mounts the source on the request's record route, so if both publish in one release the CHANGELOG states both. This PR leaves that changeset alone, because it is objectui#12032's declaration (check-changeset-overwrite). The seat decides whether to edit it.$expandis dropped on the timeline read. The route servesactor_namein place of an expansion, and the related list resolves lookup labels through its own fallback.Generated by Claude Code