Skip to content

fix(app-shell): a record page's delete asks through the console's own confirm dialog (objectui#11001) - #11074

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11001-record-delete-confirm
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11001-record-delete-confirm

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11001
Clause-②: no

What changed

On a record page, More actions then Delete asked through the browser's native window.confirm. The header's sys_delete action in packages/app-shell/src/views/RecordDetailView.tsx now awaits confirmHandler(msg): the page's own promise-based confirm handler, the one it already hands the action runner as onConfirm, which opens the in-app ActionConfirmDialog. That is the same dialog the list view's delete opens: app-shell's ObjectView hands useConsoleActionRuntime's confirm handler to useObjectActions as onConfirm, and the runner calls it with the delete question. Confirm runs the existing delete unchanged (dataSource.delete, the success toast, back to the object's list). Cancel returns before anything runs, so the record and the page stay as they are.

  • The question is the page's existing detail.deleteConfirmation. The dialog's title and buttons are its defaults (actionConfirm.title, actionConfirm.confirm, actionConfirm.cancel), exactly what the list view's delete shows, because both callers pass one argument. No pack key is added, so this is not serial behind objectui#11039.
  • Two comments said the handler had exactly one caller (the runner): the ConfirmationHandler note above confirmHandler in RecordDetailView.tsx, and the header of RecordDetailView.confirmRuntimeParity-5835.test.tsx. This change made both false, so both now name the second caller. That test file is outside the claim's declared file surface; the edit is comment-only, and the file's tests ran green (below).

Premises checked on origin/main 827550193

  1. The sys_delete action's onClick in RecordDetailView.tsx called if (!window.confirm(msg)) return;. Confirmed.
  2. app-shell's ObjectView hands actionRuntime.confirmHandler to useObjectActions as onConfirm and renders actionRuntime.dialogs, which include ActionConfirmDialog. Confirmed; the row delete goes through actions.deleteRecord, then the runner, then that handler with one argument.
  3. useObjectActions documents onConfirm as falling back to window.confirm. The record page does not mount that hook. It already owns a handler of the same published ConfirmationHandler type feeding the same ActionConfirmDialog (parity pinned by RecordDetailView.confirmRuntimeParity-5835.test.tsx), so the seam used is that existing handler, not a second hook mount.
  4. detail.deleteConfirmation and the three actionConfirm keys exist in all ten packs.

Pin

packages/app-shell/src/views/RecordDetailView.deleteConfirmDialog-11001.test.tsx mounts the real record page under a real I18nProvider (en) with the REAL ActionConfirmDialog, opens the header overflow and chooses Delete. Every test installs a window.confirm spy that answers yes, so the old code would delete at once with no dialog.

  • The alertdialog opens carrying detail.deleteConfirmation and the dialog's default buttons; nothing is deleted yet; the spy is not called.
  • Continue: dataSource.delete is called once with the object and record id, one success toast, and the router leaves for the object's list.
  • Cancel: the dialog closes, no delete, no success toast, and the router location is unchanged.

Strings are read from the en pack by key, not written out.

Verification (final commit 64c0db025)

  • pnpm exec vitest run over every test file that names RecordDetailView (104 files: all 37 RecordDetailView.* pins, 36 existing plus the new one; the three app-shell ratchets that read it; useConsoleActionRuntime.test.tsx; the console and package tests that mention it): Test Files 104 passed (104), Tests 1028 passed (1028).
  • Ablation, with the fix committed first. The mutation went through ablation-replace.mjs (anchor hits 1 then 0, blob changed) and was restored to the HEAD blob with git diff HEAD empty. With if (!window.confirm(msg)) return; put back, the new pin reads Tests 3 failed (3), each Unable to find role="alertdialog". Run at a537ec3e7 and again at 64c0db025, same result. No build or dist step is involved: the pin imports ./RecordDetailView from source.
  • pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0, after building the @object-ui/app-shell^... closure with turbo (28 of 28 tasks). tsc -p tsconfig.test.json --listFilesOnly lists the new pin and the edited parity test.
  • pnpm exec eslint --format json on the three changed .tsx files: 3 files, 0 errors. Narrowing proof: the population is the **/*.{ts,tsx} block of eslint.config.js; no parserOptions.project or projectService is set, so linting is not type-aware and this diff cannot move a verdict on an untouched file. Warnings on the two edited files are unchanged from the base (111 and 17, read through --stdin at 827550193).
  • Exit 0: check-changeset-presence, check-changeset-no-major, check-changeset-fixed, check-changeset-overwrite, check-changeset-claims (report-only; it names .changeset/7997-detail-view-related-retired.md because that body mentions RecordDetailView.tsx; its paragraph is about the retired DetailView schema mirror and this change does not touch that), check-control-bytes, check-new-cross-file-line-citations (0 new), check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-test-path-roots, check-type-check-coverage, check-lint-coverage, check:i18n-keys, check:handler-key-reads, check:pending-changeset-literals. check-governed-queue-guard --test over the four paths: NOT GOVERNED.
  • Declared narrowing: the whole packages/app-shell/src/views/ directory (565 test files) was NOT run locally. The 104-file set took about 6 minutes under the shared lock, so the directory would not fit the 10-minute foreground cap; CI's sharded pnpm test runs all of it. The set run is every test file that names RecordDetailView; a test that reaches the page only through a transitive import, without naming it, is not in it.

Acceptance notes

  • Read-only observation, not filed: the record page's Delete does not go through the shared recordDelete core in @object-ui/core (objectui#10383) that the list view's delete uses. So for a package-owned sys_permission_set row (ADR-0094) the record page would ask the plain delete question and toast "Record deleted" where the list view asks the reset question. Not measured on a live page, and whether the record page offers Delete on that object at all was not checked. Carrier: none.

Generated by Claude Code

… confirm dialog

The record header's `sys_delete` action asked through the browser's native
`window.confirm`. It now awaits the page's own confirm runtime, the same
promise-based handler the page hands the action runner as `onConfirm`, which
renders the in-app `ActionConfirmDialog` the list view's delete uses. One
argument, like the runner's call, so the dialog shows the same default title
and buttons as the list view. Cancel leaves the record and the page as they are.

Two comments that said the handler had exactly one caller now name the second.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…app confirm dialog

Mounts the real record page under a real `I18nProvider` with the real
`ActionConfirmDialog`, chooses Delete from the header overflow, and pins
three things: the alert dialog opens with the page's own delete question and
the dialog's default buttons while nothing is deleted yet; confirming runs the
existing delete and leaves for the object's list; cancelling deletes nothing
and leaves the router where it was. A `window.confirm` spy that answers yes
stays uncalled in all three.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…r, no any-casts

The pin now saves the window's own `confirm` descriptor, defines the spy over
it, and hands the original back (or removes the own slot) after each test.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/7997-detail-view-related-retired.md

  • names RecordDetailView.tsx → packages/app-shell/src/views/RecordDetailView.tsx — edited by this change

    Why it retired. @objectstack/spec declares no DetailView schema at all — every DetailView occurrence in packages/spec/src is prose about this repo's own RecordDetailView.tsx — so this array mirrored no protocol schema and drifted freely: it declared columns as TableColumn[] while the renderer it fed also accepted bare field names, { field, label } and legacy { name, label } spellings. The axis that carried the ruling was measured zero pull: no application code authored the member, both internal producers of a detail-view node (RecordDetailDrawer, renderers/record-details.tsx) synthesize it without related, and the only in-tree authorings carrying real columns were two documents — both rewritten here.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 51c294958 (merge-base with origin/main): 3 file(s) changed outside .changeset/, read against 1737 pending declaration(s) that publish a body (2341 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3107.9 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-BpPbL5PQ.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) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
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.17KB 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.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 558.92KB 133.97KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.99KB 63.22KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
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) 13.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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.01KB 3.93KB
plugin-calendar (index.js) 52.33KB 15.01KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.68KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.54KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 169.60KB 43.32KB
plugin-gantt (index.js) 171.12KB 42.43KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.77KB 22.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
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) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 19.75KB 6.81KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.26KB
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 (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-components.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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.30KB 6.99KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 64c0db0252fc5bceb1a2fdd09150bc4ec2bd00b7
Local-runs: none

Inputs read, and nothing else: card #11001 (body and all three comments: triage 5874827009, claim 5886653563, os-dev-report 5887544880); PR #11074 (body, four-file list, net diff against main at the head); the head's check-runs with filter=latest. Files read for context at the head through the contents API only (a read, not a run): RecordDetailView.tsx, RecordDetailView.confirmRuntimeParity-5835.test.tsx, ObjectView.tsx, ActionConfirmDialog.tsx, useConsoleActionRuntime.tsx, useObjectActions.ts, core/src/actions/ActionRunner.ts, core/src/actions/recordDelete.ts, i18n/src/locales/en.ts, app-shell/package.json, .changeset/config.json, AGENTS.md, and the PR's four commits.

Check-runs on the head

Read complete at 2026-09-29T09:43Z: 43 latest runs, 40 success, 3 skipped, 0 failure, 0 in progress. The skipped three are Test (coverage), the coverage matrix shard and dependabot, each skipped by its own workflow condition — no verdict withheld. Twelve runs were in progress when this review began (Test (shard 1/8) through (shard 8/8), Type Check, Lint, Spec Main Shape Gate) and a 43rd, the Test rollup, appeared once the shards landed; all were waited for, none re-run, and all concluded success. The eight shards plus the rollup are CI's sharded pnpm test, which runs the whole packages/app-shell/src/views/ set the dev declared NOT run locally. Every family the dev ran locally has its CI counterpart green here: Type Check, Lint, Changeset Declaration / Bump Policy / Fixed Group Check / Claim Re-read / Overwrite Report, Control Byte Scan, Line Citation Gate, Inert vi.mock Specifier Check, Governed Surface Queue Guard, Test (dist pins), Build and E2E.

① Derived judgments

  1. RIGHT — the one code change. In RecordDetailView.tsx the header sys_delete onClick (already async) goes from if (!window.confirm(msg)) return; to if (!(await confirmHandler(msg))) return;. confirmHandler (line 703 at the head) is the page's own useCallback typed as the published ConfirmationHandler from @object-ui/core; it settles a Promise through setConfirmState, which the page renders into ActionConfirmDialog (line 2829, in the same return branch as the header, 2605 onward; the three early returns at 2360, 2364 and 2384 render neither header nor dialog). ActionConfirmDialog is the ONE component useConsoleActionRuntime renders too (import at its line 44, render at 780), and ObjectView hands that hook's confirmHandler to useObjectActions as onConfirm (ObjectView 1595-1601, 2252-2258) and renders actionRuntime.dialogs (3634). So the card's Expected — the same in-app dialog the list view uses — holds at the component: one dialog, two runtimes, a parity the 5835 pin already holds.
  2. RIGHT — one argument, dialog defaults. The list view's delete reaches its handler through the runner (useObjectActions.deleteRecord then execute; ActionRunner.ts 1237-1240 calls this.confirmHandler with the evaluated confirmText and nothing else), so its dialog shows actionConfirm.title, .confirm, .cancel. The new caller passes one argument too, so the record page shows the same defaults. For precision: the two QUESTIONS are different keys with the same English today — the list view asks objectActions.deleteConfirm (through recordDelete.confirmText), the record page keeps its own detail.deleteConfirmation (en 1196). The PR body says exactly that (the page's existing detail.deleteConfirmation; title and buttons exactly what the list view shows) and claims no shared key. All four keys exist in en (1196 and 3129-3133); no pack key is added, so the claim's serial guard behind objectui#11039 is not engaged.
  3. RIGHT — cancel and confirm. ActionConfirmDialog.handleCancel settles false on the Cancel button AND on Radix onOpenChange(false) (Escape, overlay), so no Promise dangles; the page's own onOpenChange only closes. Confirm settles true and the existing path runs unchanged: dataSource.delete(objectName, pureRecordId), the detail.deleted toast, navigate to the object's list with replace: true. Nothing else on the page moves.
  4. RIGHT — the two comment edits. Besides its declaration, confirmHandler has exactly two uses at the head (2535 the new caller, 2690 the onConfirm prop); the note above the declaration (686-691) and the parity test header (47-50) now say so, and both were false the moment the fix landed. Comment-only in the parity test, which is outside the claim's declared surface (③ 1).
  5. RIGHT — the pin discriminates. RecordDetailView.deleteConfirmDialog-11001.test.tsx mounts the real page under a real I18nProvider (en) with the REAL ActionConfirmDialog (only orthogonal chrome is doubled: ActionParamDialog, ActionResultDialog, FlowRunner, MetadataInspector), installs a window.confirm spy that answers yes by property descriptor and hands the slot back after each test, opens the header overflow and chooses Delete by the detail.delete label read from the pack. Against the base code the yes-spy deletes at once with no alertdialog, so test 1 (findByRole('alertdialog'), question by key, default buttons by key, no delete, spy uncalled, path unchanged) is red there; the dev's ablation reads 3 failed with that reason. Tests 2 and 3 pin confirm (one delete call with the object and id, one success toast, router at /crm_call, which is what an empty baseAppUrl under a bare MemoryRouter yields) and cancel (dialog gone, no delete, no toast, path unchanged). Strings by key, not literals.
  6. RIGHT — public surface. @object-ui/app-shell exports . and ./styles.css; the diff moves no export, type, prop or schema, and RecordDetailViewProps is unchanged. The published behaviour that changes is which box one exported view asks its delete question in — a fix, not a widening of any accept set.
  7. RIGHT — the seam chosen, against the triage direction. Triage said route it through the seam useObjectActions documents (onConfirm, falling back to window.confirm), as ObjectView does. The record page does not mount useObjectActions; it already owns a handler of the same published type feeding the same dialog, and the page's ActionProvider shadows the shell runtime for every action on it. Using that handler IS that seam with no second runtime mounted; mounting the hook here would have added the duplication the 5835 pin exists to contain. Within direction.
  8. WRONG — none found.

② Semver level

.changeset/11001-record-delete-confirm.md declares '@object-ui/app-shell': patch. @object-ui/app-shell is published (publishConfig.access: public, 17.6.0) and a member of the fixed group in .changeset/config.json; patch is the right level for a behaviour fix inside one exported view with no API movement. The body names the card, the change and that no pack key is added. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read and Changeset Overwrite Report are all success on the head; the dev's report-only check-changeset-claims hit on 7997-detail-view-related-retired.md is a mention of RecordDetailView.tsx in a paragraph about the retired DetailView mirror, which this diff does not touch.

Clause-②: no — matches the claim (5886653563, Clause-②: no) and the PR body. No type, schema or export moves; the diff is one call-site swap, two comments, one pin and one changeset.

③ Boundary flags

  1. Dev flag — file surface +1, RecordDetailView.confirmRuntimeParity-5835.test.tsx, comment-only. Answered: the claim's surface named RecordDetailView.tsx, a pin and a changeset; the extra edit is three lines of header prose whose "nowhere else" sentence this fix made false. Leaving it false was the worse option; no assertion moved; the file runs green in the head's shards. Accepted.
  2. Dev flag — local vitest narrowed from packages/app-shell/src/views/ (565 files) to the 104 files naming RecordDetailView. Answered by the head's check-runs: the eight Test (shard N/8) runs and Test (dist pins) are the whole set, not a sample; all eight shards, the Test rollup and the dist pins concluded success on this head.
  3. Dev flag — commit trailers are the model-free pair (Claude-Session plus Co-authored-by: Claude). Verified on all four commits (0f12ede, a537ec3, 2a4a809, 64c0db0). AGENTS.md 325-329 makes that pair the repo rule and says the harness attribution reminder yields to it. Answered; not a code concern.
  4. open_questions: none declared — nothing to answer.
  5. Out-of-scope finding (carrier: none; PR Acceptance notes): the record page's Delete does not go through the shared recordDelete core in @object-ui/core (objectui#10383) the list view binds to, so a package-owned sys_permission_set row (ADR-0094) would get the plain question and "Record deleted" here where the list view asks the reset question. Judged NOT this PR's to fix: the claim's surface is the confirm BOX on RecordDetailView.tsx; binding the page to recordDelete changes the question key, the toasts and adds a best-effort findOne lookup — the objectui#10383 family ("every host's list Delete"), a change with its own consent-copy semantics. An honest note, correctly not filed as this card's work. Does the PR make it worse: NO — before and after, the page asks detail.deleteConfirmation, calls the same dataSource.delete and toasts detail.deleted; only the box moved. Reach is unmeasured, as the dev says: resolveRecordHeaderActionGates gates Delete at the object level while the ADR-0094 flag is row-level (managed_by), so the page may well offer Delete on such a row. Escalated to the seat as a candidate card of its own; no blocker here.

Implemented-by: claude/issue-11001-record-delete-confirm
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 09:46
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 981389b Sep 29, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11001-record-delete-confirm branch September 29, 2026 10:02
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.

RecordDetailView: deleting a record asks through the native window.confirm, while the list view already uses the in-app confirm dialog

2 participants