Skip to content

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

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12045-page-mount
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12045-page-mount

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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/console only)

  • One layer for every surface. AppContent wraps DefaultAppContent in a new ApprovalRecordReads provider. Below it, useAdapter() answers the console's adapter with two reads routed (routeApprovalRecordReads, in services/approvalRequestsDataSource.ts):

    • a findOne of sys_approval_request reads GET /approvals/requests/:id (the viewer block and the decision tally);
    • one request's sys_approval_action timeline reads GET /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 a useMemo (AGENTS.md commandment 10).

  • The console's own request record route retires. approvalRequestRoutes and its ApprovalRequestRecordRoute are gone. The generic record route reads through the layer, and so do the other surfaces (table below).

  • A timeline read honours $count. It asks for total, 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.md gets 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 RecordDetailView

A scratch probe (never committed) mounted the real RecordDetailView with objectstack's sys-approval-request.page.ts copied whole, over the build round's route source (the routed source, scope all). On main 7c13d3052, the page draws end to end:

  • page:header;
  • the decision panel, with its tally and Approve, Reject and Reply;
  • the highlights strip, without record_id;
  • the tabs Details and Timeline;
  • the timeline rows, read from the actions route.

There were three gaps:

  1. The Timeline tab had no count. The tab strip's $count probe was refused with UNSUPPORTED_QUERY_PARAM. This PR fixes it.
  2. The timeline's request_id label read was refused. RelatedList resolves labels for every reference field of sys_approval_action, not only the displayed columns. So it asked the source for sys_approval_request with { $filter: { id: { $in: [the request id] } }, $top: 1 }, the scoped list refused it, and the probe recorded the refusal's UNSUPPORTED_QUERY_PARAM code. This happened on an unmutated run, so the carried pin's premise ("does not hit A1's $filter refusal on an unmutated run") does not hold for the served shape. Nothing visible broke, because request_id is not a displayed column. With the layer, the read reaches the data API and is pinned as not refused.
  3. The Details tab is empty (not fixed here, off this surface). The served Details tab is a bare { type: 'record:details' }. objectui's record:details with no sections and no fields renders an empty body (the probe read the node's whole HTML as one empty space-y-6 div). So on the served page, the record_id pointer card (A2) and every request field outside the strip are drawn nowhere. @objectstack/spec's RecordDetailsProps (its retired-layout text) says omitting sections "falls back to the object's highlightFields". 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

