Skip to content

feat(core,app-shell,plugin-view,i18n): the delete confirmation names the record and confirms with a destructive Delete (objectui#11695) - #11744

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-11695-delete-confirm-names-record
Oct 6, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-11695-delete-confirm-names-record

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11695

Clause-②: yes

Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 (os-dev seat domain:ui#3, PM loop round 1).

What changes

A record delete confirmation now names what it deletes and confirms with a destructive "Delete" button:

  • One record: the title names the object label and the record's display name. en: Delete Product "QA Widget 0"?. zh: 删除Product“QA Widget 0”?.
  • A selection: the title counts it: Delete 3 Product records? (zh 删除 3 条 Product 记录?).
  • Confirm button: it reads "Delete" (zh 删除) and is painted with buttonVariants({ variant: 'destructive' }).
  • Body: unchanged. It is the plain delete question, the batch question, or ADR-0094's reset question for a package-owned permission set.

Every delete dialog takes this copy from one builder, recordDelete.confirmCopy(deps, target) in @object-ui/core. It sits beside the existing confirmText, which is now its one-record body. The name comes from getRecordDisplayName, the ADR-0079 resolver the record page already uses for its title. Each host passes its object definition, so a declared nameField wins.

Surface Producer What it does now
Console object list, row Delete and bulk Delete (the card's repro) @object-ui/app-shell useObjectActions, called by app-shell ObjectView deleteRecord and the new deleteRecords ask onConfirm with the copy and destructive: true, then run the delete through the runner with no confirmText
Record page Delete @object-ui/app-shell RecordDetailView, the sys_delete header action Same copy, read from the readable row (a denied name field reads as absent). A package-owned permission set now gets the reset question here too
Registered object-view grid Delete @object-ui/plugin-view ObjectView, deleteConfirmDialog Same copy, with the confirm button painted destructive
The console's confirm dialog @object-ui/app-shell ActionConfirmDialog Paints its confirm button destructive when options.destructive is set. Its options type is now the published ConfirmationHandler options, not a copy

The ConfirmationHandler options in @object-ui/core gain destructive?: boolean. Every other confirmation keeps "Confirm Action" / "Continue" in the primary style. The actionConfirm.* text is unchanged.

packages/components/src/ui/alert-dialog.tsx is not touched (No-Touch zone, AGENTS.md #7). The destructive style is a className override at each call site. objectui#8978's actionVariant uses the same approach in the alert-dialog renderer.

Premise check on origin/main 0cfe772, hypothesis by hypothesis

  1. plugin-view's deleteConfirmDialog used actionConfirm.title / actionConfirm.confirm, with recordDelete.confirmText as the body. Held.
  2. actionConfirm.title / .confirm are shared. Held. ActionConfirmDialog falls back to them for every action-runner confirmation. Their text is unchanged; the delete has its own keys.
  3. The detail page's producer is RecordDetailView in packages/app-shell, not packages/plugin-detail. Its sys_delete action asked detail.deleteConfirmation through the page's confirm handler with one argument.
    • The card's repro path (console list, row menu, Delete) never reaches plugin-view. The console list's onDelete calls useObjectActions.deleteRecord, which handed the action runner a confirmText.
    • The runner calls the confirm handler with one argument. So the console dialog could only ever show its generic title and "Continue".
    • Both producers are changed here. The record page falls under the claim's "the dev locates it" clause. The console list's producer adds three app-shell files: useObjectActions.ts, ActionConfirmDialog.tsx and ObjectView.tsx.
  4. The title resolver exists: getRecordDisplayName (@object-ui/core). Reused, no new resolver.
  5. objectui#11728 (objectPluralLabel) was still open when this was built (REST read before implementation: merged: false). So the batch title uses the singular label with a count family that mirrors objectActions.bulkDeleteSuccess (Delete 3 Product records?). That is the same shape as the toast that follows. It has merged since (main 6be0f7a). The Acceptance notes explain why this PR still keeps the count-agreeing noun.

objectui#8978: the card body says it "added the destructive confirm button to alert-dialog", and the triage says there is "no destructive variant". Both are partly right:

  • It added actionVariant to the alert-dialog renderer (packages/components/src/renderers/overlay/alert-dialog.tsx), as a className override.
  • The ui/alert-dialog.tsx primitive still bakes in buttonVariants() with no variant.

Keys

  • New in all ten packs, under objectActions:
    • deleteConfirmTitle ({{label}}, {{name}});
    • the bulkDeleteConfirmTitle count family ({{count}}, {{label}}). Each pack's slots mirror its own bulkDeleteSuccess family: es/fr/pt _many, ru _few/_many, ar all six;
    • deleteConfirmButton.
  • Unchanged: no existing value changed and no key was removed.
  • plugin-view: its English map for hosts without a translation provider gains the new rows. It drops actionConfirm.title / .confirm, which that host no longer asks for.
  • de quote census: de-quote-pairing-3876.test.ts moves from 70 to 71 for the title's one matched „…“ pair.

The console list now passes its translated object label to useObjectActions (objectLabel(objectDef), the label its header shows), so the delete toasts use that label too. With no translation bundle it is objectDef.label, as before.

Tests and gates

The package suites ran at c8f5c14. The final HEAD c3110be differs from it only by a type annotation in one test file. The changed test files and the app-shell type-check were re-run at c3110be.

Check HEAD Result
pnpm exec vitest run packages/app-shell/ --shard=N/4, N = 1..4 c8f5c14 259 + 259 + 259 + 259 files: 2435 (+1 skipped), 2309 (+8 skipped), 3021, 2440 tests passed; 0 failed
pnpm exec vitest run packages/core/ packages/i18n/ packages/plugin-view/ c8f5c14 342 files, 5833 passed, 40 skipped, 0 failed
the six changed test files c3110be 6 files, 56 tests passed
pnpm --filter @object-ui/core type-check / @object-ui/i18n c8f5c14 exit 0 / exit 0
pnpm --filter @object-ui/plugin-view type-check after pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-view^...' build c8f5c14 exit 0
pnpm --filter @object-ui/app-shell type-check after building its closure c3110be exit 0, both tsc --noEmit and tsc -p tsconfig.test.json. Its --listFiles includes the three changed app-shell tests
pnpm exec eslint over the 23 changed TS files c3110be 0 errors; 0 findings on added lines
check:control-bytes, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:i18n-designer-parity, check:new-line-citations, scripts/check-changeset-presence.mjs c3110be all exit 0
check:readme-exports c3110be NOT MEASURED. The gate refuses because 94 documented self-imports point into packages that are not built. This diff adds exports and edits no README

Console compile: narrowed, not run.

  • apps/console resolves @object-ui/* through built dist; its tsconfig has no paths.
  • Its source names none of the changed symbols. A search for useObjectActions|ActionConfirmDialog|ConfirmDialogState|ConfirmationHandler|recordDelete in apps/console/src finds one hit, in a comment.
  • The control search for from '@object-ui/app-shell' does find the console's real imports.
  • So the console reads only app-shell's emitted declarations, and that source passes the type-check above.

Reverse verification (ablation)

Every leg ran through objectstack's scripts/ablation-replace.mjs in WRAP mode, which checks three things:

  • the anchor must hit;
  • the file's blob must change;
  • the restore is proven by the blob matching HEAD and an empty git diff HEAD.

The expected direction for every leg was red.

Leg Mutation (anchor hit once, blob changed) Result
1, destructive paint ActionConfirmDialog: the options.destructive className replaced by className={undefined} 6 failed, 4 passed. Red: the console list's row and batch in en and zh, and the record page in en and zh, all on bg-destructive. The primary-style controls stayed green
2, delete label recordDelete.confirmCopy: deleteConfirmButton replaced by actionConfirm.confirm 16 failed, 22 passed, across core (copy mismatch), the console list, the record page and plugin-view (no "Delete" button found)
3, name field useObjectActions.deleteRecord: objectDef dropped from the copy's deps 2 failed, 4 passed. The row title read Delete Product "SKU-1"? (a record key) instead of the declared nameField value
type-level RecordDetailView: destructive: true misspelled destructiv tsc --noEmit reports TS2561 on that key against the rebuilt ConfirmationHandler options

Acceptance notes

  • Plural label. objectui#11728 landed after this was built, but the batch title deliberately keeps "N {{label}} records" rather than "N {{pluralLabel}}".
    • With "records", count agreement lives in a pack noun that each language inflects (ru записи/записей, ar six forms).
    • A metadata plural label has one form per object, so it cannot agree with a count.
    • It is the same pattern as the toast that follows.
    • Changing to "Delete 3 Products?" would only change pack values, not code.
  • Bulk body. The batch dialog's body is still console.objectView.bulkDeleteConfirm ("Delete N selected records? This cannot be undone."), so the title and the body both ask. The claim limited locale edits to new delete keys, so no existing value was changed.
  • Ask before execute. useObjectActions now asks before execute, so the hook's loading is false while the dialog is open. No code in the repo reads that hook's loading or error. A cancel still returns the runner's former { success: false, error: 'Action cancelled by user' }.
  • Nameless rows. A row with no resolvable name shows the resolver's English Record #id fallback, the same one every other surface shows.
  • Same class, not touched. Recorded only, no card filed, carrier: none:
    • plugin-detail DetailView still deletes through the native window.confirm, with the authorable deleteConfirmation or the generic detail.deleteConfirmation. That is the drawer and panel detail used by gantt rows and similar.
    • app-shell's "delete saved view" confirm says "Delete" but uses the primary style.
    • After a package-owned permission set is reset, the record page's delete still toasts "Record deleted". The list's shared recordDelete.run toasts the reset. Not measured through a public entry point.
  • plugin-view's label is still the raw objectSchema.label, the one its form titles and toasts already use.
  • Merge with current main (ded4494) is textually clean (git merge-tree). main's de pack quote count is unchanged since the base, so the census pin does not collide.

Generated by Claude Code

claude added 7 commits October 6, 2026 17:01
…ecord and confirms with a destructive Delete (objectui#11695)

recordDelete.confirmCopy builds the whole delete confirmation once - a title
naming the record by the ADR-0079 resolver plus the object label (or counting
a batch), the existing question as the body, and a Delete confirm label - and
every host's delete dialog shows it: the console list's row and bulk Delete
(useObjectActions), the record page's Delete (RecordDetailView) and the
registered object-view's grid Delete (plugin-view). ConfirmationHandler's
options gain `destructive`, which ActionConfirmDialog paints with the
destructive button variant; plugin-view paints its own dialog the same way.
New keys in all ten packs.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…mation in en and zh (objectui#11695)

The console list (real useObjectActions + ActionConfirmDialog under a real
I18nProvider), the record page and the registered object-view each show a
title naming the record (or counting the batch) and a destructive Delete
confirm, in en and zh; a confirmation without `destructive` keeps the primary
confirm as the control.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…n (objectui#11695)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…e's named copy (objectui#11695)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
… pair (objectui#11695)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…`any` (objectui#11695)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…ndler (objectui#11695)

`ReturnType<typeof vi.fn>` includes a constructable mock and is refused by
the test config's tsc against `ConfirmationHandler`.

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

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3327.1 KB 3330.4 KB
Main entry chunk (gzip) 154.8 KB 350 KB
Entry file index-DvT9npGf.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) 17.82KB 6.58KB
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.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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 578.98KB 139.21KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 233.72KB 64.83KB
fields (index.js) 263.36KB 66.69KB
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.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) 37.51KB 10.04KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.18KB 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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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) 53.39KB 15.52KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.26KB 38.97KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 247.28KB 65.06KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.09KB 45.89KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 238.51KB 65.53KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 116.85KB 29.12KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.90KB 11.74KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 90.58KB 22.80KB
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) 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.08KB 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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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: c3110bee48fc8914703a8b8b793829d48be3fad3
Local-runs: none

Inputs read: card objectui#11695 (body and all 4 comments: triage 6011124623, claim 6021049528, os-dev-report 6023350986, claim amendment 6023399466), PR objectui#11744 (body, 25-file list, 1 comment, net diff against main at the head), and the 42 check-runs on the head. Check-runs waited to completion and read at 19:11Z: 40 success, 3 skipped (Test (coverage), the coverage shard matrix and dependabot), 0 failures. Type Check, Test and all eight Test (shard N/8), Lint, Build & E2E, README Export Check, Governed Surface Queue Guard, the four Changeset * gates, Line Citation Gate and Spec Main Shape Gate are all success. PR is draft, head repo equals base repo, auto-merge not armed, 761 additions / 87 deletions.

① Derived judgments

Public-surface and accept-set changes the diff implies, each judged:

  1. @object-ui/core — recordDelete.confirmCopy(deps, target) added to the frozen recordDelete object; new exported types RecordDeleteConfirmCopy and RecordDeleteConfirmTarget (reach the package root through actions/index.ts and the core barrel's export * from './actions/index.js'). RIGHT: additive. The one-record branch names the record through getRecordDisplayName(deps.objectDef, record) — the ADR-0079 resolver the record header already uses, so a declared nameField wins and an id-only row reads by the Record #id floor — and the body is the unchanged recordDelete.confirmText, so the ADR-0094 reset question for a package-owned permission set is preserved. The batch branch asks objectActions.bulkDeleteConfirmTitle with count and label and keeps the existing console.objectView.bulkDeleteConfirm body. confirmText's signature and behaviour are unchanged. Pinned by the five new recordDelete.confirmCopy cases including the control that drops objectDef and reads the record key.
  2. @object-ui/core — ConfirmationHandler options gain destructive?: boolean. RIGHT: optional, so every existing handler and every existing two-argument call still type-checks (Type Check success). The runner's own confirm step still hands the handler one argument; the diff adds no second dialect, it adds one optional field to the one options bag.
  3. @object-ui/app-shell — useObjectActions (exported from the package root): config gains objectDef?: unknown, onConfirm is retyped from the inline three-field shape to the published ConfirmationHandler, the result gains deleteRecords(records). RIGHT as a widening: the new onConfirm type accepts every handler the old one accepted. Behaviour change judged right: deleteRecord now asks ONCE through onConfirm ?? window.confirm with title, confirm label and destructive: true, then runs the runner's delete with no confirmText, so the runner's confirm step is not asked a second time; a cancel returns the runner's former { success: false, error: 'Action cancelled by user' } (pinned). The dev's note that the hook's loading is false while the dialog is open is accepted — nothing in the repo reads it, and the runner's post-execution hook still owns the toasts through recordDelete.run.
  4. @object-ui/app-shell — ConfirmDialogState.options retyped to the published ConfirmationHandler's own options parameter (read off the handler type with Parameters, not re-spelled); ActionConfirmDialog paints buttonVariants({ variant: 'destructive' }) as a className when options.destructive is set. RIGHT: the primitive AlertDialogAction composes cn(buttonVariants(), className), so tailwind-merge resolves bg-primary to bg-destructive; without the flag the class is byte-identical. packages/components/src/ui/alert-dialog.tsx is untouched (No-Touch 构建一个 **Live Playground (实时演练场)** (用于展示引擎能力) #7 held), matching the precedent of objectui#8978's renderer-side actionVariant. buttonVariants is reachable from the @object-ui/components barrel (export * from './ui', ui/index.ts re-exports ./button). The control pin (a plain confirm keeps bg-primary) shows the class check can fail.
  5. @object-ui/app-shell — console list ObjectView: bulk delete routes through actions.deleteRecords(valid) instead of actions.execute with an inline confirmText; useObjectActions receives objectLabel(objectDef) (the translated label the header shows) and objectDef. RIGHT: the bulk path now confirms once with the counting title; the toasts read the translated label, identical to objectDef.label with no bundle. The row path already called deleteRecord(String(record.id), record) and needed no edit.
  6. @object-ui/app-shell — RecordDetailView sys_delete: asks confirmHandler(copy.message, { title, confirmText, destructive: true }) over readablePageRecord (a denied name field reads as absent) instead of the one-argument detail.deleteConfirmation. RIGHT: same copy as the list, same in-app dialog as objectui#11001 required, and the body for a package-owned permission set becomes the ADR-0094 reset question — an improvement in honesty. The delete itself (dataSource.delete plus the detail.deleted toast) is unchanged; see ③.
  7. @object-ui/plugin-view — deleteConfirmDialog reads title, body and confirm label from recordDelete.confirmCopy with objectDef: objectSchema; the confirm button is painted destructive; the module-private VIEW_DEFAULT_TRANSLATIONS gains the new objectActions.* rows (with _one / _other slots) and drops actionConfirm.title / actionConfirm.confirm. RIGHT: the map is not exported (index.tsx exports ObjectView and friends only), so dropping two rows this host no longer asks for is internal; actionConfirm.cancel stays. tView is the createSafeTranslation translator, which under a real I18nProvider reads the session pack (zh pin) and without one reads the fallback rows with count-family selection (en pins).
  8. @object-ui/i18n — new keys in all ten packs under objectActions: deleteConfirmTitle (placeholders label and name), the bulkDeleteConfirmTitle count family (placeholders count and label; slots mirror each pack's own bulkDeleteSuccess family: es/fr/pt add _many, ru _one / _few / _many / _other, ar all six), deleteConfirmButton. RIGHT: no existing value changed, no key removed (read across all ten patches). The de-quote-pairing-3876 census pin moves 70 to 71 with its history note, for the one new matched German quote pair in deleteConfirmTitle — a correct pin update, not a laundered exemption.
  9. content/docs/guide/console-architecture.md — one line documenting ConfirmationHandler options and destructive. RIGHT per objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2; Build Docs and Internal Docs Link Check success. No path:line citation anywhere in the diff (Line Citation Gate success).
  10. Governed surfaces — none touched (no .claude/**, skills/**, docs/adr/**, AGENTS.md, CLAUDE.md); Governed Surface Queue Guard success. Not a fork PR; under the 5,000 changed-line ceiling.

Card Done-when and pin, judged against the diff: the confirmation names the record by its title field (ADR-0079 nameField through getRecordDisplayName) and the object label — met; the confirm button reads "Delete" in the destructive style — met in all three hosts; translated in every locale — ten packs; both call sites share the one text — all three producers (console list row and bulk, record page, registered object-view grid) read one builder. Pin "en and zh-CN" — pinned in en and zh for the console list (useObjectActions.deleteDialog-11695.test.tsx), the record page (RecordDetailView.deleteConfirmDialog-11001.test.tsx) and plugin-view (ObjectView.gridDelete-10383.test.tsx). The card's "Where" named plugin-view as the repro path; the dev measured the console list deletes through app-shell useObjectActions and fixed both producers — the correct reading of "fix at the producer", and the claim amendment widened the file surface to match.

② Semver level

Changeset .changeset/11695-delete-confirm-names-record.md: @object-ui/core, @object-ui/app-shell, @object-ui/plugin-view, @object-ui/i18n at minor. Matches what the diff publishes: new exports in core, a new optional handler option, a widened hook config and result in app-shell, new pack keys in i18n, and a behavioural change in plugin-view — all additive, nothing removed or renamed from a public surface, so no migration text is owed and none is needed. minor is the objectui ceiling (check-changeset-no-major) and the right floor for a Clause-②: yes widening. The body describes the user-visible change and names the new API by symbol, with no model identifier and no line address. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check and Changeset Claim Re-read are success. No skip-changeset label.

Clause-②: yes (PR body, claim and amendment agree) — correct: new i18n keys ship in the exported packs, core gains confirmCopy and destructive, app-shell's exported hook gains objectDef and deleteRecords. Pure widening; no (narrowing) arm applies.

③ Boundary flags

Dev deviations (os-dev-report 6023350986):

  1. File surface widened to the measured producers (app-shell useObjectActions.ts, ActionConfirmDialog.tsx, ObjectView.tsx, core ActionRunner.ts, RecordDetailView.tsx in app-shell rather than plugin-detail, the docs guide, the de census pin) — ANSWERED: adopted by the seat's claim amendment 6023399466; each file's edit is load-bearing for the card (judged in ①).
  2. Console list toasts now read the translated object label — ANSWERED: accepted; identical with no bundle, and it makes the toast agree with the header and the new dialog.
  3. Console compile narrowed, not run locally — ANSWERED by the head: Build & E2E and Type Check are success.
  4. app-shell suite run in four shards locally — ANSWERED by the head: Test and all eight Test (shard N/8) are success.
  5. Two lock queue timeouts retried; 6. one tsc --listFiles outside the verify lock — process notes, no contract effect; accepted.
  6. Commit trailers use the model-free pair rather than the harness reminder's model-named trailer — ANSWERED: that is what AGENTS.md requires; accepted.

check:readme-exports NOT MEASURED locally — ANSWERED by the head: README Export Check is success.

Open question (plural label; objectui#11728 merged after the build): the seat answered A in 6023399466 (keep "Delete COUNT LABEL records?"). JUDGED RIGHT, not escalated: the card's Done-when constrains the single-record title and the destructive "Delete" button; the body's bulk wording ("Delete 3 Tasks?") is an example. A pack noun carries count agreement that a one-form metadata plural label cannot (ru записи / записей, ar six forms), and it matches the bulkDeleteSuccess toast that follows. Switching to the plural label later is a pack-value and plumbing change on its own card if the maintainer wants the card's exact wording.

Out-of-scope findings (carrier: none, noted, not filed) — each answered:

  • plugin-detail DetailView.handleDelete still confirms through native window.confirm — ESCALATED to the owning seat, non-blocking: same class as objectui#11001; measure reach through a public entry point (the drawer or panel detail, e.g. gantt rows) and file a card if it reproduces. Not this PR's producer.
  • app-shell delete-saved-view confirm says "Delete" in the primary style — ESCALATED, non-blocking: a one-flag candidate card (destructive: true on handleDeleteView's confirm); out of this card's scope.
  • Record page delete of a package-owned permission set toasts "Record deleted" after what ADR-0094 defines as a reset — ESCALATED, non-blocking: pre-existing, and this PR narrows the gap (the dialog now asks the reset question); the toast still advertises a delete the runtime does not perform, which is a Prime Directive [WIP] Enhance every detail of the designer #10 candidate card once reach is measured. Not a regression introduced here.
  • Bulk dialog body repeats the title's question — ANSWERED: accepted as copy polish; the claim limited locale edits to new keys, so no existing value was changed, which is the right call for this card.

No needs-user-decision item remains open on this head.

Implemented-by: claude/issue-11695-delete-confirm-names-record
Reviewed-by: session_01CGZy1BGCjdN5cXqL9cnvB8

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 6, 2026 19:15
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 6, 2026 19:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit 17acfbb Oct 6, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11695-delete-confirm-names-record branch October 6, 2026 19:34
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.

delete confirmation does not name the record and confirms with a neutral "Continue" — the destructive alert-dialog variant is not used

2 participants