Repository navigation
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
Conversation
…rovals routes; the served page's timeline counts and labels (objectui#12045) B1's page-mount step. The console's AppContent re-provides the adapter below it as `routeApprovalRecordReads(adapter)`: a request's findOne reads GET /approvals/requests/:id and one request's timeline reads the actions route, on the record route, ObjectView drawers and split overlays, and the record views embedded in ObjectDataPage and InterfaceListPage. Lists of requests stay on the data API, so the timeline's request_id label read is no longer refused. The console's own request record route retires. A timeline read honours $count, so the served page's Timeline tab shows its count. Claude-Session: https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB Co-authored-by: Claude <noreply@anthropic.com>
…; type the record-reads doubles (objectui#12045) The one wiring every request surface shares is DefaultAppContent's useAdapter(); a probe in its place, under the real AppContent, reads the routed adapter. The record-reads unit test's adapter double now types its findOne and aggregate parameters, which the console type-check reads. Claude-Session: https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB Co-authored-by: Claude <noreply@anthropic.com>
… reassigned outer variable (objectui#12045) react-hooks/globals refuses a render-time reassignment of an outer `let`; the provider pin now reads what the probe component recorded. Claude-Session: https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB 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
|
… request route retires (objectui#12045) Append-only, after the existing "Superseded in part" paragraph: the console declares no request record route of its own; ApprovalRecordReads sends a request's single read and its timeline to the approvals routes on every surface, and lists of requests stay on the data API. Frontmatter and every existing line are unchanged (claim amendment 6185211283). Claude-Session: https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB 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
|
Seat ACCEPT: objectui PR #12432 at
|
Refs #12045 (B1 page-mount step, part)
Clause-②: no
B1's page-mount step, the parts a measurement showed are in scope. Every console surface that opens an approval request now reads it through the approvals routes, from one layer, and the page objectstack serves for a request (
sys_approval_request_detail,kind: 'slotted') draws its Timeline tab's count and its timeline's labels with no refused read. The inbox mount is not built: the gap table below shows the served page loses faces the inbox sheet's tests pin, so it goes back to the card as a decision. #12045 remains open for it.What this lands (
@object-ui/consoleonly)One layer for every surface.
AppContentwrapsDefaultAppContentin a newApprovalRecordReadsprovider. Below it,useAdapter()answers the console's adapter with two reads routed (routeApprovalRecordReads, inservices/approvalRequestsDataSource.ts):findOneofsys_approval_requestreadsGET /approvals/requests/:id(theviewerblock and the decision tally);sys_approval_actiontimeline readsGET /approvals/requests/:id/actions.A list of requests, and every other read and member, stay on the data API. The routed adapter is cached on the adapter object in a
WeakMap, not in auseMemo(AGENTS.md commandment 10).The console's own request record route retires.
approvalRequestRoutesand itsApprovalRequestRecordRouteare gone. The generic record route reads through the layer, and so do the other surfaces (table below).A timeline read honours
$count. It asks fortotal, which every timeline read already returns. The tab strip's count probe sends{ $filter, $top: 1, $count: true }, and the routed source used to refuse it, so the served Timeline tab had no count.Changesets: a new
.changeset/12045-request-record-surfaces.md(@object-ui/console: patch)..changeset/12045-approval-decision-panel.mdgets one appended paragraph, following the PR feat(types,components)!: retire filter-builder nesting at the name authority; components derives FilterBuilderCondition / FilterGroup (objectui#9306) #10823 precedent, with the frontmatter and every existing line unchanged. It says the console bullet's last sentence is an app-shell change, and that the console's request route is replaced.No
@object-ui/*export, prop or option changes. No language-pack key is added.ObjectView.tsx,packages/i18n/**,packages/components/src/ui/**, app-shell and objectstack are untouched.Measured first
A3: the served page on a real
RecordDetailViewA scratch probe (never committed) mounted the real
RecordDetailViewwith objectstack'ssys-approval-request.page.tscopied whole, over the build round's route source (the routed source, scopeall). Onmain7c13d3052, the page draws end to end:page:header;record_id;There were three gaps:
$countprobe was refused withUNSUPPORTED_QUERY_PARAM. This PR fixes it.request_idlabel read was refused.RelatedListresolves labels for every reference field ofsys_approval_action, not only the displayed columns. So it asked the source forsys_approval_requestwith{ $filter: { id: { $in: [the request id] } }, $top: 1 }, the scoped list refused it, and the probe recorded the refusal'sUNSUPPORTED_QUERY_PARAMcode. This happened on an unmutated run, so the carried pin's premise ("does not hit A1's$filterrefusal on an unmutated run") does not hold for the served shape. Nothing visible broke, becauserequest_idis not a displayed column. With the layer, the read reaches the data API and is pinned as not refused.{ type: 'record:details' }. objectui'srecord:detailswith nosectionsand nofieldsrenders an empty body (the probe read the node's whole HTML as one emptyspace-y-6div). So on the served page, therecord_idpointer card (A2) and every request field outside the strip are drawn nowhere.@objectstack/spec'sRecordDetailsProps(its retired-layouttext) says omittingsections"falls back to the object'shighlightFields". The page's own comment expects "the request's own fields by the object's field groups". The renderer does neither. This is reported to the seat; see Acceptance notes.With the layer, the same probe read: the Timeline tab shows
2, the label read reaches the adapter, and zero reads are refused.A4: every request record surface, and the file that picks its data source
DefaultAppContent(useAdapter())create-app,systemormetadatasegment, and declares no:objectName/record/:recordIdrouteObjectViewdrawer / split overlaynavigation. With none,deriveRecordSurfaceanswerspagefor this object (its authorable-field count is aboveRECORD_SURFACE_PAGE_THRESHOLD; a reading taken once from the served object's fields)ObjectView'sdataSourceprop, fromDefaultAppContent'suseAdapter()ObjectView.tsxeditObjectDataPageembedded record view:objectName/data)dataSourceprop, fromDefaultAppContent'suseAdapter()InterfaceListPageembedded record viewuseAdapter()The one-layer route exists because all of these read
useAdapter()at or belowDefaultAppContent, and the console can re-provideAdapterCtx(an existing@object-ui/reactexport) around it. The provider and the wiring are pinned. The surface mounts beyond the record route are covered by that construction, not by a mount per surface.A5: the inbox sheet vs the served page (not mounted:
needs_decision)Every face the sheet's tests pin, and what the served page does with it today:
approverHeadline-12110)pending_approversis drawn nowhere: not in highlights, and the Details tab is emptyapproverHeadline-12110,characterizationPins)characterizationPins)flow:submitter (characterizationPins)submitter_idas asys_userlookupsummaryFaces-11677)hidden: truetrim of the summary and the lead (hiddenFieldTrim)studio.accessholder, withheld from business approvers (rawPayloadGate)recordLinkRefused-11878)tombstone)unresolvableReference)stepProgressVertical)Rendered, and pinned by no inbox test: the decision progress (the panel's
DecisionProgressIndicator), the declared decisions (the panel'sDeclaredActionsBar), the activity (the Timeline tab, with different columns), and the reply (theapproval_commentdeclared action, as ruled). Lost and unpinned: the Previous / Next pager, the SLA and round chips, and the "why can't I act" hint.So the mount is not built. The options, with the four-axis analysis, are on the card's report.
Carried items (ACCEPT
6082369712)request_idlabel-read pin: premise falsified for the served shape (A3, gap 2). Now pinned in the opposite, true direction: the read reaches the data API and is not refused (servedPagetest 4, ablation AB3).approvalRequestRoutesand the comment naming a test filemaindoes not have are both gone.record:approval_decision: already landed with objectui#12342 (PR feat(app-shell): register record:approval_decision, armed in @object-ui/types (objectui#12342) #12362,52d6194), confirmed by grep. Nothing redone.Tests
At head
b688d164db: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), lockVERDICT command-exit 0.New and changed pins:
approvalRequestsDataSource.servedPage.test.tsx(new). It uses the served page whole, mounted the way the console mounts it: the generic route overuseAdapter()underApprovalRecordReads. It pins:approvalRequestsDataSource.recordReads.test.ts(new): whatrouteApprovalRecordReadsroutes, and what it leaves to the host (Setup list reads with filter and sort, the label read, another object,aggregate). The result reads as the host: its class, its state, and one identity per method.AppContent.approvalRecordReads.test.tsx(new): the REALAppContenthandsDefaultAppContent(a probe) the routed adapter, with another object's read as the control.approvalRequestsDataSource.timeline.test.ts: a$countprobe is honoured.approvalRequestsDataSource.decisionPage.test.tsx: its source is nowrouteApprovalRecordReads, and the retired route's describe becomes the provider over the generic route, with a no-provider control.Ablations
Predictions were written before any run. Each leg went through objectstack's
scripts/ablation-replace.mjsand landed on disk (anchor count 1 to 0, blob changed). Each restore was proven (blob equal to HEAD,git diff HEADempty). Console tests importsrcdirectly, so there is nodist/leg.$countfrom the timeline's applied names$countpin and the served Timeline count), 17 green, as predictedrouteApprovalRecordReads'findOnegoes to the hostWeakMapsetAppContentwithout theApprovalRecordReadswrapperAB2's first attempt was refused by the tool: the replacement was a substring of the anchor, so its count did not rise. It was not a measurement, and it was rerun with a distinct replacement.
Gates (local subset; CI runs the full farm)
pnpm --filter @object-ui/console type-check: exit 0 atb688d164db(script echoed; afterturbo run build --filter=@object-ui/console^...).systemRoutesreact-refresh warning; base had 2 warnings, one of them the retiredapprovalRequestRoutes.pnpm check:control-bytespnpm check:new-line-citations(0 new citations)node scripts/check-changeset-no-major.mjsnode scripts/check-changeset-presence.mjspnpm check:pending-changeset-literalspnpm check:i18n-keyspnpm check:test-path-rootspnpm check:vi-mock-specifiers,check:vi-mock-inheritandcheck:vi-mock-override-shapenode scripts/check-changeset-overwrite.mjs: exit 0, report-only, naming the appended.changeset/12045-approval-decision-panel.md.pnpm check:changeset-claims: exit 0, report-only, naming.changeset/12032-approval-requests-data-source.md(see Acceptance notes).pnpm 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)".pnpm check:sdui-registration-pins: exit 0, "All 16 registration(s) asideEffectsarray promises are present in the built console".bce9baf5cf.b688d164dbchanges one test file only, which the bundle does not contain.node scripts/check-governed-queue-guard.mjs --test(the diff's paths): NOT GOVERNED.check:side-effects-array(app-shell'spackage.jsonis unchanged) and theRecordDetailView*suites (app-shell is unchanged).Acceptance notes
record:detailsdraws nothing in objectui, while the spec text says it falls back tohighlightFieldsand the page expects the object's field groups. Two places could fix it: the renderer (implement the declared fallback), or the page (authorsections). Choosing one is a contract question for the seat, not this PR.payload_jsonandnode_config_json. Those must not reach a business approver as raw text; the inbox's raw-payload gate exists for exactly that..changeset/12032-approval-requests-data-source.md's "Superseded in part" paragraph says the console mounts the routed source on the request record route. That route retires here, so the paragraph is partly stale when both changes release together. It is outside this claim's file surface, so it is reported, not edited.pending_approversnowhere, so its raw-id face does not arise on the page. The inbox sheet's chips stay the one approver-name face.Generated by Claude Code