Surface Can a request open there? Picks its data source After this PR
App-scoped record route yes (a list row; the console's own route used to override it) DefaultAppContent (useAdapter()) through the layer; the console route retires
No-app branch no: that branch mounts only for a path with a create-app, system or metadata segment, and declares no :objectName/record/:recordId route n/a nothing to route
ObjectView drawer / split overlay only when a view authors navigation. With none, deriveRecordSurface answers page for this object (its authorable-field count is above RECORD_SURFACE_PAGE_THRESHOLD; a reading taken once from the served object's fields) ObjectView's dataSource prop, from DefaultAppContent's useAdapter() through the layer, no ObjectView.tsx edit
ObjectDataPage embedded record view yes (:objectName/data) its dataSource prop, from DefaultAppContent's useAdapter() through the layer
InterfaceListPage embedded record view yes, when an interface page lists the object its own useAdapter() through the layer

The one-layer route exists because all of these read useAdapter() at or below DefaultAppContent, and the console can re-provide AdapterCtx (an existing @object-ui/react export) 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:

Sheet face (pinning test file) Served page Verdict
"Waiting on" approver chips; a position approver reads its registry label (approverHeadline-12110) pending_approvers is drawn nowhere: not in highlights, and the Details tab is empty lost
Lead figure is the declared amount, once (approverHeadline-12110, characterizationPins) no payload summary lost
会签 group on approver chips (characterizationPins) no chips lost
"Flow-initiated" origin for a flow: submitter (characterizationPins) highlights draw submitter_id as a sys_user lookup lost (drawn as a lookup, not as the origin)
Payload summary: declared labels, option and date faces, no undeclared key, distinct keys (summaryFaces-11677) no payload summary lost
hidden: true trim of the summary and the lead (hiddenFieldTrim) nothing drawn to trim lost as a face (no leak)
Raw-payload panel for a studio.access holder, withheld from business approvers (rawPayloadGate) no raw panel for anyone lost for the operator (the withholding holds trivially)
Record link withheld on a refused target, in the sheet too (recordLinkRefused-11878) no record reference: the Details tab is empty, so the A2 card is not drawn lost
Dead-record tombstone in the sheet (tombstone) no record reference lost
Unresolvable-reference affordance in the sheet (unresolvableReference) no record reference lost
Vertical step stepper (stepProgressVertical) no step strip lost

Rendered, and pinned by no inbox test: the decision progress (the panel's DecisionProgressIndicator), the declared decisions (the panel's DeclaredActionsBar), the activity (the Timeline tab, with different columns), and the reply (the approval_comment declared 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)

  • Every request record surface: done through the layer (A4 table).
  • The request_id label-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 (servedPage test 4, ablation AB3).
  • The export-comment nit: resolved by retirement. approvalRequestRoutes and the comment naming a test file main does not have are both gone.
  • The changeset console-bullet nit: fixed append-only (above).
  • The registration of 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), lock VERDICT 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 over useAdapter() under ApprovalRecordReads. It pins:
    • the panel's tally and decisions from the approvals get;
    • the Timeline tab's count and rows from the actions route;
    • the label read reaching the adapter, with no lookup failure;
    • the provider's one identity across renders and remounts;
    • request routing;
    • a no-adapter control.
  • approvalRequestsDataSource.recordReads.test.ts (new): what routeApprovalRecordReads routes, 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 REAL AppContent hands DefaultAppContent (a probe) the routed adapter, with another object's read as the control.
  • approvalRequestsDataSource.timeline.test.ts: a $count probe is honoured.
  • approvalRequestsDataSource.decisionPage.test.tsx: its source is now routeApprovalRecordReads, 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.mjs and landed on disk (anchor count 1 to 0, blob changed). Each restore was proven (blob equal to HEAD, git diff HEAD empty). Console tests import src directly, so there is no dist/ leg.

Leg Mutation Result
AB1 drop $count from the timeline's applied names 2 red (the timeline $count pin and the served Timeline count), 17 green, as predicted
AB2 routeApprovalRecordReads' findOne goes to the host 14 red, 12 green. Predicted red; wider than the listed tests, because every page-level test needs the record read. Controls green
AB3 the layer refuses request lists (the build-round behaviour) 4 red, 9 green, as predicted
AB4 drop the WeakMap set 1 red (the identity across renders and remounts), 8 green, as predicted
AB5 AppContent without the ApprovalRecordReads wrapper 1 red (the wiring pin), the control green, as predicted

AB2'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 at b688d164db (script echoed; after turbo run build --filter=@object-ui/console^...).
  • ESLint on the 7 touched TS files: exit 0, 0 errors, 1 warning. The warning is the pre-existing systemRoutes react-refresh warning; base had 2 warnings, one of them the retired approvalRequestRoutes.
  • These exit 0:
    • pnpm check:control-bytes
    • pnpm check:new-line-citations (0 new citations)
    • node scripts/check-changeset-no-major.mjs
    • node scripts/check-changeset-presence.mjs
    • pnpm check:pending-changeset-literals
    • pnpm check:i18n-keys
    • pnpm check:test-path-roots
    • pnpm check:vi-mock-specifiers, check:vi-mock-inherit and check:vi-mock-override-shape
  • node 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) a sideEffects array promises are present in the built console".
  • Both bundle gates were read on the vite build of bce9baf5cf. b688d164db changes one test file only, which the bundle does not contain.
  • node scripts/check-governed-queue-guard.mjs --test (the diff's paths): NOT GOVERNED.
  • Not applicable: check:side-effects-array (app-shell's package.json is unchanged) and the RecordDetailView* suites (app-shell is unchanged).

Acceptance notes

  • The served Details tab renders empty (A3, gap 3). A bare record:details draws nothing in objectui, while the spec text says it falls back to highlightFields and the page expects the object's field groups. Two places could fix it: the renderer (implement the declared fallback), or the page (author sections). Choosing one is a contract question for the seat, not this PR.
    • Either way the A2 record card stays off the served page until one of them lands.
    • If the answer is "the object's field groups", the State group carries payload_json and node_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.
  • objectui#12077 (pending approvers as raw ids): the served page draws pending_approvers nowhere, so its raw-id face does not arise on the page. The inbox sheet's chips stay the one approver-name face.
  • The scratch probe that read the served page was a test file inside the worktree. It was never committed and is deleted.

Generated by Claude Code

…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>
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

✅ Nothing to re-read

No 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 63079f4a0 (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 467 pending declaration(s) that publish a body (485 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 69 chunks) 3075.7 KB 3119.4 KB
Main entry chunk (gzip) 73.6 KB 350 KB
Entry file index-CCUbGrZm.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 20.90KB 7.67KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 591.88KB 142.76KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 251.19KB 70.38KB
fields (index.js) 282.89KB 72.11KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.88KB 38.88KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 262.05KB 69.25KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 179.38KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 261.69KB 72.58KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.37KB 31.61KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.99KB 23.23KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

… 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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 69 chunks) 3075.7 KB 3119.4 KB
Main entry chunk (gzip) 73.6 KB 350 KB
Entry file index-Dtd42ZJX.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 20.90KB 7.67KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 591.88KB 142.76KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 251.19KB 70.38KB
fields (index.js) 282.89KB 72.11KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.88KB 38.88KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 262.05KB 69.25KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 179.38KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 261.69KB 72.58KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.37KB 31.61KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.99KB 23.23KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat ACCEPT: objectui PR #12432 at 7680faeca0 · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-15T05:35Z

Verdict: ACCEPT, as a part of the card. Reviewed on GitHub against the claim 6184502298 and its amendment 6185211283 on objectui#12045, and against the dev's reports 6185129577 and 6185237006.

No contract-review face applies: Clause-②: no, there is no published schema and no governed path. The review of record is this seat's own reading plus CI. The PR lands once CI on this head is green.

  • Shape:
    • Draft, base main, assignee sunhaolin.
    • Line 1 is Refs #12045 (B1 page-mount step, part), with ⛔ no closing keyword. The card stays open for the inbox decision.
    • Line 2 is Clause-②: no.
    • 9 files, +826/−115, all under apps/console and .changeset/.
  • The fix, read in the diff:
    • AppContent wraps DefaultAppContent in ApprovalRecordReads, which re-provides AdapterCtx (an existing @object-ui/react export) as routeApprovalRecordReads(adapter).
    • A request's findOne reads GET /approvals/requests/:id, and one request's timeline reads GET /approvals/requests/:id/actions. Lists of requests, and every other read and member, stay on the adapter.
    • The routed adapter is cached in a WeakMap keyed on the adapter, not a useMemo (AGENTS.md [WIP] Enhance every detail of the designer #10).
    • The console's own request record route is retired; the generic route reads through the layer.
  • The seat's own check on the layer: DefaultAppContent (packages/app-shell/src/console/AppContent.tsx) does not re-provide AdapterCtx. AdapterProvider sits above it, in ConsoleShell. So the outer provider reaches every surface below it: the record route, ObjectView drawers and split overlays, and the embedded record views of ObjectDataPage and InterfaceListPage. Metadata and permission reads above the layer keep the unrouted adapter, which is correct.
  • Carried items (ACCEPT 6082369712):
    • The timeline honours $count.
    • The request_id label read reaches the data API and is pinned as not refused. The carried pin's premise, that it was not refused on an unmutated run, was falsified: it was refused, and that refusal is what this PR fixes.
    • The export-comment nit is gone with the retired export.
    • The console-bullet nit is fixed append-only.
    • record:approval_decision's registration was confirmed already landed (PR feat(app-shell): register record:approval_decision, armed in @object-ui/types (objectui#12342) #12362) and not redone.
  • Tests:
    • 23 files and 180 tests passed across the console's services, ApprovalsInboxPage and AppContent suites.
    • The new served-page pin mounts the real page shape through the generic route under the layer.
    • Ablations: five legs, each red as predicted. AB2 came out wider than listed, and the report explains why. Every restore is blob-proven.
    • Console type-check: exit 0. check:eager-closure: 3,075.7 KB against the 3,119.4 KB ceiling.
  • Rider (amendment 1): .changeset/12032-approval-requests-data-source.md gains one appended paragraph, "Superseded again …". The old 1,870 bytes are byte-identical, so the release notes no longer say the console mounts that source on a request route.
  • Deviations, accepted:
    • One extra test file, the only pin on the AppContent wiring (AB5).
    • The retired route, which the layer makes redundant. Keeping it would have kept the scoped list that refused the label read.
    • The decisionPage test's source re-pointed to the layer, to match production.
  • Out of scope, one line each:
    • The served page's Details tab renders empty (a bare record:details), though the spec declares the highlightFields fallback. → filed objectui#12433.
    • The 12032 changeset paragraph. → rode this PR as amendment 1.
    • The bearing on objectui#12077: the served page draws pending_approvers nowhere, so the raw-id face does not arise on the page. → Acceptance notes, and it is carried in the decision below.
  • The open question (the inbox mount: A, B or C) goes to the maintainer as decision 6185241431 on objectui#12045. The seat recommends C, with A as the fallback.

Landing: ready and auto-merge once every check on this head is green or an expected skip. Its merge does not close objectui#12045. At landing, the card moves from pm:dispatched to needs-user-decision.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 15, 2026 05:49
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 15, 2026 05:50
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 15, 2026
Merged via the queue into main with commit bedf41c Oct 15, 2026
38 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12045-page-mount branch October 15, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants