Skip to content

test(plugin-detail): pin record:alert's CTA honours resultDialog through the shared runner - #6039

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-5808-record-alert-resultdialog-pin
Aug 24, 2026
Merged

test(plugin-detail): pin record:alert's CTA honours resultDialog through the shared runner#6039
yinlianghui merged 2 commits into
mainfrom
claude/issue-5808-record-alert-resultdialog-pin

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#5808

Test-only. Adds the missing pin the triage fence names: record:alert's CTA honours an
action's resultDialog through the shared ActionRunner when an action provider is
mounted — mirroring the sibling pin record-quick-actions.resultDialog.test.tsx (#5711 /
PR #5807), including the fallback-runner (unwired) branch.

New file: packages/plugin-detail/src/renderers/__tests__/record-alert.resultDialog.test.tsx
(+ an empty-frontmatter changeset — this repo's "releases nothing" declaration).
No source file is touched: record-alert.tsx, useActionEngine.ts and ActionRunner.ts
are read-only in this PR.

The gap, re-derived on this branch's merge-base (not recalled)

The card is from 2026-08-23 and packages/plugin-detail moved twice since (#6000, and
PR #6015 / #5998). Re-running the card's own grep on merge-base 594704f78:

$ grep -in "resultDialog\|ActionProvider" packages/plugin-detail/src/renderers/__tests__/record-alert*.test.tsx
record-alert.test.tsx:14: * require a live provider tree (RecordContext, ActionProvider,

One hit, unchanged, and it is a comment — no assertion in either direction. Per-file
counts: record-alert.test.tsx 1, record-alert.rowBinding.test.tsx 0,
record-alert.visibleWhen.evidence.test.tsx 0,
record-alert.loadingFrameDiagnostic.test.tsx 0. No pin appeared in the interim; the card
is still open on the merits.

The suppression claim, verified in running code

The card's load-bearing claim is that toast suppression keys on resultDialogbeing
set
, not on a handler existing. Confirmed in ActionRunner.handlePostExecution
(packages/core/src/actions/ActionRunner.ts:1135, :1142):

consthasResultDialog=!!(action.resultDialog&&result.success);
...
if(result.success&&!hasResultDialog&&!result.silent&&showToast.showOnSuccess!==false){

hasResultDialog reads the action key and the result only — this.resultDialogHandler is
consulted afterwards, at :1182, purely to decide between opening the dialog and
console.warn-ing. So the silent-success framing stands: with the key set and no handler,
the action reports success, the toast stays suppressed, the value is discarded, and the
only trace is a warn. That branch is now pinned directly (spec 5 below).

What the suite pins

#SpecLeg
1dialog opens with the response value; success toast suppressedpositive (provider mounted)
2counter-probe — an action withoutresultDialog on the same provider does toastpositive
3no provider: value discarded, documented console.warn firesnegative (fallback runner)
4counter-probe — an action without resultDialog, no provider, still executes and does not produce the runner's resultDialog warningnegative
5provider with a toast handler and no result-dialog handler: toast suppressed anyway and the warning firesthe hasResultDialog gate itself

Only the DATA layer is doubled — useMetadataItem, the CTA's metadata fetch — the same
surgical strategy record-alert.test.tsx established under #3941. useActionEngine, the
provider, ActionRunner and RecordContextProvider are the real shipped ones; doubling
any of them would replace the mechanism under test with a second copy of it.

On spec 4 and the counter-probe's toast half: the fence asks that an action without
resultDialog still show its success toast on both legs. On the provider-less leg a
toast is not observable at all — the fallback runner is constructed with no toastHandler
installed, so there is no channel for one. That is a fact about running code, not a gap in
this file. Spec 4 therefore substitutes the strongest observable that leg has: the action
provably executed (an onClick spy fired) while the resultDialog warning provably did not.
Specs 2 and 5 carry the toast half on the legs where a toast can exist.

Failure on demand — three mutations, each restored under trap … EXIT INT TERM

Vitest aliases @object-ui/react and @object-ui/core to src (root vitest.config.mts
resolve.alias), so these mutations take effect with no rebuild step. Each mutation's
landing site was printed, and the injected text and the removed text were grepped
separately.

MutationLanding site (printed, not assumed)PredictedObserved
A — force the CTA down the fallback path: const sharedRunner = providerCtx?.runner ?? null becomes nullpackages/react/src/hooks/useActionEngine.ts:70specs 1, 2 redexit 1, specs 1 and 2 red, 3/4/5 green
B — make the fallback branch behave like the wired one: install a result-dialog handler on the local runnerpackages/react/src/hooks/useActionEngine.ts:103spec 3 redexit 1, spec 3 red, others green
C — suppress the success toast unconditionally (false && on the toast gate)packages/core/src/actions/ActionRunner.ts:1142spec 2 red, spec 1 greenexit 1, exactly that

Mutation C is the one that justifies the counter-probes: it makes spec 1 ("toast
suppressed") pass in a world where no toast could ever appear, and only spec 2 notices.
git diff HEAD --stat was empty after each restore.

Gates — exit codes captured before any pipe, at final HEAD 470c79ca9

GateVerdict line
pnpm --filter @object-ui/plugin-detail type-checkexit 0 — output echoes > @object-ui/plugin-detail@17.6.0 type-check then tsc --noEmit && tsc -p tsconfig.test.json, so this is a real run, not a zero-match silent pass
pnpm exec vitest run packages/plugin-detail/src --maxWorkers=2 (root form)exit 0Test Files 101 passed (101), Tests 943 passed (943)
pnpm --filter @object-ui/plugin-detail lint (eslint ., no --no-inline-config)exit 0823 problems (0 errors, 823 warnings), all pre-existing; the new file contributes 2 no-explicit-any warnings, matching its sibling suites
node scripts/check-changeset-presence.mjsexit 0 — "declares 1 changeset(s) … EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption"
node scripts/check-changeset-no-major.mjsexit 0
node scripts/check-control-bytes.mjsexit 0 — scanned 4987 tracked text files

Merge-base delta vs 594704f78: two files, both added —
packages/plugin-detail/src/renderers/__tests__/record-alert.resultDialog.test.tsx and
.changeset/wise-pandas-pin.md.

Not widened (fence assumption 4)

useActionEngine has exactly three non-test call sites:
record-alert.tsx, record-quick-actions.tsx (both now pinned) and
plugin-dashboard/src/DashboardRenderer.tsx:283. The dashboard one takes the same
fallback, but it builds its action defs in-component from a four-key projection of
schema.header.actions ({ name, type, target, label }, line 273), which cannot carry
resultDialog at all — so it has no exposure to this defect class. Named here rather than
covered; triage can grade whether the same projection dropping confirmText / params is
worth its own card.

Generated by Claude Code


Generated by Claude Code

…the shared runner
`record:alert`'s CTA resolves its action from object metadata and dispatches
through `useActionEngine`, which reuses the surrounding ActionProvider's
ActionRunner when one is mounted and falls back to a local, unwired one
otherwise. `resultDialog` is honoured centrally in
`ActionRunner.handlePostExecution`, whose toast gate keys on the key BEING SET
(`!!(action.resultDialog && result.success)`), not on a handler existing — so a
refactor that dropped the ambient provider would leave the CTA reporting
success, the toast suppressed and the user shown nothing, traced only by a
console.warn.
Mirrors the sibling pin record-quick-actions.resultDialog.test.tsx, with both
legs the triage fence names (provider-mounted, and the fallback-runner branch)
plus the counter-probes that make each leg a measurement rather than an empty
harness.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
…ing nothing
Empty frontmatter — this repo's explicit "releases nothing" declaration for a
test-only change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3219.5 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-B1Sfqp8R.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.38KB3.90KB
app-shell (runtime-config.js)18.10KB6.51KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)505.23KB114.56KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)165.30KB45.79KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.35KB34.44KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)244.12KB61.87KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.11KB6.64KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.49KB2.14KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] record:alert's CTA path shares the same unpinned resultDialog-through-shared-runner gap as record:quick_actions

2 participants

@yinlianghui@claude