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/09/02 04:04:09 UTC

(causeway) 03/03: docs(openspec): plan action result presentation styles

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 6f44d1a2f1e8cfff7a26f53f73435db1b93fa792
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Sep 2 05:56:11 2026 +0200

    docs(openspec): plan action result presentation styles
---
 .../.openspec.yaml                                 |   2 +
 .../design.md                                      | 105 +++++++++++++++++++++
 .../proposal.md                                    |  30 ++++++
 .../specs/domain-web-components/spec.md            |  62 ++++++++++++
 .../generic-htmx-web-component-viewer/spec.md      |  81 ++++++++++++++++
 .../add-action-result-presentation-styles/tasks.md |  31 ++++++
 6 files changed, 311 insertions(+)

diff --git a/openspec/changes/add-action-result-presentation-styles/.openspec.yaml b/openspec/changes/add-action-result-presentation-styles/.openspec.yaml
new file mode 100644
index 00000000000..032461ff9fa
--- /dev/null
+++ b/openspec/changes/add-action-result-presentation-styles/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-09-02
diff --git a/openspec/changes/add-action-result-presentation-styles/design.md b/openspec/changes/add-action-result-presentation-styles/design.md
new file mode 100644
index 00000000000..14b8bbc47c9
--- /dev/null
+++ b/openspec/changes/add-action-result-presentation-styles/design.md
@@ -0,0 +1,105 @@
+## Context
+
+`<cw-action-results>` is currently a passive light-DOM region whose host replaces direct result children and owns routing, dismissal, navigation, and announcements.
+The HTMX viewer snapshots one unique route outlet at interaction start and otherwise uses the stable shell outlet.
+Inline results work well near the invoking action but can displace long pages, while some applications need a temporary modal or right-side result surface.
+The new surface choice must not affect action metadata, invocation, result normalization, outlet selection, canonical object navigation, or application result overrides.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Add declarative `INLINE`, `DIALOG`, and `SIDEBAR` presentation styles to the public result outlet.
+- Keep `INLINE` fully backward compatible.
+- Provide accessible modal focus containment and a responsive non-modal sidebar.
+- Preserve passive outlet ownership and the existing host result lifecycle.
+- Keep mounted result nodes in light DOM and retain semantic component behavior.
+- Reuse shared focus, Escape, overlay, and responsive conventions where practical.
+
+**Non-Goals:**
+
+- Do not add presentation metadata to GraphQL actions or action declarations.
+- Do not let a result style change invocation, validation, confirmation, projection, routing, or navigation.
+- Do not make the outlet subscribe globally to action events or resolve collection fragments.
+- Do not persist result surfaces across unrelated routes or browser sessions.
+- Do not introduce arbitrary placement coordinates, resizable panels, or multiple simultaneous result surfaces.
+
+## Decisions
+
+### The result outlet owns a presentation-only attribute
+
+`<cw-action-results presentation-style="INLINE">` is the public declaration.
+The attribute accepts `INLINE`, `DIALOG`, and `SIDEBAR` case-insensitively, normalizes through a reflected `presentationStyle` property, and falls back to `INLINE` for an absent, blank, or unsupported value.
+The name is `presentation-style` rather than `prompt-style` because the outlet presents completed outcomes rather than parameter prompts.
+
+Alternative considered: configure style on `<cw-action>`.
+That would couple reusable result placement to every invoking action and would conflict with the existing destination-outlet snapshot, so the resolved outlet remains authoritative for surface choice.
+
+### Inline remains the direct-child compatibility mode
+
+`INLINE` keeps the current direct result children, region semantics, automatic reveal, compact dismiss alignment, replacement, and clear behavior.
+Existing markup and hosts require no migration.
+
+Alternative considered: always wrap inline content in a surface element.
+That would break established selectors, layout hooks, and direct-child tests without adding value.
+
+### Dialog is modal and sidebar is non-modal
+
+`DIALOG` uses a bounded labelled modal surface with a backdrop, initial focus, Tab containment, Escape dismissal, scrollable content, and originating-control focus restoration.
+`SIDEBAR` uses a fixed panel at the viewport inline end with complementary region semantics, no backdrop, no page inerting, and no forced initial focus.
+The sidebar remains keyboard reachable through normal document order, supports Escape while focus is within it, and uses the same explicit dismiss control.
+
+Alternative considered: make both dialog and sidebar modal, matching parameter prompts.
+Completed results are informational and often benefit from comparison with the current object, so a non-modal sidebar is less disruptive while the dialog remains available when attention must be constrained.
+
+### Presentation nodes remain light-DOM and host-owned
+
+For styled surfaces, the outlet creates only bounded structural surface nodes and places the host-supplied presentation nodes inside them.
+The outlet does not clone, reinterpret, or independently render result data.
+Replacement retires the previous surface and transient toolkit state before mounting the next one.
+`clear()` closes any open surface, removes structural nodes, returns the outlet to hidden-empty state, and restores focus when an eligible connected origin remains.
+
+Alternative considered: portal result nodes to `document.body`.
+Keeping surfaces under the outlet preserves route ownership, disconnection safety, component queries, and deterministic cleanup.
+
+### The host supplies focus restoration context additively
+
+The outlet exposes a bounded presentation context API through which the owning host can identify the originating action control before mounting a result.
+If no valid connected origin is supplied, dismissal closes safely without speculative focus movement.
+The existing HTMX result-dismiss lifecycle supplies the same action origin for inline, dialog, and sidebar styles.
+
+Alternative considered: capture `document.activeElement` during replacement.
+Asynchronous completion and prompt closure make that value unreliable, especially for service-menu actions.
+
+### Responsive and layering behavior is deterministic
+
+Dialog dimensions are clamped to the visual viewport and its body scrolls internally.
+Sidebar width is bounded by a documented token and becomes viewport-width on narrow screens without horizontal document overflow.
+Both styled surfaces use established z-index tokens and must coexist safely with action prompts, confirmation dialogs, menus, and route replacement.
+Reduced-motion preference disables optional transitions.
+
+### HTMX routing remains unchanged
+
+The HTMX viewer resolves and snapshots the destination outlet exactly as today.
+After a successful non-navigating result, it supplies origin context and result nodes to that outlet; the outlet's style controls only the visual surface.
+Application result claims, object-valued navigation, duplicate-outlet fallback, disconnected-outlet fallback, void refresh preservation, announcements, and collection projection remain unchanged.
+Automatic viewport reveal applies only to `INLINE`; opening a dialog or sidebar does not scroll the underlying route.
+
+## Risks / Trade-offs
+
+- [Risk] Modal focus handling could conflict with an action prompt or confirmation that has not fully closed. → Open the result surface only after the interaction controller publishes completion and add topmost-surface browser tests.
+- [Risk] A non-modal sidebar can cover narrow content. → Bound its width, switch to viewport width at the narrow breakpoint, provide an always-visible dismiss control, and prohibit horizontal overflow.
+- [Risk] Wrapping nodes for styled modes can invalidate direct-child assumptions. → Preserve direct children for `INLINE`, document styled-mode structure as host-owned, and test node identity rather than fixed wrapper selectors.
+- [Risk] Route replacement could leave an overlay visible or focus a stale origin. → Keep surfaces beneath the route-owned outlet, close on disconnection, and restore focus only to a connected eligible target.
+- [Risk] Native dialog and browser focus behavior can vary. → Qualify Chromium behavior under Vaadin and native component-toolkit policies and retain explicit focus containment and cleanup tests.
+
+## Migration Plan
+
+Existing outlets remain `INLINE` because the attribute defaults safely.
+Applications opt in by adding `presentation-style="DIALOG"` or `presentation-style="SIDEBAR"` to an authored route outlet or stable shell outlet.
+Rollback consists of removing the attribute; no server, GraphQL, or result-resource migration is required.
+
+## Open Questions
+
+None for the initial implementation.
+Resizable sidebars, persistent user preference, and action-specific destination selection remain possible later enhancements.
diff --git a/openspec/changes/add-action-result-presentation-styles/proposal.md b/openspec/changes/add-action-result-presentation-styles/proposal.md
new file mode 100644
index 00000000000..5b289060fff
--- /dev/null
+++ b/openspec/changes/add-action-result-presentation-styles/proposal.md
@@ -0,0 +1,30 @@
+## Why
+
+Action results are currently limited to inline placement, which can displace a long object page and force users to move between the invoking action and its outcome.
+Applications need a declarative, accessible way to choose inline, modal-dialog, or right-sidebar result presentation without changing action invocation or result authority.
+
+## What Changes
+
+- Add a `presentation-style` attribute to `<cw-action-results>` with case-insensitive `INLINE`, `DIALOG`, and `SIDEBAR` values.
+- Retain `INLINE` as the default and preserve the current in-page result lifecycle.
+- Present `DIALOG` results in an accessible modal surface with bounded focus, Escape and dismiss handling, and focus restoration.
+- Present `SIDEBAR` results in a responsive right-side panel with accessible naming, dismissal, and focus restoration.
+- Keep the outlet passive: it does not invoke actions, subscribe globally, choose result destinations, navigate, or reinterpret result data.
+- Extend HTMX result routing so the already-resolved outlet controls only the surface used for successful non-navigating results.
+- Add Foundation and Petclinic browser qualification for all three styles under Vaadin and native toolkit policies.
+
+## Capabilities
+
+### New Capabilities
+
+None.
+
+### Modified Capabilities
+
+- `domain-web-components`: Extend the passive action-result outlet contract with declarative inline, dialog, and sidebar presentation surfaces and their accessibility lifecycle.
+- `generic-htmx-web-component-viewer`: Honor the resolved outlet presentation style while preserving result routing, ownership, replacement, dismissal, navigation, and responsive behavior.
+
+## Impact
+
+The change affects the public `<cw-action-results>` attribute contract, Foundation component styles and lifecycle APIs, HTMX result presentation policy, Petclinic page declarations, usage documentation, and browser accessibility and responsive tests.
+It introduces no GraphQL schema, domain action, invocation, result payload, or server resource changes.
diff --git a/openspec/changes/add-action-result-presentation-styles/specs/domain-web-components/spec.md b/openspec/changes/add-action-result-presentation-styles/specs/domain-web-components/spec.md
new file mode 100644
index 00000000000..a583a55603b
--- /dev/null
+++ b/openspec/changes/add-action-result-presentation-styles/specs/domain-web-components/spec.md
@@ -0,0 +1,62 @@
+## MODIFIED Requirements
+
+### Requirement: Generic action-result outlet
+The component library SHALL provide `<cw-action-results>` as a passive accessible outlet for host-owned action-result presentation with declarative `INLINE`, `DIALOG`, and `SIDEBAR` surfaces.
+The outlet MUST NOT globally subscribe to action results, invoke actions, choose application policy, navigate, resolve fragments, or independently construct domain presentation.
+
+#### Scenario: Outlet is empty
+- **WHEN** no current host-owned result presentation is mounted
+- **THEN** `<cw-action-results>` contributes no misleading result content, focus target, occupied layout space, dialog, or sidebar
+- **AND** it remains available as a semantic placement boundary
+
+#### Scenario: Presentation style is absent or invalid
+- **WHEN** `presentation-style` is absent, blank, or unsupported
+- **THEN** the outlet normalizes to `INLINE`
+- **AND** existing direct-child layout, replacement, clear, and accessibility behavior remains compatible
+
+#### Scenario: Host mounts an inline result
+- **WHEN** the owning viewer places a current scalar, void-status, or standalone collection presentation into an `INLINE` outlet
+- **THEN** the outlet exposes an accessible in-page result-region relationship for that content
+- **AND** optional host controls remain compactly aligned with the result presentation rather than stretching across its width
+- **AND** result rendering, announcements, links, values, and lifecycle remain owned by their established host and semantic components
+
+#### Scenario: Host mounts a dialog result
+- **WHEN** the owning viewer places a current presentation into a `DIALOG` outlet
+- **THEN** the outlet opens one bounded labelled modal surface with a backdrop and internally scrollable content
+- **AND** presentation nodes remain identity-stable light-DOM descendants of the route-owned outlet
+
+#### Scenario: Keyboard user operates a dialog result
+- **WHEN** a dialog result is open
+- **THEN** initial focus enters the modal surface, Tab and Shift+Tab remain contained, and Escape requests dismissal
+- **AND** dismissal restores focus to the connected eligible originating control supplied by the host
+
+#### Scenario: Host mounts a sidebar result
+- **WHEN** the owning viewer places a current presentation into a `SIDEBAR` outlet
+- **THEN** the outlet opens one labelled non-modal panel at the viewport inline end without making the underlying page inert
+- **AND** the panel is reachable through ordinary keyboard navigation and its content scrolls without horizontal document overflow
+
+#### Scenario: Keyboard user operates a sidebar result
+- **WHEN** focus is within an open sidebar result
+- **THEN** Escape requests dismissal without trapping ordinary Tab navigation
+- **AND** explicit dismissal restores focus to the connected eligible originating control supplied by the host
+
+#### Scenario: Narrow viewport or reduced motion applies
+- **WHEN** a dialog or sidebar result is presented on a narrow viewport or under reduced-motion preference
+- **THEN** content, heading, and dismissal remain visible and operable within the visual viewport
+- **AND** the surface introduces neither horizontal document overflow nor required animation
+
+#### Scenario: Result is replaced
+- **WHEN** the host accepts a newer successful result generation
+- **THEN** prior result nodes, surface structure, focus containment, and transient toolkit state are retired before the newer presentation becomes current
+- **AND** stale nodes cannot remain interactive or overwrite the replacement
+
+#### Scenario: Result is cleared
+- **WHEN** the host clears or dismisses the current result
+- **THEN** any open dialog or sidebar closes and the outlet returns to hidden-empty state
+- **AND** focus restoration occurs only for a connected eligible origin and never targets stale route content
+
+#### Scenario: Outlet disconnects
+- **WHEN** route replacement or page lifecycle disconnects the outlet
+- **THEN** its mounted presentation and styled surface cannot receive later asynchronous state
+- **AND** modal state, backdrop, focus containment, and document-overflow effects are fully retired
+- **AND** the viewer remains free to use its current deterministic fallback destination
diff --git a/openspec/changes/add-action-result-presentation-styles/specs/generic-htmx-web-component-viewer/spec.md b/openspec/changes/add-action-result-presentation-styles/specs/generic-htmx-web-component-viewer/spec.md
new file mode 100644
index 00000000000..d952fc0c69c
--- /dev/null
+++ b/openspec/changes/add-action-result-presentation-styles/specs/generic-htmx-web-component-viewer/spec.md
@@ -0,0 +1,81 @@
+## MODIFIED Requirements
+
+### Requirement: HTMX action-result outlet resolution
+The generic HTMX viewer SHALL prefer one unique `<cw-action-results>` in the active route page for successful non-navigating action-result presentation and SHALL retain its stable shell result region as fallback.
+Result routing MUST remain host-owned and generation-safe, while the resolved outlet's normalized presentation style controls only the visual surface.
+
+#### Scenario: Active page has one outlet
+- **WHEN** an action interaction begins while exactly one connected action-result outlet belongs to the active route page
+- **THEN** the viewer snapshots that outlet and route generation as the preferred destination
+- **AND** a later successful scalar, void-status, or collection presentation is mounted there only while the destination remains current
+
+#### Scenario: Inline result is outside the viewport
+- **WHEN** the current host mounts a successful non-navigating result outside the visible viewport into an `INLINE` outlet
+- **THEN** the viewer scrolls the result outlet into view without moving keyboard focus
+- **AND** reduced-motion preference disables animated scrolling
+
+#### Scenario: Dialog or sidebar result is mounted
+- **WHEN** the resolved outlet normalizes to `DIALOG` or `SIDEBAR`
+- **THEN** the viewer supplies the originating action focus target and mounts the unchanged host-owned result presentation into that outlet
+- **AND** opening the styled surface does not scroll the underlying route or alter result data
+
+#### Scenario: Styled result is dismissed
+- **WHEN** a dialog or sidebar dismiss control or supported Escape lifecycle requests dismissal
+- **THEN** the viewer clears the current result through the established destination lifecycle
+- **AND** focus returns to the eligible originating object-action or service-action control when it remains connected
+
+#### Scenario: Active page has no outlet
+- **WHEN** no applicable page outlet exists
+- **THEN** successful non-navigating results use the stable shell result region and its normalized presentation style
+- **AND** existing applications require no markup migration
+
+#### Scenario: Active page has duplicate outlets
+- **WHEN** more than one equally applicable outlet exists in the active route page
+- **THEN** the viewer exposes a bounded ambiguity diagnostic and uses the stable shell fallback
+- **AND** DOM order is not used to choose an arbitrary owner or presentation style
+
+#### Scenario: Captured outlet disconnects
+- **WHEN** route replacement disconnects the captured outlet before an asynchronous result completes
+- **THEN** stale work cannot mount into that outlet, reopen its styled surface, or use a different page's outlet
+- **AND** the still-current host policy may use the stable shell fallback without transferring stale node or focus state
+
+#### Scenario: Application claims the result
+- **WHEN** configured `causewayHtmxPolicy.handleResult` claims a result
+- **THEN** neither page outlet nor shell fallback is modified or opened by default policy
+- **AND** the application receives the authoritative semantic result detail and additive resolved-presentation snapshot
+
+#### Scenario: Object result is returned
+- **WHEN** a successful result advertises one navigable object identity
+- **THEN** established canonical object navigation remains authoritative
+- **AND** no inline, dialog, or sidebar result surface is mounted
+
+#### Scenario: Void refresh preserves status
+- **WHEN** void-result policy refreshes the active route while preserving current result status
+- **THEN** the still-current status is rehomed to the unique equivalent outlet in the refreshed page or stable shell fallback
+- **AND** obsolete outlet, route, surface, or focus generations cannot reclaim it
+
+## ADDED Requirements
+
+### Requirement: Action-result presentation style qualification
+The Petclinic browser acceptance application SHALL exercise `INLINE`, `DIALOG`, and `SIDEBAR` action-result outlets under default Vaadin and explicit native component-toolkit policies.
+Unexpected invocation, routing, focus, Escape, backdrop, overflow, responsive, replacement, dismissal, announcement, console, page, or external-request failures MUST fail qualification.
+
+#### Scenario: Inline result is qualified
+- **WHEN** a deterministic Petclinic action returns a non-navigating result to an `INLINE` outlet
+- **THEN** current compact layout, sticky-header-aware reveal, links, values, announcements, replacement, and dismissal remain operable
+- **AND** the result surface introduces no modal or sidebar behavior
+
+#### Scenario: Dialog result is qualified
+- **WHEN** a deterministic Petclinic action returns a result to a `DIALOG` outlet
+- **THEN** labelled modal semantics, backdrop, initial focus, Tab containment, Escape, explicit dismissal, replacement, and origin focus restoration are verified
+- **AND** route content cannot remain interactively exposed through stale modal state
+
+#### Scenario: Sidebar result is qualified
+- **WHEN** a deterministic Petclinic action returns a result to a `SIDEBAR` outlet
+- **THEN** right-side placement, non-modal page access, ordinary Tab order, Escape while focused within, explicit dismissal, replacement, and origin focus restoration are verified
+- **AND** wide and narrow viewports retain bounded internal scrolling with no horizontal document overflow
+
+#### Scenario: Styled result respects established ownership
+- **WHEN** duplicate, disconnected, superseded, application-claimed, object-valued, or void-refresh result paths are exercised
+- **THEN** established outlet fallback, canonical navigation, application ownership, preservation, and stale-generation behavior remain authoritative
+- **AND** presentation style cannot select a destination or reinterpret the result
diff --git a/openspec/changes/add-action-result-presentation-styles/tasks.md b/openspec/changes/add-action-result-presentation-styles/tasks.md
new file mode 100644
index 00000000000..181de1756c8
--- /dev/null
+++ b/openspec/changes/add-action-result-presentation-styles/tasks.md
@@ -0,0 +1,31 @@
+## 1. Public Outlet Contract
+
+- [ ] 1.1 Add bounded case-insensitive `presentation-style` attribute normalization and reflected `presentationStyle` property for `INLINE`, `DIALOG`, and `SIDEBAR`.
+- [ ] 1.2 Add a host-supplied presentation context API for connected originating-focus targets without changing result ownership.
+- [ ] 1.3 Extend component contracts, exports, usage documentation, and CSS variables for styled result surfaces.
+
+## 2. Surface Lifecycle
+
+- [ ] 2.1 Preserve current direct-child region, compact controls, sticky-header-aware reveal, replacement, and clear behavior for `INLINE`.
+- [ ] 2.2 Implement a bounded labelled modal dialog surface with backdrop, initial focus, Tab containment, Escape dismissal, and focus restoration.
+- [ ] 2.3 Implement a labelled non-modal inline-end sidebar with ordinary Tab navigation, focused Escape dismissal, bounded scrolling, and focus restoration.
+- [ ] 2.4 Make replacement, clear, disconnection, style changes, and stale generations retire surface structure, focus handling, backdrop, and overflow effects deterministically.
+- [ ] 2.5 Add narrow-viewport, forced-colors, and reduced-motion styling without horizontal document overflow.
+
+## 3. HTMX Host Integration
+
+- [ ] 3.1 Supply the resolved outlet with the originating object-action or service-action focus target before mounting successful non-navigating results.
+- [ ] 3.2 Route explicit and Escape dismissal through the existing host-owned result lifecycle for all presentation styles.
+- [ ] 3.3 Restrict automatic viewport reveal to inline results while preserving outlet snapshotting, shell fallback, application claims, object navigation, void refresh, and announcements.
+
+## 4. Foundation Verification
+
+- [ ] 4.1 Add unit tests for attribute normalization, property reflection, inline compatibility, node identity, replacement, clear, style changes, and disconnection.
+- [ ] 4.2 Add DOM tests for dialog semantics, focus entry and containment, Escape, explicit dismissal, and connected-origin focus restoration.
+- [ ] 4.3 Add DOM tests for non-modal sidebar semantics, ordinary Tab behavior, focused Escape, responsive bounds, reduced motion, and cleanup.
+
+## 5. Petclinic Qualification
+
+- [ ] 5.1 Add deterministic Petclinic outlet declarations and journeys demonstrating inline, dialog, and sidebar collection results without changing invocation projection.
+- [ ] 5.2 Verify object-action and service-action origins, replacement, dismissal, focus restoration, stale/disconnected fallback, object navigation, and void refresh under Vaadin and native toolkit policies.
+- [ ] 5.3 Run Foundation Node and Maven suites, HTMX and Petclinic integration tests, browser accessibility/console/network/focus/overflow audits, strict OpenSpec validation, and `git diff --check` with Java 21 where required.