You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/08/30 20:30:20 UTC
(causeway) 01/03: docs(openspec): plan destructive action confirmation
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch htmx
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 24c2168dbf97d3209c722e3664e9ccc15b88abff
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun Aug 30 21:51:07 2026 +0200
docs(openspec): plan destructive action confirmation
---
.../confirm-destructive-actions/.openspec.yaml | 2 +
.../changes/confirm-destructive-actions/design.md | 97 ++++++++++++++++++++++
.../confirm-destructive-actions/proposal.md | 29 +++++++
.../specs/domain-web-components/spec.md | 46 ++++++++++
.../generic-htmx-web-component-viewer/spec.md | 37 +++++++++
.../specs/rich-graphql-member-metadata/spec.md | 22 +++++
.../changes/confirm-destructive-actions/tasks.md | 25 ++++++
7 files changed, 258 insertions(+)
diff --git a/openspec/changes/confirm-destructive-actions/.openspec.yaml b/openspec/changes/confirm-destructive-actions/.openspec.yaml
new file mode 100644
index 00000000000..88ea4823207
--- /dev/null
+++ b/openspec/changes/confirm-destructive-actions/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-08-30
diff --git a/openspec/changes/confirm-destructive-actions/design.md b/openspec/changes/confirm-destructive-actions/design.md
new file mode 100644
index 00000000000..c2d660fa127
--- /dev/null
+++ b/openspec/changes/confirm-destructive-actions/design.md
@@ -0,0 +1,97 @@
+## Context
+
+Rich GraphQL action wrappers already expose canonical static presentation metadata, but they do not expose the metamodel's are-you-sure semantic.
+The Web Components action element therefore cannot tell the standard interaction controller that an otherwise invokable action needs confirmation.
+The controller currently prepares parameterless and parameterized actions, validates complete parameter values, and invokes immediately.
+Petclinic's parameterless `delete` action is marked `NON_IDEMPOTENT_ARE_YOU_SURE`, making it a deterministic acceptance example, but its current fixture-only disable rule masks the pinned original application's visit-based veto.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Expose canonical are-you-sure semantics without invoking domain behavior or broadening GraphQL metadata into a metamodel API.
+- Insert confirmation after authoritative preparation and validation but before invocation.
+- Preserve pending parameter values when a user declines a parameterized action's confirmation.
+- Preserve action-source focus when a parameterless confirmation is cancelled.
+- Restore Petclinic's original rule that an owner with visits cannot be deleted.
+
+**Non-Goals:**
+
+- Custom confirmation text, browser-native `window.confirm`, or application-authored confirmation policy.
+- Confirmation for ordinary non-idempotent, idempotent, or safe actions.
+- Cascading deletion of visits to make owner deletion succeed.
+- Changes to server-side authorization, validation, invocation placement, or result policy.
+
+## Decisions
+
+### Expose a nullable `areYouSure` Boolean in shared rich member metadata
+
+`RichMemberMetadata` will add `areYouSure` as a nullable Boolean.
+For an `ObjectAction`, it reflects `action.getSemantics().isAreYouSure()`; for property, collection, and parameter wrappers it is null.
+A Boolean communicates the exact client decision while avoiding coupling clients to all `SemanticsOf` enum values.
+The field remains side-effect-free and backward compatible because GraphQL responses include it only when selected.
+
+Alternative considered: expose the complete semantics enum or string.
+That would reveal more metamodel vocabulary than this viewer needs and force clients to interpret enum evolution.
+
+### Treat confirmation as authoritative action presentation, not HTML configuration
+
+Object and service action state reads will select `metadata.areYouSure` when the schema advertises it.
+`<cw-action>` will carry only a strict `true` value into the semantic action request and standard controller.
+No public attribute will enable or disable confirmation, so page-authored presentation cannot weaken canonical destructive semantics.
+Older compatible schemas that lack the field continue with existing behavior.
+
+Alternative considered: infer confirmation from mutation placement or action names.
+Neither reliably represents Causeway's explicit semantics.
+
+### Add a `confirming` interaction state before invocation
+
+After parameterless preparation, an are-you-sure action enters `confirming` instead of invoking.
+After a parameterized action passes parameter preparation and whole-action validation, it enters the same state instead of invoking.
+Confirmation invokes once using the retained action id, context, values, source, and generation.
+Declining a parameterized confirmation returns to editing with values intact; declining a parameterless confirmation closes the prompt and restores source focus.
+Escape follows the same decline behavior, while stale generations and repeated activation remain gated.
+
+Alternative considered: call `window.confirm`.
+A controller-owned light-DOM dialog provides stable accessibility, styling, automation, focus, and lifecycle behavior without blocking the browser event loop.
+
+### Use a separate confirmation presentation within the existing prompt owner
+
+The interaction controller will render an accessible modal confirmation with the effective action name, concise irreversible-action wording, and explicit Confirm and Cancel controls.
+The confirm control receives a destructive semantic class and stable test id.
+The existing controller remains the sole owner of action prompts and invocation state.
+
+Alternative considered: nest a second dialog over the parameter prompt.
+Replacing the prompt presentation avoids stacked modal focus and accessibility ambiguity while retained state allows returning to parameters.
+
+### Restore Petclinic's visit-count veto through member support
+
+`PetOwner.disableDelete()` will query `VisitRepository.findByPetOwner(this)` and return the pinned original reason `This owner has N visits` when the count is nonzero.
+Owners without visits remain eligible, so their delete action demonstrates confirmation and can remove the owner and cascade-owned pets without referential-integrity failure.
+This reproduces the original observable policy without importing obsolete application event infrastructure.
+The action description will no longer claim that visits are deleted.
+
+Alternative considered: subscribe to the delete domain event exactly as the pinned application did.
+The current compact sample can express the same authoritative disable semantic with conventional member support and less ported infrastructure.
+Cascade-deleting visits was rejected because the selected policy is the original veto behavior.
+
+## Risks / Trade-offs
+
+- [Risk] A compatible older GraphQL schema lacks `areYouSure`, so confirmation cannot be inferred safely. → Select the field only after introspection and preserve existing compatibility behavior when absent.
+- [Risk] Re-rendering between parameter and confirmation views can lose values or focus. → Retain one immutable prompt state, test decline and confirmation paths, and restore focus according to parameterized versus parameterless origin.
+- [Risk] Double activation could invoke twice. → Gate confirm while invoking and retain generation checks around invocation.
+- [Risk] Petclinic fixture owners all have visits, leaving no successful delete example. → Browser acceptance creates or selects a disposable owner without visits before testing confirmation and successful deletion.
+
+## Migration Plan
+
+1. Add and test the optional rich GraphQL metadata field.
+2. Consume the field conditionally in object and service action state reads.
+3. Add confirmation state, markup, behavior, styling, and foundation tests.
+4. Replace Petclinic's fixture-specific disable reason with the visit-count veto and update its description and tests.
+5. Exercise disabled fixture deletion and confirmed disposable deletion in integration and browser verification.
+
+Rollback removes the optional field selection and confirmation state while leaving existing GraphQL documents compatible.
+
+## Open Questions
+
+None.
diff --git a/openspec/changes/confirm-destructive-actions/proposal.md b/openspec/changes/confirm-destructive-actions/proposal.md
new file mode 100644
index 00000000000..ed81b0c4497
--- /dev/null
+++ b/openspec/changes/confirm-destructive-actions/proposal.md
@@ -0,0 +1,29 @@
+## Why
+
+Actions declared with `NON_IDEMPOTENT_ARE_YOU_SURE` currently reach the Web Components interaction controller without their destructive confirmation semantic, so the standard controller can invoke them immediately.
+Petclinic also advertises owner deletion while relying on a fixture-specific disable rule rather than the pinned original application's visit-based veto.
+
+## What Changes
+
+- Expose a side-effect-free `areYouSure` flag for action wrappers through shared rich GraphQL member metadata.
+- Carry that canonical flag through object and service action presentation without allowing HTML presentation overrides to weaken it.
+- Require an accessible explicit confirmation before invoking an are-you-sure action, after successful parameter validation for parameterized actions and directly after activation for parameterless actions.
+- Preserve cancellation, focus restoration, disabled-action gating, stale-response protection, and authoritative GraphQL validation and invocation behavior.
+- Demonstrate the behavior with Petclinic owner deletion while restoring the pinned original policy that disables Delete when related visits exist.
+
+## Capabilities
+
+### New Capabilities
+
+None.
+
+### Modified Capabilities
+
+- `rich-graphql-member-metadata`: Expose canonical are-you-sure action semantics as narrow local metadata.
+- `domain-web-components`: Require the standard interaction controller to confirm destructive action invocation accessibly at the correct point in parameterless and parameterized flows.
+- `generic-htmx-web-component-viewer`: Restore Petclinic's original visit-based owner-deletion veto and verify the destructive confirmation journey.
+
+## Impact
+
+The change affects rich GraphQL metadata generation, Web Components action reads and interaction state, shared action-prompt markup and styling, foundation tests, Petclinic domain behavior, integration tests, and Playwright acceptance.
+The metadata addition is backward compatible because existing GraphQL documents and response shapes remain unchanged unless clients select the new field.
diff --git a/openspec/changes/confirm-destructive-actions/specs/domain-web-components/spec.md b/openspec/changes/confirm-destructive-actions/specs/domain-web-components/spec.md
new file mode 100644
index 00000000000..76da707f9b1
--- /dev/null
+++ b/openspec/changes/confirm-destructive-actions/specs/domain-web-components/spec.md
@@ -0,0 +1,46 @@
+## ADDED Requirements
+
+### Requirement: Canonical are-you-sure action confirmation
+The standard interaction controller SHALL require explicit accessible confirmation before invoking an action whose canonical rich GraphQL metadata reports `areYouSure` as true.
+HTML-authored component presentation MUST NOT suppress, fabricate, or override that canonical requirement.
+
+#### Scenario: Parameterless action requires confirmation
+- **WHEN** an enabled parameterless action with `areYouSure` metadata is activated
+- **THEN** the controller presents confirmation before invoking the action
+- **AND** the action is invoked only after the user explicitly confirms
+
+#### Scenario: Parameterized action requires confirmation
+- **WHEN** an action with `areYouSure` metadata has parameters
+- **AND** the user submits values that pass authoritative parameter preparation and whole-action validation
+- **THEN** the controller presents confirmation with the validated pending values retained
+- **AND** invokes only after the user explicitly confirms
+
+#### Scenario: Parameterized confirmation is declined
+- **WHEN** a user cancels or presses Escape from confirmation for a parameterized action
+- **THEN** the controller returns to the parameter prompt with pending values intact
+- **AND** does not invoke the action
+
+#### Scenario: Parameterless confirmation is declined
+- **WHEN** a user cancels or presses Escape from confirmation for a parameterless action
+- **THEN** the controller closes the prompt without invocation
+- **AND** restores focus to the originating action affordance
+
+#### Scenario: Ordinary action is activated
+- **WHEN** an enabled action's canonical metadata reports `areYouSure` as false or the compatible schema does not expose the field
+- **THEN** the controller retains the established parameterless or parameterized interaction flow
+- **AND** does not infer confirmation from presentation or operation placement
+
+#### Scenario: Confirmation is presented accessibly
+- **WHEN** an action enters confirmation state
+- **THEN** one controller-owned modal dialog identifies the effective action name and destructive decision
+- **AND** exposes keyboard-operable Confirm and Cancel controls with deterministic focus and stable automation hooks
+
+#### Scenario: Confirmation is activated repeatedly
+- **WHEN** the user confirms an action and invocation begins
+- **THEN** confirmation controls are gated against repeated activation
+- **AND** the controller invokes the action at most once for that prompt generation
+
+#### Scenario: Action is disabled
+- **WHEN** authoritative action state supplies a disabled reason
+- **THEN** activation remains unavailable and the reason remains accessible
+- **AND** no confirmation prompt opens
diff --git a/openspec/changes/confirm-destructive-actions/specs/generic-htmx-web-component-viewer/spec.md b/openspec/changes/confirm-destructive-actions/specs/generic-htmx-web-component-viewer/spec.md
new file mode 100644
index 00000000000..c32fdde5097
--- /dev/null
+++ b/openspec/changes/confirm-destructive-actions/specs/generic-htmx-web-component-viewer/spec.md
@@ -0,0 +1,37 @@
+## ADDED Requirements
+
+### Requirement: Faithful Petclinic owner deletion policy
+The Petclinic sample SHALL retain the pinned original application's observable owner-deletion policy while demonstrating standard are-you-sure confirmation through the HTMX Web Components viewer.
+
+#### Scenario: Owner has related visits
+- **WHEN** a Petclinic owner has one or more related visits
+- **THEN** Delete is disabled with the reason `This owner has N visits` using the authoritative count
+- **AND** the viewer does not offer confirmation or attempt deletion
+
+#### Scenario: Owner has no related visits
+- **WHEN** a Petclinic owner has no related visits
+- **THEN** Delete remains enabled with canonical are-you-sure metadata
+- **AND** its description does not claim that related visits will be deleted
+
+#### Scenario: Eligible deletion is cancelled
+- **WHEN** a user activates Delete for an eligible disposable owner and declines confirmation
+- **THEN** the owner remains available on its canonical route
+- **AND** no delete mutation is issued
+
+#### Scenario: Eligible deletion is confirmed
+- **WHEN** a user activates Delete for an eligible disposable owner and explicitly confirms
+- **THEN** the action completes through the established GraphQL mutation and void-result policy
+- **AND** the deleted object route returns to the configured home route after post-action refresh establishes that the owner no longer exists
+
+### Requirement: Petclinic destructive-action regression coverage
+Petclinic integration and opt-in browser acceptance SHALL verify canonical confirmation metadata, visit-based disable behavior, cancellation, successful confirmed deletion, focus, routing, and error monitoring.
+
+#### Scenario: Rich GraphQL delete state is queried
+- **WHEN** integration coverage reads Delete for a fixture owner with visits and an eligible owner without visits
+- **THEN** both expose `areYouSure` as true
+- **AND** only the fixture owner exposes the visit-count disabled reason
+
+#### Scenario: Browser exercises destructive deletion
+- **WHEN** browser automation cancels one eligible deletion and confirms another
+- **THEN** focus and route outcomes follow the standard interaction and void-action recovery contracts
+- **AND** no unexpected console, page, resource, GraphQL, or referential-integrity failure occurs
diff --git a/openspec/changes/confirm-destructive-actions/specs/rich-graphql-member-metadata/spec.md b/openspec/changes/confirm-destructive-actions/specs/rich-graphql-member-metadata/spec.md
new file mode 100644
index 00000000000..29e5c33e52d
--- /dev/null
+++ b/openspec/changes/confirm-destructive-actions/specs/rich-graphql-member-metadata/spec.md
@@ -0,0 +1,22 @@
+## ADDED Requirements
+
+### Requirement: Static action confirmation metadata
+The shared rich member metadata object SHALL expose nullable `areYouSure` metadata derived from canonical action semantics without invoking domain behavior or exposing the complete action-semantics model.
+
+#### Scenario: Action requires confirmation
+- **WHEN** an action's canonical semantics have are-you-sure behavior
+- **THEN** `areYouSure` returns true
+
+#### Scenario: Action does not require confirmation
+- **WHEN** an action's canonical semantics do not have are-you-sure behavior
+- **THEN** `areYouSure` returns false
+- **AND** no confirmation requirement is inferred from the action's name, icon, description, or mutation placement
+
+#### Scenario: Metadata belongs to another wrapper
+- **WHEN** property, collection, or action-parameter metadata is requested
+- **THEN** `areYouSure` returns null
+- **AND** metadata resolution does not invoke domain-object methods
+
+#### Scenario: Existing client omits confirmation metadata
+- **WHEN** an existing GraphQL document does not select `areYouSure`
+- **THEN** its operation and response shape remain unchanged
diff --git a/openspec/changes/confirm-destructive-actions/tasks.md b/openspec/changes/confirm-destructive-actions/tasks.md
new file mode 100644
index 00000000000..eba759942d9
--- /dev/null
+++ b/openspec/changes/confirm-destructive-actions/tasks.md
@@ -0,0 +1,25 @@
+## 1. Rich GraphQL Metadata
+
+- [ ] 1.1 Add nullable `areYouSure` to shared rich member metadata using canonical action semantics and null for non-action wrappers.
+- [ ] 1.2 Extend GraphQL model and Petclinic integration tests for true, false, null, and backward-compatible action metadata reads.
+
+## 2. Web Components Confirmation Flow
+
+- [ ] 2.1 Select optional `areYouSure` metadata for object and service action state and carry strict canonical confirmation presentation in action-request events.
+- [ ] 2.2 Add confirming interaction state and invocation gating for parameterless and successfully validated parameterized actions.
+- [ ] 2.3 Render and style the accessible confirmation dialog with deterministic focus, Confirm, Cancel, Escape, decline, and repeated-activation behavior.
+- [ ] 2.4 Add foundation unit tests covering canonical metadata compatibility, ordinary actions, both confirmation entry paths, retained parameter values, cancellation, focus, stale generations, and at-most-once invocation.
+- [ ] 2.5 Document the standard are-you-sure behavior and stable confirmation hooks in foundation usage guidance.
+
+## 3. Petclinic Demonstration
+
+- [ ] 3.1 Replace the fixture-specific Delete disable rule with the authoritative visit-count veto and correct the action description.
+- [ ] 3.2 Extend Petclinic integration tests for visit-based disabled state, enabled no-visit state, and canonical confirmation metadata.
+- [ ] 3.3 Extend opt-in Playwright coverage to cancel and confirm deletion of disposable eligible owners while checking focus, route recovery, and browser errors.
+
+## 4. Verification
+
+- [ ] 4.1 Run focused GraphQL model, foundation, and Petclinic Maven tests.
+- [ ] 4.2 Run the foundation direct Node test suite and compile Playwright tests.
+- [ ] 4.3 Run the focused Petclinic Playwright destructive-action journey with an available browser.
+- [ ] 4.4 Run strict OpenSpec validation and record all applicable checks passing.