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/23 05:40:54 UTC

(causeway) branch CAUSEWAY-4058 updated (473d2c218c4 -> e7961a5bdfc)

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a change to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git


    from 473d2c218c4 CAUSEWAY-4058: prunes archived OpenSpec history
     new f5319d1c682 CAUSEWAY-4058: proposes Wicket render observations
     new 0b93bd8662a CAUSEWAY-4058: adds Wicket region render observations
     new e7961a5bdfc CAUSEWAY-4058: archives Wicket render observations

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 adoc/micrometer-tracing-operations.adoc            |  31 +-
 .../.openspec.yaml                                 |   2 +
 .../design.md                                      | 118 ++++++
 .../proposal.md                                    |  32 ++
 .../specs/wicket-region-render-observation/spec.md |  98 +++++
 .../tasks.md                                       |  30 ++
 .../validation.md                                  |  33 ++
 .../add-wicket-collection-render-detail-spans.md   |  48 +++
 .../add-wicket-region-preparation-spans.md         |  49 +++
 .../add-wicket-slow-row-diagnostics.md             |  49 +++
 .../configure-wicket-observation-detail.md         |  60 +++
 .../wicket-render-observability-roadmap.md         | 113 ++++++
 .../specs/wicket-region-render-observation/spec.md | 102 +++++
 .../observation/WicketRenderObservationTest.java   | 423 +++++++++++++++++++++
 viewers/wicket/ui/src/main/java/module-info.java   |   1 +
 .../wicket/ui/CausewayModuleViewerWicketUi.java    |   1 +
 .../entity/collection/EntityCollectionPanel.java   |   7 +
 .../components/entity/fieldset/PropertyGroup.java  |   6 +
 .../ui/components/scalars/ScalarPanelAbstract.java |   5 +
 .../components/widgets/actionlink/ActionLink.java  |   4 +
 .../WicketRenderObservationBehavior.java           | 109 ++++++
 .../WicketRenderObservationDescriptor.java         | 111 ++++++
 .../observation/WicketRenderObservationPolicy.java |  61 +++
 .../WicketRenderObservationTracker.java            | 144 +++++++
 .../viewer/wicket/ui/pages/entity/EntityPage.java  |   5 +
 .../integration/WebRequestCycleForCauseway.java    |  13 +-
 26 files changed, 1652 insertions(+), 3 deletions(-)
 create mode 100644 openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/.openspec.yaml
 create mode 100644 openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/design.md
 create mode 100644 openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/proposal.md
 create mode 100644 openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
 create mode 100644 openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/tasks.md
 create mode 100644 openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/validation.md
 create mode 100644 openspec/planned-changes/add-wicket-collection-render-detail-spans.md
 create mode 100644 openspec/planned-changes/add-wicket-region-preparation-spans.md
 create mode 100644 openspec/planned-changes/add-wicket-slow-row-diagnostics.md
 create mode 100644 openspec/planned-changes/configure-wicket-observation-detail.md
 create mode 100644 openspec/planned-changes/wicket-render-observability-roadmap.md
 create mode 100644 openspec/specs/wicket-region-render-observation/spec.md
 create mode 100644 viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTest.java
 create mode 100644 viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationBehavior.java
 create mode 100644 viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationDescriptor.java
 create mode 100644 viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationPolicy.java
 create mode 100644 viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTracker.java


(causeway) 01/03: CAUSEWAY-4058: proposes Wicket render observations

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit f5319d1c6823be7cbaa02159e9d2cdd51d361554
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Sep 22 23:27:55 2026 +0100

    CAUSEWAY-4058: proposes Wicket render observations
---
 .../add-wicket-region-render-spans/.openspec.yaml  |   2 +
 .../add-wicket-region-render-spans/design.md       | 118 +++++++++++++++++++++
 .../add-wicket-region-render-spans/proposal.md     |  32 ++++++
 .../specs/wicket-region-render-observation/spec.md |  98 +++++++++++++++++
 .../add-wicket-region-render-spans/tasks.md        |  30 ++++++
 .../add-wicket-collection-render-detail-spans.md   |  48 +++++++++
 .../add-wicket-region-preparation-spans.md         |  49 +++++++++
 .../add-wicket-slow-row-diagnostics.md             |  49 +++++++++
 .../configure-wicket-observation-detail.md         |  60 +++++++++++
 .../wicket-render-observability-roadmap.md         | 113 ++++++++++++++++++++
 10 files changed, 599 insertions(+)

diff --git a/openspec/changes/add-wicket-region-render-spans/.openspec.yaml b/openspec/changes/add-wicket-region-render-spans/.openspec.yaml
new file mode 100644
index 00000000000..1b9acb7fd02
--- /dev/null
+++ b/openspec/changes/add-wicket-region-render-spans/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-09-22
diff --git a/openspec/changes/add-wicket-region-render-spans/design.md b/openspec/changes/add-wicket-region-render-spans/design.md
new file mode 100644
index 00000000000..ac5f92c9944
--- /dev/null
+++ b/openspec/changes/add-wicket-region-render-spans/design.md
@@ -0,0 +1,118 @@
+## Context
+
+The OpenTelemetry Java agent currently creates automatic HTTP and JDBC spans, while Causeway adds `causeway.root.interaction` and `causeway.action.invocation` through `CausewayObservationIntegration`.
+Entity-page work below the root interaction is otherwise opaque even though the Wicket component tree already has stable semantic boundaries for the page, fieldsets, properties, collections, and action links.
+
+Wicket separates component construction, initialization, configuration, visibility evaluation, and markup rendering.
+This change measures actual markup rendering and the descendant work performed during that rendering; it does not claim to measure earlier preparation callbacks.
+
+Wicket pages and behaviors can be serialized between requests, Ajax requests can render only selected subtrees, and `Behavior.afterRender(...)` is not guaranteed after a rendering exception.
+The design must therefore avoid serializing active telemetry state and must provide request-end failure cleanup.
+
+The maintenance branch favors a small targeted implementation, no new dependencies, and unchanged application behavior when observation is inactive.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Make server-side entity-page rendering visible as a page span with nested spans for logical regions.
+- Preserve Wicket component-tree parentage for full-page and Ajax partial renders.
+- Use stable span names and bounded, type-level metadata.
+- Integrate through the existing Micrometer Observation bridge and Java-agent-owned trace context.
+- Close scopes correctly after successful rendering and defensively after failures.
+- Keep inactive-mode overhead small and behavior functionally unchanged.
+
+**Non-Goals:**
+
+- Measure component construction, initialization, configuration, visibility, usability, or authorization evaluation.
+- Instrument action execution, which already has `causeway.action.invocation`.
+- Create spans for collection cells, collection rows, row actions, action parameters, Bootstrap layout containers, or browser-side work.
+- Record domain values, rendered labels, object titles, bookmarks, user identities, tenancy data, or generated Wicket component paths.
+- Add an OpenTelemetry SDK, exporter, sampling policy, or third-party dependency.
+- Add observation-detail configuration or a per-request span budget in this initial change.
+
+## Decisions
+
+### Instrument semantic Wicket components rather than generic layout components
+
+The implementation will attach render observation behavior at the established semantic component boundaries represented by `EntityPage`, `PropertyGroup`, regular entity-property scalar panels, `EntityCollectionPanel`, and relevant `ActionLink` instances.
+Rows, columns, tabs, and generic component factories will not be observed because they add noise without identifying a domain region.
+
+A global rule that observes every Wicket component was rejected because it would create high span volume, unstable names, and spans for framework implementation details.
+
+### Measure actual rendering as a contiguous operation
+
+Each observation will start immediately before the selected component's actual Wicket render callback and stop immediately afterward.
+Wicket's normal recursive rendering will make descendant region observations children of the active enclosing region observation.
+
+A single observation stretching from component preparation through rendering was rejected because sibling components are prepared and rendered in different orders, causing overlapping durations and incorrect scope nesting.
+Separate preparation observations are reserved for a follow-up change if real traces show significant unattributed time.
+
+### Use one reusable serializable behavior and immutable descriptors
+
+A reusable Wicket behavior will own the common observation lifecycle, while each instrumented component will supply an immutable descriptor containing its region kind and safe identifiers.
+Active observations, scopes, integration services, and request registrations will be transient and resolved per request so page serialization cannot retain telemetry state.
+
+Explicit attachment at semantic construction points is preferred over class-name inspection by a global component-instantiation listener.
+It keeps inclusion rules visible, permits model-aware filtering, and avoids observing extension components accidentally.
+
+### Use stable observation names and bounded attributes
+
+The observation names will be:
+
+- `causeway.wicket.page.render`
+- `causeway.wicket.fieldset.render`
+- `causeway.wicket.property.render`
+- `causeway.wicket.collection.render`
+- `causeway.wicket.action.render`
+
+The span display name will remain stable rather than embedding application member identifiers.
+Applicable low-cardinality attributes will identify the logical object type and fieldset, property, collection, or action member.
+Identifiers will come from layout or metamodel definitions and will describe application structure rather than a particular object instance.
+
+The implementation will reuse the module metadata convention supplied by `CausewayObservationIntegration.withModuleName(...)` with the Wicket viewer namespace.
+
+### Bound member-level instrumentation
+
+Property observations will apply only to regular entity properties and will exclude scalar action parameters and compact/table-cell rendering.
+Action observations will apply to action links rendered in entity object-form regions and will exclude service menus and row-level collection actions.
+Collection observations will cover the entity collection region as a whole and allow automatic JDBC spans to appear beneath it when loading occurs during rendering.
+
+These rules prevent a collection table from producing a span for every cell and row action.
+
+### Make failure cleanup request-scoped
+
+Starting an observation will register its closure in request-local render state.
+Successful completion will close and unregister it.
+If normal behavior completion is skipped, the Wicket request-cycle exception or detach path will mark the active observation with the rendering failure when available and close remaining closures in reverse start order.
+Cleanup will be idempotent so normal completion followed by request detach is harmless.
+
+Storing cleanup state in global or session scope was rejected because request processing is thread-bound and Wicket sessions can outlive individual traces.
+
+### Preserve opt-in activation
+
+All observations will use `CausewayObservationIntegration`, whose registry is `ObservationRegistry.NOOP` unless the existing `observation` profile is active.
+No separate exporter or SDK configuration will be introduced.
+The instrumentation will avoid attaching or starting unnecessary active state when the integration reports no-op operation where practical.
+
+## Risks / Trade-offs
+
+- [Render spans do not explain expensive preparation or visibility evaluation] → Name them explicitly as render spans, document the limitation, and reserve preparation observations for a follow-up informed by real traces.
+- [A page with many logical members creates many spans] → Exclude table cells, row actions, parameters, and structural layout components; rely on the existing opt-in profile and agent sampling for the initial release.
+- [A render exception skips the behavior completion callback] → Track active closures in request-local state and drain them during request exception or detach handling.
+- [Wicket page serialization captures observation objects] → Keep all active telemetry and service references transient and retain only immutable safe descriptors.
+- [Extension-provided property or collection renderers differ from core components] → Attach at stable enclosing semantic boundaries and test representative alternate presentations without attempting to instrument every extension component.
+- [Some expensive work remains directly under the page or root span] → Use the trace evidence to decide whether the preparation or collection-detail follow-up is warranted.
+
+## Migration Plan
+
+No application migration is required.
+Applications without the `observation` profile continue to use the no-op registry.
+Applications with observation enabled receive the additional Wicket spans after upgrading and can suppress their export through existing agent sampling if necessary.
+Rollback consists of reverting this instrumentation; it does not change persisted Wicket page formats beyond serializable behavior metadata or any domain data.
+
+## Open Questions
+
+- Confirm the exact metamodel method used to produce a stable logical object-type identifier consistently across entity, property, collection, and action models during implementation.
+- Confirm whether Wicket's request exception callback always exposes the originating render failure before detach; otherwise cleanup will close the span without an error annotation in that fallback path.
+- Confirm through an integration trace which collection presentations perform data loading inside the observed render callback rather than during earlier preparation.
diff --git a/openspec/changes/add-wicket-region-render-spans/proposal.md b/openspec/changes/add-wicket-region-render-spans/proposal.md
new file mode 100644
index 00000000000..5020b7836ac
--- /dev/null
+++ b/openspec/changes/add-wicket-region-render-spans/proposal.md
@@ -0,0 +1,32 @@
+## Why
+
+Entity-page rendering time is currently hidden inside the broad Causeway root-interaction span, making it difficult to determine whether a slow page is dominated by a fieldset, property, collection, or action-button region.
+Now that Causeway has an opt-in Micrometer Observation bridge to the OpenTelemetry Java agent, the Wicket viewer can add bounded semantic render spans without introducing another SDK or exporter.
+
+## What Changes
+
+- Add a semantic page-render observation around server-side Wicket entity-page rendering.
+- Add nested semantic render observations for fieldsets, regular entity properties, entity collections, and relevant action buttons.
+- Emit stable observation names and bounded layout or metamodel identifiers that let operators identify slow regions without recording object values or instance identifiers.
+- Include both full-page and Ajax partial rendering when an instrumented region is rendered.
+- Exclude collection table cells, per-row actions, action parameters, browser-side rendering, and component preparation from this initial capability.
+- Preserve no-op behavior when the `observation` Spring profile is inactive and preserve the Java agent as the sole owner of the OpenTelemetry SDK, sampling, and export.
+- Record rendering failures and defensively close active observation scopes at the end of the Wicket request cycle.
+- Document the new viewer span taxonomy, privacy constraints, expected trace shape, and diagnostic limitations.
+
+## Capabilities
+
+### New Capabilities
+
+- `wicket-region-render-observation`: Server-side semantic observations for Wicket page and logical-region rendering, including naming, metadata, nesting, lifecycle safety, exclusions, and inactive-mode behavior.
+
+### Modified Capabilities
+
+None.
+
+## Impact
+
+The change affects the Wicket UI components representing entity pages, fieldsets, properties, collections, and action links, together with the Wicket viewer request-cycle integration used for defensive cleanup.
+It uses the existing `CausewayObservationIntegration` and Micrometer Observation dependencies and introduces no new telemetry SDK, exporter, or third-party dependency.
+Operational tracing documentation and focused Wicket observation tests will be added or updated.
+The feature remains opt-in through the existing `observation` profile, but an observed entity page will produce additional spans proportional to its logical regions.
diff --git a/openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md b/openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
new file mode 100644
index 00000000000..4ddc06ddea4
--- /dev/null
+++ b/openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
@@ -0,0 +1,98 @@
+## ADDED Requirements
+
+### Requirement: Entity-page render observation
+When observation is active, the Wicket viewer SHALL create one `causeway.wicket.page.render` observation for each server-side render of an entity page.
+The observation SHALL join the current Java-agent-owned trace and SHALL use the Wicket viewer module metadata convention.
+
+#### Scenario: Full entity-page render
+- **WHEN** Wicket renders an entity page during a full HTTP response with observation active
+- **THEN** one `causeway.wicket.page.render` observation covers the page's actual server-side rendering
+- **AND** the observation is a descendant of the current request or Causeway interaction span
+
+#### Scenario: Observation is inactive
+- **WHEN** Wicket renders an entity page without the `observation` profile active
+- **THEN** rendering behavior and output remain unchanged
+- **AND** no exported Causeway Wicket render span is produced
+
+### Requirement: Logical region render observations
+The Wicket viewer SHALL create nested render observations for fieldsets, regular entity properties, entity collections, and relevant entity-page action buttons that are actually rendered.
+The stable observation names SHALL be `causeway.wicket.fieldset.render`, `causeway.wicket.property.render`, `causeway.wicket.collection.render`, and `causeway.wicket.action.render` respectively.
+
+#### Scenario: Nested entity regions
+- **WHEN** an entity page renders a fieldset containing regular properties and associated action buttons
+- **THEN** the fieldset render observation is a child of the page render observation
+- **AND** each rendered property and relevant action-button observation is a child of the enclosing rendered region
+
+#### Scenario: Entity collection region
+- **WHEN** an entity collection region is rendered
+- **THEN** one `causeway.wicket.collection.render` observation covers that collection region's actual rendering
+- **AND** work initiated during that rendering inherits the collection observation as its current parent
+
+#### Scenario: Ajax partial render
+- **WHEN** an Ajax response renders an instrumented logical region without rendering the complete entity page
+- **THEN** the viewer creates an observation for each instrumented region actually rendered
+- **AND** each observation joins the current Ajax request trace
+
+### Requirement: Bounded render metadata
+Each Wicket render observation SHALL use stable observation names and SHALL attach only bounded layout or metamodel identifiers needed to identify the logical region.
+The viewer MUST NOT attach object values, rendered labels, object titles, bookmarks, action arguments, user identities, tenancy identifiers, or generated Wicket component paths.
+
+#### Scenario: Property metadata
+- **WHEN** a regular entity property is rendered with observation active
+- **THEN** its observation identifies the logical object type and property member using stable metamodel identifiers
+- **AND** it contains no property value or object-instance identifier
+
+#### Scenario: Other region metadata
+- **WHEN** a fieldset, collection, or action button is rendered with observation active
+- **THEN** its observation identifies the applicable layout or metamodel member
+- **AND** the identifier describes application structure rather than a particular domain-object instance
+
+### Requirement: Bounded instrumentation scope
+The viewer SHALL exclude high-volume or non-entity rendering contexts from member-level observations in this capability.
+It SHALL NOT create property render observations for compact collection-table cells or action parameters, and SHALL NOT create action render observations for service menus or row-level collection actions.
+
+#### Scenario: Collection table rendering
+- **WHEN** an entity collection renders multiple rows and scalar cells
+- **THEN** the viewer creates the enclosing collection render observation
+- **AND** it does not create a property render observation for each table cell
+- **AND** it does not create an action render observation for each row action
+
+#### Scenario: Action prompt rendering
+- **WHEN** Wicket renders action parameter fields in a prompt
+- **THEN** those parameter fields do not produce `causeway.wicket.property.render` observations
+
+### Requirement: Render observation lifecycle safety
+The viewer SHALL close render observation scopes after successful rendering and SHALL defensively close any remaining active render observations when request rendering fails or the request detaches.
+A rendering failure SHALL be recorded on the active observation when the originating failure is available.
+
+#### Scenario: Successful nested rendering
+- **WHEN** nested instrumented regions render successfully
+- **THEN** their observation scopes close in reverse nesting order
+- **AND** no Wicket render observation remains current after request completion
+
+#### Scenario: Rendering failure
+- **WHEN** rendering throws before a normal component completion callback
+- **THEN** request-level cleanup records the failure when available
+- **AND** closes every remaining render observation scope
+- **AND** no render observation state leaks into subsequent work on the request thread
+
+#### Scenario: Defensive cleanup after normal completion
+- **WHEN** all observations close normally and request detach subsequently performs defensive cleanup
+- **THEN** cleanup is harmless and does not stop an observation more than once
+
+### Requirement: Serializable instrumentation state
+Wicket render instrumentation SHALL retain only serializable region descriptors in persisted component state and SHALL NOT serialize active observations, scopes, registries, or integration services.
+
+#### Scenario: Page passivation and later rendering
+- **WHEN** an instrumented Wicket page is serialized, restored, and rendered in a later request
+- **THEN** no telemetry object from the earlier request is restored
+- **AND** the later render creates observations using that request's current trace context
+
+### Requirement: Render-only semantics
+The duration of a Wicket region render observation SHALL represent the component's actual Wicket rendering callback and descendant work performed during that callback.
+It SHALL NOT be presented as covering component construction, initialization, configuration, visibility, usability, or authorization evaluation performed before rendering.
+
+#### Scenario: Preparation precedes rendering
+- **WHEN** component preparation completes before an instrumented region begins actual rendering
+- **THEN** the region render observation starts only for the actual render callback
+- **AND** operational documentation states that preparation time can remain outside region render spans
diff --git a/openspec/changes/add-wicket-region-render-spans/tasks.md b/openspec/changes/add-wicket-region-render-spans/tasks.md
new file mode 100644
index 00000000000..b0c51c3b807
--- /dev/null
+++ b/openspec/changes/add-wicket-region-render-spans/tasks.md
@@ -0,0 +1,30 @@
+## 1. Observation Infrastructure
+
+- [ ] 1.1 Define immutable serializable descriptors for page, fieldset, property, collection, and action render regions with stable observation names and safe metadata.
+- [ ] 1.2 Implement a reusable Wicket render behavior that resolves `CausewayObservationIntegration` per request, starts and scopes an observation immediately before actual rendering, and closes it after successful rendering.
+- [ ] 1.3 Implement request-local tracking that unregisters normally completed observations and closes remaining observations in reverse start order after an exception or request detach.
+- [ ] 1.4 Wire defensive cleanup into the existing Wicket request-cycle integration and record the originating render failure when available.
+
+## 2. Semantic Region Instrumentation
+
+- [ ] 2.1 Attach the page render observation to `EntityPage` with stable logical object-type metadata.
+- [ ] 2.2 Attach fieldset render observations to `PropertyGroup` using safe layout identifiers.
+- [ ] 2.3 Attach property render observations to regular entity-property scalar panels while excluding action parameters and compact/table-cell renderings.
+- [ ] 2.4 Attach collection render observations to `EntityCollectionPanel` using stable collection identifiers.
+- [ ] 2.5 Attach action render observations to entity object-form action links while excluding service-menu and collection-row actions.
+- [ ] 2.6 Apply the Wicket viewer module-name convention and verify that no domain values, instance identifiers, generated Wicket paths, or other prohibited metadata are emitted.
+
+## 3. Automated Verification
+
+- [ ] 3.1 Add focused lifecycle tests proving observation start, scope nesting, normal reverse-order closure, and idempotent request cleanup.
+- [ ] 3.2 Add failure tests proving that an interrupted render records the available failure, drains active observations, and leaves no current observation behind.
+- [ ] 3.3 Add Wicket component tests proving the page, fieldset, property, collection, and action observation names, parentage, and safe identifiers.
+- [ ] 3.4 Add exclusion tests for table cells, row actions, action parameters, service menus, and inactive observation mode.
+- [ ] 3.5 Add a serialization test proving that active observations, scopes, registries, and integration services are not retained with an instrumented page or behavior.
+- [ ] 3.6 Add an Ajax partial-render test proving that only rendered instrumented regions create observations under the current Ajax request trace.
+
+## 4. Documentation and Validation
+
+- [ ] 4.1 Update `adoc/micrometer-tracing-operations.adoc` with the Wicket span taxonomy, expected trace tree, activation behavior, metadata privacy rules, exclusions, and render-only limitation.
+- [ ] 4.2 Run the focused Wicket UI and viewer tests together with observation integration tests.
+- [ ] 4.3 Validate a representative entity page against local Jaeger and record that full-page and Ajax traces have the expected nesting without per-cell span amplification.
diff --git a/openspec/planned-changes/add-wicket-collection-render-detail-spans.md b/openspec/planned-changes/add-wicket-collection-render-detail-spans.md
new file mode 100644
index 00000000000..ab823f0cdca
--- /dev/null
+++ b/openspec/planned-changes/add-wicket-collection-render-detail-spans.md
@@ -0,0 +1,48 @@
+# Add Wicket Collection Render Detail Spans
+
+**Roadmap position:** Proposal 3, conditional on collection regions remaining broad bottlenecks.
+
+## Why
+
+A collection-region span can show that a collection is slow without distinguishing data loading, pagination or materialization, presentation selection, and markup generation.
+More focused collection observations would make the next optimization decision evidence-based while retaining the collection as the semantic parent.
+
+## Proposed Changes
+
+- Split selected work beneath `causeway.wicket.collection.render` into bounded semantic operations.
+- Identify collection data loading separately from page slicing or materialization where the code provides reliable contiguous boundaries.
+- Identify presentation-specific work for table, icon, summary, hidden, and extension-provided presentations.
+- Ensure automatic JDBC spans inherit from the collection-loading observation when database access occurs within that boundary.
+- Add bounded metadata such as logical collection ID, presentation kind, configured page size, and returned count where semantically safe.
+- Retain the no-per-row and no-per-cell policy.
+
+Candidate stable names are:
+
+```text
+causeway.wicket.collection.load
+causeway.wicket.collection.page
+causeway.wicket.collection.presentation
+```
+
+## Non-Goals
+
+- Do not create observations for every row, cell, or row action.
+- Do not record collection elements, bookmarks, row identifiers, filter values, or exported content.
+- Do not introduce persistence-adapter-specific assumptions into shared Wicket instrumentation.
+- Do not replace automatic JDBC instrumentation.
+
+## Prerequisites and Evidence Gate
+
+The region-render proposal must show that one or more collection regions dominate representative traces.
+Proceed only when the broad collection duration cannot already be explained by child JDBC spans or existing application observations.
+
+## Design Questions
+
+- Which collection presentations load data during rendering and which load it during preparation?
+- Is a returned element count bounded operational metadata or potentially sensitive application data?
+- Can custom collection presentations participate through a small SPI without coupling extensions to core instrumentation?
+- Are paging and conversion boundaries shared across table and non-table presentations?
+
+## Expected Exit Gate
+
+A slow collection trace separates loading, paging or materialization, and presentation work without emitting row-level spans or exposing collection contents.
diff --git a/openspec/planned-changes/add-wicket-region-preparation-spans.md b/openspec/planned-changes/add-wicket-region-preparation-spans.md
new file mode 100644
index 00000000000..326bd2fe6f7
--- /dev/null
+++ b/openspec/planned-changes/add-wicket-region-preparation-spans.md
@@ -0,0 +1,49 @@
+# Add Wicket Region Preparation Spans
+
+**Roadmap position:** Proposal 2, conditional on evidence from `add-wicket-region-render-spans`.
+
+## Why
+
+Wicket performs component initialization, configuration, visibility, usability, and authorization work before actual markup rendering.
+If the first render spans leave substantial page time unattributed, operators need bounded observations that identify which logical region spends time in preparation.
+
+## Proposed Changes
+
+- Add preparation observations for fieldsets, regular entity properties, and entity collections.
+- Add an action-evaluation observation around visibility and usability decisions for relevant entity-page actions.
+- Keep preparation observations separate from render observations so every duration represents a contiguous operation.
+- Reuse the safe static identifiers and exclusion rules established by the region-render capability.
+- Preserve full-page and Ajax request trace context and failure cleanup.
+- Document how preparation and render spans complement rather than overlap one another.
+
+Candidate stable names are:
+
+```text
+causeway.wicket.fieldset.prepare
+causeway.wicket.property.prepare
+causeway.wicket.collection.prepare
+causeway.wicket.action.evaluate
+```
+
+## Non-Goals
+
+- Do not stretch one observation from construction through later rendering.
+- Do not observe arbitrary Wicket lifecycle callbacks or generic layout components.
+- Do not duplicate `causeway.action.invocation` or record action execution.
+- Do not add property-value, object-instance, user, tenant, or generated component-path metadata.
+
+## Prerequisites and Evidence Gate
+
+The region-render proposal must be implemented and exercised against representative slow pages first.
+Proceed only when traces show material time outside logical render spans and profiling or targeted diagnostics associate that gap with Wicket preparation or consent evaluation.
+
+## Design Questions
+
+- Which callbacks provide contiguous and exception-safe boundaries for each semantic component?
+- Should property preparation cover only `configure()` work or also lazy GUI initialization?
+- How can repeated Wicket visibility checks be aggregated without producing multiple tiny action spans?
+- Which preparation callbacks run before the Causeway root interaction is current, if any?
+
+## Expected Exit Gate
+
+A representative trace distinguishes fieldset, property, collection, and action-evaluation preparation from actual rendering, retains correct parentage, and adds no per-cell or per-row span amplification.
diff --git a/openspec/planned-changes/add-wicket-slow-row-diagnostics.md b/openspec/planned-changes/add-wicket-slow-row-diagnostics.md
new file mode 100644
index 00000000000..d9e45403f33
--- /dev/null
+++ b/openspec/planned-changes/add-wicket-slow-row-diagnostics.md
@@ -0,0 +1,49 @@
+# Add Wicket Slow Row Diagnostics
+
+**Roadmap position:** Proposal 5, only after collection-detail spans demonstrate unexplained row-scaling cost.
+
+## Why
+
+A collection may spend most of its time converting and rendering rows even when its query is fast.
+Unconditional per-row and per-cell spans would create excessive telemetry, so row diagnostics need an aggregate-first and explicitly bounded design.
+
+## Proposed Changes
+
+- Add aggregate diagnostics for collection row and cell processing beneath the collection presentation observation.
+- Record bounded counts and aggregate duration statistics that explain scaling without identifying individual rows.
+- Consider opt-in slow-row observations only when a row exceeds a configured diagnostic threshold and the request remains within its observation budget.
+- Ensure any slow-row observation uses an ordinal or anonymous sequence rather than a domain identifier.
+- Integrate with observation-detail and per-request budget controls from Proposal 4.
+- Document the intended use as temporary high-detail diagnosis rather than routine business telemetry.
+
+Potential aggregate metadata includes:
+
+```text
+causeway.wicket.collection.rowCount
+causeway.wicket.collection.cellCount
+causeway.wicket.collection.rows.totalDuration
+causeway.wicket.collection.rows.maxDuration
+```
+
+## Non-Goals
+
+- Do not emit one observation for every row or cell by default.
+- Do not record row values, entity identifiers, bookmarks, table filter values, or user-visible labels.
+- Do not use aggregate telemetry as an application analytics or auditing mechanism.
+- Do not duplicate JDBC query spans or collection-loading observations.
+
+## Prerequisites and Evidence Gate
+
+Collection-detail traces must show that loading is acceptably fast while collection presentation time grows materially with row count.
+Observation-detail controls and a request budget should be available before any opt-in slow-row spans are considered.
+
+## Design Questions
+
+- Can aggregate timing be collected with negligible overhead when tracing is inactive or unsampled?
+- Should slow-row thresholds be absolute durations, relative outliers within the page, or collector-side policy?
+- Can custom table and icon presentations expose row boundaries consistently?
+- Are duration aggregates better represented as span attributes, span events, or Micrometer metrics in this maintenance branch?
+
+## Expected Exit Gate
+
+A representative large collection explains row-scaling cost with bounded aggregate telemetry, and optional slow-row diagnosis cannot expose domain identity or exceed the configured request budget.
diff --git a/openspec/planned-changes/configure-wicket-observation-detail.md b/openspec/planned-changes/configure-wicket-observation-detail.md
new file mode 100644
index 00000000000..52d27c5fa14
--- /dev/null
+++ b/openspec/planned-changes/configure-wicket-observation-detail.md
@@ -0,0 +1,60 @@
+# Configure Wicket Observation Detail
+
+**Roadmap position:** Proposal 4, before broad production enablement if trace volume proves material.
+
+## Why
+
+Member-level Wicket observations are intentionally bounded but can still add tens or hundreds of spans to a complex page.
+Operators may need a framework-level way to select useful detail and cap pathological requests independently of agent sampling.
+
+## Proposed Changes
+
+- Add Wicket observation detail levels for disabled, page-only, region-level, and member-level tracing.
+- Add a configurable maximum number of Causeway Wicket observations per request.
+- Preserve deterministic parentage when detail is reduced.
+- Mark the page or current enclosing region when the request budget truncates lower-level observations.
+- Expose configuration through the established Causeway Wicket configuration model.
+- Document how framework detail controls interact with Java-agent head sampling and collector policies.
+
+Illustrative configuration is:
+
+```yaml
+causeway:
+  viewer:
+    wicket:
+      observation:
+        detail: members
+        max-spans-per-request: 200
+```
+
+Potential detail levels are:
+
+| Level | Semantic observations |
+|---|---|
+| `none` | No Causeway Wicket observations |
+| `page` | Entity-page rendering only |
+| `regions` | Page, fieldsets, and collections |
+| `members` | Properties and relevant action buttons in addition to regions |
+
+## Non-Goals
+
+- Do not implement duration-based sampling inside Causeway.
+- Do not replace OpenTelemetry agent sampling or collector tail sampling.
+- Do not provide exporter, endpoint, or SDK configuration.
+- Do not silently change global tracing configuration outside the Wicket viewer.
+
+## Prerequisites and Evidence Gate
+
+The initial region spans should first establish realistic span counts for representative full-page and Ajax requests.
+Proceed before broad production rollout if those measurements show unacceptable volume or if applications require different diagnostic detail levels.
+
+## Design Questions
+
+- Should the default remain full member detail when the observation profile is active, or default to region detail?
+- Does the request budget count the page span or only descendants?
+- Which enclosing observation receives the truncation attribute during an Ajax partial render with no page span?
+- Should configuration changes require application restart, consistent with the current observation profile?
+
+## Expected Exit Gate
+
+Operators can choose page, region, or member detail and can prove that an oversized page never exceeds the configured Causeway Wicket observation budget while automatic agent spans remain unaffected.
diff --git a/openspec/planned-changes/wicket-render-observability-roadmap.md b/openspec/planned-changes/wicket-render-observability-roadmap.md
new file mode 100644
index 00000000000..8f4cc24d27f
--- /dev/null
+++ b/openspec/planned-changes/wicket-render-observability-roadmap.md
@@ -0,0 +1,113 @@
+# Wicket Render Observability Roadmap
+
+## Objective
+
+Expose enough server-side Wicket rendering structure to explain slow entity pages while keeping observations semantically stable, privacy-safe, operationally bounded, and compatible with the existing Java-agent-owned telemetry architecture.
+
+The roadmap deliberately starts with actual rendering because it has clean contiguous boundaries and natural component-tree nesting.
+Later proposals are evidence-gated so Causeway does not add lifecycle complexity or telemetry volume without a demonstrated diagnostic gap.
+
+## Intended Evolution
+
+```text
+Existing trace
+HTTP
+└── causeway.root.interaction
+    └── largely opaque Wicket page work
+
+Proposal 1
+HTTP
+└── causeway.root.interaction
+    └── causeway.wicket.page.render
+        ├── fieldset.render
+        │   ├── property.render
+        │   └── action.render
+        └── collection.render
+
+Proposal 2, if page time remains unattributed
+HTTP
+└── causeway.root.interaction
+    ├── property.prepare / action.evaluate / collection.prepare
+    └── causeway.wicket.page.render and nested render spans
+
+Proposal 3, if a collection remains broad
+collection.render
+├── collection.load
+│   └── JDBC
+├── collection.page
+└── collection.presentation
+
+Proposals 4 and 5, if volume or row scaling requires control
+configuration and request budget
+└── bounded aggregate row diagnostics
+```
+
+## Phase 1: Establish Semantic Render Regions
+
+**Active OpenSpec change:** `add-wicket-region-render-spans`
+
+Add page, fieldset, regular entity-property, entity-collection, and relevant action-button render observations.
+Use fixed names, safe static identifiers, natural Wicket rendering parentage, Ajax support, and request-level failure cleanup.
+Exclude preparation, table cells, row actions, action parameters, and browser work.
+
+**Exit gate:** Representative full-page and Ajax traces show correctly nested logical regions, no scope leaks, and no per-cell span amplification.
+
+## Phase 2: Explain Pre-Render Work When Necessary
+
+**Planned change:** `add-wicket-region-preparation-spans`
+
+Use Phase 1 traces to measure time that remains directly under the root or page boundary.
+If preparation is material, add separate contiguous observations for fieldset, property, and collection preparation and for action visibility or usability evaluation.
+Do not stretch render observations across Wicket lifecycle phases.
+
+**Entry gate:** Material unexplained duration is associated with initialization, configuration, or consent evaluation.
+
+**Exit gate:** Traces clearly distinguish preparation from actual rendering without duplicate or overlapping duration semantics.
+
+## Phase 3: Decompose Slow Collections
+
+**Planned change:** `add-wicket-collection-render-detail-spans`
+
+If collection regions dominate, separate loading, paging or materialization, and presentation work.
+Retain automatic JDBC spans as children where applicable and continue to exclude individual rows and cells.
+
+**Entry gate:** A collection region is slow and its existing child spans do not explain the duration.
+
+**Exit gate:** Operators can tell whether the bottleneck is data access, model processing, or presentation generation.
+
+## Phase 4: Bound Production Detail
+
+**Planned change:** `configure-wicket-observation-detail`
+
+Use measured span counts to decide whether page, region, and member detail levels or a per-request observation budget are needed.
+Keep these controls independent from Java-agent and collector sampling.
+
+**Entry gate:** Representative measurements or production rollout requirements show that one fixed detail level is insufficient.
+
+**Exit gate:** An application can select diagnostic detail and enforce a hard bound on Causeway Wicket observations per request.
+
+## Phase 5: Diagnose Row Scaling Without Span Explosion
+
+**Planned change:** `add-wicket-slow-row-diagnostics`
+
+Add aggregate-first row and cell timing only if collection presentation remains slow after loading has been separated.
+Permit any individual slow-row observations only as explicit bounded diagnostics, without domain identifiers.
+
+**Entry gate:** Collection-detail traces demonstrate presentation cost that scales with rows rather than query latency.
+
+**Exit gate:** Row-scaling cost is explained with bounded overhead and no disclosure of row identity or values.
+
+## Longer-Term Horizon
+
+Browser-side OpenTelemetry or Real User Monitoring could eventually correlate server generation with network transfer, JavaScript initialization, DOM work, layout, and paint.
+That would introduce propagation, browser security, deployment, and telemetry-ownership questions and is not part of Proposals 1 through 5.
+
+## Decision Principles
+
+- Add a new observation only for a contiguous operation with a clear semantic meaning.
+- Prefer fixed observation names and static layout or metamodel identifiers.
+- Never record domain values or object-instance, user, tenant, or row identity.
+- Use trace evidence from the preceding phase as the entry gate for the next phase.
+- Prefer aggregate diagnostics over per-item spans.
+- Keep the OpenTelemetry Java agent responsible for SDK ownership, automatic instrumentation, sampling, and export.
+- Preserve no-op behavior when observation is inactive.


(causeway) 02/03: CAUSEWAY-4058: adds Wicket region render observations

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 0b93bd8662a378a832b54acdee64b2303dd3ef32
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Sep 23 06:39:30 2026 +0100

    CAUSEWAY-4058: adds Wicket region render observations
---
 adoc/micrometer-tracing-operations.adoc            |  31 +-
 .../add-wicket-region-render-spans/tasks.md        |  38 +-
 .../add-wicket-region-render-spans/validation.md   |  33 ++
 .../observation/WicketRenderObservationTest.java   | 423 +++++++++++++++++++++
 viewers/wicket/ui/src/main/java/module-info.java   |   1 +
 .../wicket/ui/CausewayModuleViewerWicketUi.java    |   1 +
 .../entity/collection/EntityCollectionPanel.java   |   7 +
 .../components/entity/fieldset/PropertyGroup.java  |   6 +
 .../ui/components/scalars/ScalarPanelAbstract.java |   5 +
 .../components/widgets/actionlink/ActionLink.java  |   4 +
 .../WicketRenderObservationBehavior.java           | 109 ++++++
 .../WicketRenderObservationDescriptor.java         | 111 ++++++
 .../observation/WicketRenderObservationPolicy.java |  61 +++
 .../WicketRenderObservationTracker.java            | 144 +++++++
 .../viewer/wicket/ui/pages/entity/EntityPage.java  |   5 +
 .../integration/WebRequestCycleForCauseway.java    |  13 +-
 16 files changed, 970 insertions(+), 22 deletions(-)

diff --git a/adoc/micrometer-tracing-operations.adoc b/adoc/micrometer-tracing-operations.adoc
index 3ac0fdca749..92bfa45453a 100644
--- a/adoc/micrometer-tracing-operations.adoc
+++ b/adoc/micrometer-tracing-operations.adoc
@@ -223,6 +223,34 @@ With the `observation` profile active, Causeway adds two stable semantic spans:
 Action spans carry the bounded attributes `causeway.action.id` and `causeway.execution.initiatedBy`.
 Causeway does not add action arguments, return values, target instance identifiers, user names, tenancy tokens, or other instance-specific application data to these semantic spans.
 
+=== Wicket Entity Page Rendering
+
+When the Wicket viewer renders an entity page, Causeway adds semantic spans for the page and its rendered logical regions:
+
+[source,text]
+----
+causeway.wicket.page.render
+├── causeway.wicket.fieldset.render
+│   ├── causeway.wicket.property.render
+│   └── causeway.wicket.action.render
+├── causeway.wicket.collection.render
+└── causeway.wicket.action.render
+----
+
+Ajax responses create spans only for the instrumented regions included in the partial render, so an Ajax trace need not contain `causeway.wicket.page.render`.
+Automatic JDBC spans become descendants of the active region span when database work occurs during that region's rendering.
+
+Wicket spans use `causeway.object.type` together with the applicable bounded attribute `causeway.fieldset.id`, `causeway.property.id`, `causeway.collection.id`, or `causeway.action.id`.
+These identifiers describe static application structure.
+Causeway does not attach property values, rendered labels, object titles, bookmarks, action arguments, user names, tenancy tokens, collection-row identities, or generated Wicket component paths.
+
+The initial Wicket instrumentation deliberately excludes collection table cells, collection row actions, action parameters, service menus, and browser-side rendering.
+It measures the actual Wicket render callback and descendant work performed during that callback.
+Component construction, initialization, configuration, visibility, usability, and authorization work performed before rendering can therefore remain directly beneath the request or root-interaction span.
+
+Without the `observation` profile, the existing no-op registry prevents these spans from being exported and Wicket output remains unchanged.
+The Java agent continues to own sampling and export when Wicket observations are active.
+
 === Filter Foreground and Background Traces
 
 Causeway classifies Java-agent-created HTTP entry spans with `causeway.execution.mode=foreground` and the command-log extension's Quartz entry spans with `causeway.execution.mode=background`.
@@ -249,7 +277,8 @@ Classification is low-cardinality and does not add another span, SDK, or exporte
 It makes traces easier to discover but does not reduce collection, export, retention, or Jaeger memory consumption.
 Use head sampling or a configurable collector's tail processing when background telemetry volume itself must be reduced.
 
-The initial slice deliberately does not add Causeway semantic spans for transactions, property edits, execution publishing, persistence-specific operations, or viewer-specific operations.
+Causeway does not add semantic spans for transactions, property edits, execution publishing, or persistence-specific operations.
+The Wicket viewer adds only the bounded render observations described above and does not add preparation or browser-side observations.
 The Java agent can still produce automatic spans for supported libraries such as HTTP and JDBC.
 
 The behavior of the two activation controls is:
diff --git a/openspec/changes/add-wicket-region-render-spans/tasks.md b/openspec/changes/add-wicket-region-render-spans/tasks.md
index b0c51c3b807..471308e770a 100644
--- a/openspec/changes/add-wicket-region-render-spans/tasks.md
+++ b/openspec/changes/add-wicket-region-render-spans/tasks.md
@@ -1,30 +1,30 @@
 ## 1. Observation Infrastructure
 
-- [ ] 1.1 Define immutable serializable descriptors for page, fieldset, property, collection, and action render regions with stable observation names and safe metadata.
-- [ ] 1.2 Implement a reusable Wicket render behavior that resolves `CausewayObservationIntegration` per request, starts and scopes an observation immediately before actual rendering, and closes it after successful rendering.
-- [ ] 1.3 Implement request-local tracking that unregisters normally completed observations and closes remaining observations in reverse start order after an exception or request detach.
-- [ ] 1.4 Wire defensive cleanup into the existing Wicket request-cycle integration and record the originating render failure when available.
+- [x] 1.1 Define immutable serializable descriptors for page, fieldset, property, collection, and action render regions with stable observation names and safe metadata.
+- [x] 1.2 Implement a reusable Wicket render behavior that resolves `CausewayObservationIntegration` per request, starts and scopes an observation immediately before actual rendering, and closes it after successful rendering.
+- [x] 1.3 Implement request-local tracking that unregisters normally completed observations and closes remaining observations in reverse start order after an exception or request detach.
+- [x] 1.4 Wire defensive cleanup into the existing Wicket request-cycle integration and record the originating render failure when available.
 
 ## 2. Semantic Region Instrumentation
 
-- [ ] 2.1 Attach the page render observation to `EntityPage` with stable logical object-type metadata.
-- [ ] 2.2 Attach fieldset render observations to `PropertyGroup` using safe layout identifiers.
-- [ ] 2.3 Attach property render observations to regular entity-property scalar panels while excluding action parameters and compact/table-cell renderings.
-- [ ] 2.4 Attach collection render observations to `EntityCollectionPanel` using stable collection identifiers.
-- [ ] 2.5 Attach action render observations to entity object-form action links while excluding service-menu and collection-row actions.
-- [ ] 2.6 Apply the Wicket viewer module-name convention and verify that no domain values, instance identifiers, generated Wicket paths, or other prohibited metadata are emitted.
+- [x] 2.1 Attach the page render observation to `EntityPage` with stable logical object-type metadata.
+- [x] 2.2 Attach fieldset render observations to `PropertyGroup` using safe layout identifiers.
+- [x] 2.3 Attach property render observations to regular entity-property scalar panels while excluding action parameters and compact/table-cell renderings.
+- [x] 2.4 Attach collection render observations to `EntityCollectionPanel` using stable collection identifiers.
+- [x] 2.5 Attach action render observations to entity object-form action links while excluding service-menu and collection-row actions.
+- [x] 2.6 Apply the Wicket viewer module-name convention and verify that no domain values, instance identifiers, generated Wicket paths, or other prohibited metadata are emitted.
 
 ## 3. Automated Verification
 
-- [ ] 3.1 Add focused lifecycle tests proving observation start, scope nesting, normal reverse-order closure, and idempotent request cleanup.
-- [ ] 3.2 Add failure tests proving that an interrupted render records the available failure, drains active observations, and leaves no current observation behind.
-- [ ] 3.3 Add Wicket component tests proving the page, fieldset, property, collection, and action observation names, parentage, and safe identifiers.
-- [ ] 3.4 Add exclusion tests for table cells, row actions, action parameters, service menus, and inactive observation mode.
-- [ ] 3.5 Add a serialization test proving that active observations, scopes, registries, and integration services are not retained with an instrumented page or behavior.
-- [ ] 3.6 Add an Ajax partial-render test proving that only rendered instrumented regions create observations under the current Ajax request trace.
+- [x] 3.1 Add focused lifecycle tests proving observation start, scope nesting, normal reverse-order closure, and idempotent request cleanup.
+- [x] 3.2 Add failure tests proving that an interrupted render records the available failure, drains active observations, and leaves no current observation behind.
+- [x] 3.3 Add Wicket component tests proving the page, fieldset, property, collection, and action observation names, parentage, and safe identifiers.
+- [x] 3.4 Add exclusion tests for table cells, row actions, action parameters, service menus, and inactive observation mode.
+- [x] 3.5 Add a serialization test proving that active observations, scopes, registries, and integration services are not retained with an instrumented page or behavior.
+- [x] 3.6 Add an Ajax partial-render test proving that only rendered instrumented regions create observations under the current Ajax request trace.
 
 ## 4. Documentation and Validation
 
-- [ ] 4.1 Update `adoc/micrometer-tracing-operations.adoc` with the Wicket span taxonomy, expected trace tree, activation behavior, metadata privacy rules, exclusions, and render-only limitation.
-- [ ] 4.2 Run the focused Wicket UI and viewer tests together with observation integration tests.
-- [ ] 4.3 Validate a representative entity page against local Jaeger and record that full-page and Ajax traces have the expected nesting without per-cell span amplification.
+- [x] 4.1 Update `adoc/micrometer-tracing-operations.adoc` with the Wicket span taxonomy, expected trace tree, activation behavior, metadata privacy rules, exclusions, and render-only limitation.
+- [x] 4.2 Run the focused Wicket UI and viewer tests together with observation integration tests.
+- [x] 4.3 Validate a representative entity page against local Jaeger and record that full-page and Ajax traces have the expected nesting without per-cell span amplification.
diff --git a/openspec/changes/add-wicket-region-render-spans/validation.md b/openspec/changes/add-wicket-region-render-spans/validation.md
new file mode 100644
index 00000000000..026cf65a64a
--- /dev/null
+++ b/openspec/changes/add-wicket-region-render-spans/validation.md
@@ -0,0 +1,33 @@
+# Validation
+
+## Automated Tests
+
+The focused Wicket and observation test suite passed with no failures.
+`WicketRenderObservationTest` ran nine tests covering descriptor metadata, nested scope lifecycle, failure cleanup, Wicket rendering parentage, inactive mode, serialization, inclusion policy, exclusion policy, and Ajax partial rendering.
+The existing `ObservationClosureTest`, `CausewayObservationConfigurationTest`, `InteractionServiceDefaultObservationTest`, and `MemberExecutorServiceDefaultObservationTest` suites also passed.
+
+## Local Jaeger Trace
+
+A representative `TestAppJdoWkt` application was run with OpenTelemetry Java agent 1.31.0, the `observation` Spring profile, always-on sampling, and Jaeger 2.21.0.
+The `JdoInventoryJaxbVm` page contained multiple fieldsets, regular properties, collections with table rows and cells, entity actions, and a collection-associated action.
+
+Full-page trace `c8f5306f661634960f4095a189715512` contained 35 spans, of which 24 were Causeway Wicket spans:
+
+```text
+causeway.wicket.page.render        1
+causeway.wicket.fieldset.render    2
+causeway.wicket.property.render    5
+causeway.wicket.collection.render  4
+causeway.wicket.action.render     12
+```
+
+The page span was a child of `causeway.root.interaction`.
+Fieldsets and collections were children of the page span, regular properties were children of their fieldsets, and the collection-associated action was a child of its collection.
+The exported attributes contained `causeway.module=viewer.wicket`, `causeway.object.type`, and only the applicable static fieldset or member identifier.
+The unnamed fieldset was represented by the bounded identifier `<default>`.
+
+The page displayed collection tables with multiple rows, but only the five regular entity properties produced property spans.
+No per-cell property spans or per-row action spans were exported.
+
+Editing the `name` property generated Ajax trace `b2f059051b188b62f524125cc7f7fb9e`.
+That trace contained one `causeway.wicket.property.render` span beneath `causeway.root.interaction` and no `causeway.wicket.page.render` span, confirming partial-render behavior.
diff --git a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTest.java b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTest.java
new file mode 100644
index 00000000000..47c17d43f2d
--- /dev/null
+++ b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTest.java
@@ -0,0 +1,423 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.wicket.ui.observation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.causeway.applib.Identifier;
+import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.applib.id.LogicalType;
+import org.apache.causeway.core.config.observation.CausewayObservationIntegration;
+import org.apache.causeway.core.config.observation.ObservationClosure;
+import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
+import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.causeway.viewer.commons.model.hints.RenderingHint;
+import org.apache.causeway.viewer.wicket.model.models.ActionModel;
+import org.apache.causeway.viewer.wicket.model.models.ScalarModel;
+import org.apache.causeway.viewer.wicket.model.models.ScalarParameterModel;
+import org.apache.causeway.viewer.wicket.model.models.ScalarPropertyModel;
+
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationHandler;
+import io.micrometer.observation.ObservationRegistry;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.markup.Markup;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.util.tester.WicketTester;
+
+class WicketRenderObservationTest {
+
+    private static final String OBJECT_TYPE = "demo.Customer";
+
+    @Test
+    void descriptorsUseStableNamesAndSafeIdentifiers() {
+        final ObservationRegistry registry = registryWith(new RecordingHandler());
+        final WicketRenderObservationDescriptor descriptor =
+                WicketRenderObservationDescriptor.property(OBJECT_TYPE, OBJECT_TYPE + "#name");
+        final Observation observation = descriptor.customize(
+                Observation.createNotStarted(
+                        descriptor.getRegion().getObservationName(), registry));
+
+        assertEquals("causeway.wicket.property.render", observation.getContext().getName());
+        assertEquals("causeway.wicket.property.render", observation.getContext().getContextualName());
+        assertEquals(OBJECT_TYPE,
+                observation.getContext().getLowCardinalityKeyValue("causeway.object.type").getValue());
+        assertEquals(OBJECT_TYPE + "#name",
+                observation.getContext().getLowCardinalityKeyValue("causeway.property.id").getValue());
+        assertNull(observation.getContext().getLowCardinalityKeyValue("causeway.action.id"));
+        assertEquals("<default>",
+                WicketRenderObservationDescriptor.fieldset(OBJECT_TYPE, null).getMemberId());
+    }
+
+    @Test
+    void nestedLifecycleClosesScopesInReverseOrder() {
+        final RecordingHandler handler = new RecordingHandler();
+        final ObservationRegistry registry = registryWith(handler);
+        final WicketRenderObservationTracker.State state = new WicketRenderObservationTracker.State();
+
+        final WicketRenderObservationBehavior page = behaviorFor(
+                WicketRenderObservationDescriptor.page(OBJECT_TYPE));
+        final ObservationClosure pageClosure = closure(registry, page.descriptor());
+        page.activate(pageClosure);
+        state.register(page, pageClosure);
+        assertEquals("causeway.wicket.page.render", currentName(registry));
+
+        final WicketRenderObservationBehavior property = behaviorFor(
+                WicketRenderObservationDescriptor.property(OBJECT_TYPE, OBJECT_TYPE + "#name"));
+        final ObservationClosure propertyClosure = closure(registry, property.descriptor());
+        property.activate(propertyClosure);
+        state.register(property, propertyClosure);
+        assertEquals("causeway.wicket.property.render", currentName(registry));
+
+        state.complete(property);
+        assertEquals("causeway.wicket.page.render", currentName(registry));
+        assertFalse(property.isActive());
+
+        state.complete(page);
+        assertNull(currentName(registry));
+        assertFalse(page.isActive());
+        assertTrue(state.isEmpty());
+        assertEquals(List.of(
+                "start:causeway.wicket.page.render",
+                "scope-opened:causeway.wicket.page.render",
+                "start:causeway.wicket.property.render",
+                "scope-opened:causeway.wicket.property.render",
+                "scope-closed:causeway.wicket.property.render",
+                "stop:causeway.wicket.property.render",
+                "scope-closed:causeway.wicket.page.render",
+                "stop:causeway.wicket.page.render"), handler.events);
+    }
+
+    @Test
+    void failureCleanupMarksAndClosesAllActiveObservationsIdempotently() {
+        final RecordingHandler handler = new RecordingHandler();
+        final ObservationRegistry registry = registryWith(handler);
+        final WicketRenderObservationTracker.State state = new WicketRenderObservationTracker.State();
+        final WicketRenderObservationBehavior page = behaviorFor(
+                WicketRenderObservationDescriptor.page(OBJECT_TYPE));
+        final WicketRenderObservationBehavior collection = behaviorFor(
+                WicketRenderObservationDescriptor.collection(OBJECT_TYPE, OBJECT_TYPE + "#orders"));
+
+        final ObservationClosure pageClosure = closure(registry, page.descriptor());
+        final ObservationClosure collectionClosure = closure(registry, collection.descriptor());
+        page.activate(pageClosure);
+        collection.activate(collectionClosure);
+        state.register(page, pageClosure);
+        state.register(collection, collectionClosure);
+
+        final RuntimeException failure = new RuntimeException("render failed");
+        state.cleanup(failure);
+        state.cleanup(failure);
+
+        assertNull(currentName(registry));
+        assertFalse(page.isActive());
+        assertFalse(collection.isActive());
+        assertTrue(state.isEmpty());
+        assertEquals(List.of(
+                "error:causeway.wicket.collection.render:render failed",
+                "error:causeway.wicket.page.render:render failed"), handler.errors);
+    }
+
+    @Test
+    void actualWicketRenderingPreservesComponentTreeParentage() {
+        final RecordingHandler handler = new RecordingHandler();
+        final CausewayObservationIntegration integration =
+                new CausewayObservationIntegration(registryWith(handler));
+        final WicketTester tester = new WicketTester();
+        try {
+            final ObservedContainer page = new ObservedContainer(
+                    "component", integration,
+                    WicketRenderObservationDescriptor.page(OBJECT_TYPE));
+            page.add(new ObservedContainer(
+                    "property", integration,
+                    WicketRenderObservationDescriptor.property(
+                            OBJECT_TYPE, OBJECT_TYPE + "#name")));
+
+            tester.startComponentInPage(page, Markup.of(
+                    "<div wicket:id='component'><span wicket:id='property'></span></div>"));
+
+            assertEquals(List.of(
+                    "causeway.wicket.page.render<-null",
+                    "causeway.wicket.property.render<-causeway.wicket.page.render"), handler.parents);
+            assertEquals(List.of(
+                    "start:causeway.wicket.page.render",
+                    "scope-opened:causeway.wicket.page.render",
+                    "start:causeway.wicket.property.render",
+                    "scope-opened:causeway.wicket.property.render",
+                    "scope-closed:causeway.wicket.property.render",
+                    "stop:causeway.wicket.property.render",
+                    "scope-closed:causeway.wicket.page.render",
+                    "stop:causeway.wicket.page.render"), handler.events);
+            assertNull(integration.observationRegistry().getCurrentObservation());
+        } finally {
+            tester.destroy();
+        }
+    }
+
+    @Test
+    void noopObservationLeavesWicketRenderingUnchanged() {
+        final CausewayObservationIntegration integration =
+                new CausewayObservationIntegration(ObservationRegistry.NOOP);
+        final WicketTester tester = new WicketTester();
+        try {
+            final ObservedContainer property = new ObservedContainer(
+                    "component", integration,
+                    WicketRenderObservationDescriptor.property(
+                            OBJECT_TYPE, OBJECT_TYPE + "#name"));
+
+            tester.startComponentInPage(property, Markup.of(
+                    "<span wicket:id='component'>value</span>"));
+
+            tester.assertComponent("component", ObservedContainer.class);
+            assertNull(integration.observationRegistry().getCurrentObservation());
+        } finally {
+            tester.destroy();
+        }
+    }
+
+    @Test
+    void ajaxPartialRenderingObservesOnlyTheTargetedRegion() {
+        final RecordingHandler handler = new RecordingHandler();
+        final CausewayObservationIntegration integration =
+                new CausewayObservationIntegration(registryWith(handler));
+        final WicketTester tester = new WicketTester();
+        try {
+            final WebMarkupContainer root = new WebMarkupContainer("component");
+            final ObservedContainer property = new ObservedContainer(
+                    "property", integration,
+                    WicketRenderObservationDescriptor.property(
+                            OBJECT_TYPE, OBJECT_TYPE + "#name"));
+            property.setOutputMarkupId(true);
+            final AjaxLink<Void> refresh = new AjaxLink<Void>("refresh") {
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                public void onClick(final AjaxRequestTarget target) {
+                    target.add(property);
+                }
+            };
+            root.add(refresh, property);
+            tester.startComponentInPage(root, Markup.of(
+                    "<div wicket:id='component'>"
+                    + "<a wicket:id='refresh'>refresh</a>"
+                    + "<span wicket:id='property'></span>"
+                    + "</div>"));
+            handler.events.clear();
+
+            tester.executeAjaxEvent(refresh, "click");
+
+            assertEquals(List.of(
+                    "start:causeway.wicket.property.render",
+                    "scope-opened:causeway.wicket.property.render",
+                    "scope-closed:causeway.wicket.property.render",
+                    "stop:causeway.wicket.property.render"), handler.events);
+            assertNull(integration.observationRegistry().getCurrentObservation());
+        } finally {
+            tester.destroy();
+        }
+    }
+
+    @Test
+    void serializationRetainsDescriptorButNotActiveTelemetry() throws Exception {
+        final WicketRenderObservationBehavior original = behaviorFor(
+                WicketRenderObservationDescriptor.fieldset(OBJECT_TYPE, "identity"));
+        original.activate(new ObservationClosure());
+
+        final WicketRenderObservationBehavior restored = roundTrip(original);
+
+        assertFalse(restored.isActive());
+        assertEquals("causeway.wicket.fieldset.render",
+                restored.descriptor().getRegion().getObservationName());
+        assertEquals(OBJECT_TYPE, restored.descriptor().getObjectType());
+        assertEquals("identity", restored.descriptor().getMemberId());
+    }
+
+    @Test
+    void propertyPolicyIncludesOnlyRegularEntityProperties() {
+        final Identifier propertyId = Identifier.propertyIdentifier(
+                LogicalType.eager(Object.class, OBJECT_TYPE), "name");
+        final OneToOneAssociation property = mock(OneToOneAssociation.class);
+        when(property.getFeatureIdentifier()).thenReturn(propertyId);
+
+        final ScalarPropertyModel regularProperty = mock(ScalarPropertyModel.class);
+        when(regularProperty.getRenderingHint()).thenReturn(RenderingHint.REGULAR);
+        when(regularProperty.getMetaModel()).thenReturn(property);
+
+        final ScalarPropertyModel tableProperty = mock(ScalarPropertyModel.class);
+        when(tableProperty.getRenderingHint()).thenReturn(RenderingHint.PARENTED_PROPERTY_COLUMN);
+
+        final ScalarModel parameter = mock(ScalarParameterModel.class);
+        when(parameter.getRenderingHint()).thenReturn(RenderingHint.REGULAR);
+
+        final Optional<WicketRenderObservationDescriptor> included =
+                WicketRenderObservationPolicy.propertyDescriptor(regularProperty);
+        assertTrue(included.isPresent());
+        assertEquals(OBJECT_TYPE + "#name", included.get().getMemberId());
+        assertTrue(WicketRenderObservationPolicy.propertyDescriptor(tableProperty).isEmpty());
+        assertTrue(WicketRenderObservationPolicy.propertyDescriptor(parameter).isEmpty());
+    }
+
+    @Test
+    void actionPolicyIncludesOnlyObjectFormActionsUnassociatedWithParameters() {
+        final Identifier actionId = Identifier.actionIdentifier(
+                LogicalType.eager(Object.class, OBJECT_TYPE), "updateName");
+        final ObjectAction action = mock(ObjectAction.class);
+        when(action.getFeatureIdentifier()).thenReturn(actionId);
+
+        final ActionModel entityAction = mock(ActionModel.class);
+        when(entityAction.getAssociatedParameter()).thenReturn(Optional.empty());
+        when(entityAction.getAction()).thenReturn(action);
+
+        final ActionModel parameterAction = mock(ActionModel.class);
+        when(parameterAction.getAssociatedParameter()).thenReturn(
+                Optional.of(mock(ScalarParameterModel.class)));
+
+        final Optional<WicketRenderObservationDescriptor> included =
+                WicketRenderObservationPolicy.actionDescriptor(entityAction, Where.OBJECT_FORMS);
+        assertTrue(included.isPresent());
+        assertEquals(OBJECT_TYPE + "#updateName()", included.get().getMemberId());
+        assertTrue(WicketRenderObservationPolicy.actionDescriptor(
+                entityAction, Where.ALL_TABLES).isEmpty());
+        assertTrue(WicketRenderObservationPolicy.actionDescriptor(
+                entityAction, Where.ANYWHERE).isEmpty());
+        assertTrue(WicketRenderObservationPolicy.actionDescriptor(
+                parameterAction, Where.OBJECT_FORMS).isEmpty());
+    }
+
+    private static WicketRenderObservationBehavior behaviorFor(
+            final WicketRenderObservationDescriptor descriptor) {
+        return new WicketRenderObservationBehavior(descriptor);
+    }
+
+    private static ObservationClosure closure(
+            final ObservationRegistry registry,
+            final WicketRenderObservationDescriptor descriptor) {
+        return new ObservationClosure().startAndOpenScope(descriptor.customize(
+                Observation.createNotStarted(descriptor.getRegion().getObservationName(), registry)));
+    }
+
+    private static String currentName(final ObservationRegistry registry) {
+        final Observation current = registry.getCurrentObservation();
+        return current != null ? current.getContext().getName() : null;
+    }
+
+    private static ObservationRegistry registryWith(final RecordingHandler handler) {
+        final ObservationRegistry registry = ObservationRegistry.create();
+        registry.observationConfig().observationHandler(handler);
+        return registry;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T roundTrip(final T object) throws Exception {
+        final byte[] serialized;
+        try (ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+                ObjectOutputStream output = new ObjectOutputStream(bytes)) {
+            output.writeObject(object);
+            serialized = bytes.toByteArray();
+        }
+        try (ObjectInputStream input = new ObjectInputStream(
+                new ByteArrayInputStream(serialized))) {
+            return (T) input.readObject();
+        }
+    }
+
+    private static final class ObservedContainer
+    extends WebMarkupContainer
+    implements HasMetaModelContext {
+
+        private static final long serialVersionUID = 1L;
+        private final CausewayObservationIntegration integration;
+
+        private ObservedContainer(
+                final String id,
+                final CausewayObservationIntegration integration,
+                final WicketRenderObservationDescriptor descriptor) {
+            super(id);
+            this.integration = integration;
+            WicketRenderObservationBehavior.addTo(this, descriptor);
+        }
+
+        @Override
+        public <T> Optional<T> lookupService(final Class<T> serviceClass) {
+            return serviceClass == CausewayObservationIntegration.class
+                    ? Optional.of(serviceClass.cast(integration))
+                    : Optional.empty();
+        }
+    }
+
+    private static final class RecordingHandler
+    implements ObservationHandler<Observation.Context> {
+
+        private final List<String> events = new ArrayList<>();
+        private final List<String> errors = new ArrayList<>();
+        private final List<String> parents = new ArrayList<>();
+
+        @Override
+        public boolean supportsContext(final Observation.Context context) {
+            return true;
+        }
+
+        @Override
+        public void onStart(final Observation.Context context) {
+            events.add("start:" + context.getName());
+            parents.add(context.getName() + "<-" + (context.getParentObservation() != null
+                    ? context.getParentObservation().getContextView().getName()
+                    : "null"));
+        }
+
+        @Override
+        public void onScopeOpened(final Observation.Context context) {
+            events.add("scope-opened:" + context.getName());
+        }
+
+        @Override
+        public void onError(final Observation.Context context) {
+            errors.add("error:" + context.getName() + ":" + context.getError().getMessage());
+        }
+
+        @Override
+        public void onScopeClosed(final Observation.Context context) {
+            events.add("scope-closed:" + context.getName());
+        }
+
+        @Override
+        public void onStop(final Observation.Context context) {
+            events.add("stop:" + context.getName());
+        }
+    }
+}
diff --git a/viewers/wicket/ui/src/main/java/module-info.java b/viewers/wicket/ui/src/main/java/module-info.java
index a9d5c439231..0f52e37d477 100644
--- a/viewers/wicket/ui/src/main/java/module-info.java
+++ b/viewers/wicket/ui/src/main/java/module-info.java
@@ -79,6 +79,7 @@
     exports org.apache.causeway.viewer.wicket.ui.components.actioninfo;
     exports org.apache.causeway.viewer.wicket.ui.components.collection.bulk;
     exports org.apache.causeway.viewer.wicket.ui.pages.entity;
+    exports org.apache.causeway.viewer.wicket.ui.observation;
     exports org.apache.causeway.viewer.wicket.ui.components.scalars.bool;
     exports org.apache.causeway.viewer.wicket.ui.pages.accmngt.register;
     exports org.apache.causeway.viewer.wicket.ui.components.collectioncontents.ajaxtable;
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CausewayModuleViewerWicketUi.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CausewayModuleViewerWicketUi.java
index 0d13297f211..121d05bcb52 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CausewayModuleViewerWicketUi.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CausewayModuleViewerWicketUi.java
@@ -41,4 +41,5 @@
 })
 public class CausewayModuleViewerWicketUi {
 
+    public static final String NAMESPACE = "causeway.viewer.wicket";
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanel.java
index ece05c56baa..9c3ca7c4b11 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanel.java
@@ -37,6 +37,8 @@
 import org.apache.causeway.viewer.wicket.ui.components.collection.CollectionPanel;
 import org.apache.causeway.viewer.wicket.ui.components.collection.selector.CollectionPresentationSelectorHelper;
 import org.apache.causeway.viewer.wicket.ui.components.collection.selector.CollectionPresentationSelectorPanel;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationBehavior;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationDescriptor;
 import org.apache.causeway.viewer.wicket.ui.panels.HasDynamicallyVisibleContent;
 import org.apache.causeway.viewer.wicket.ui.panels.PanelAbstract;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
@@ -81,6 +83,11 @@ public EntityCollectionPanel(final String id, final UiObjectWkt entityModel, fin
         super(id, entityModel);
 
         this.layoutData = layoutData;
+        WicketRenderObservationBehavior.addTo(this,
+                WicketRenderObservationDescriptor.collection(
+                        entityModel.getTypeOfSpecification().logicalTypeName(),
+                        entityModel.getTypeOfSpecification().logicalTypeName()
+                                + "#" + layoutData.getId()));
         this.div = new WebMarkupContainer(ID_COLLECTION_GROUP);
 
         selectedItemHintKey = ComponentHintKey.create(super.getMetaModelContext(),
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/fieldset/PropertyGroup.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/fieldset/PropertyGroup.java
index 45ccdf6cb87..e21f627d85f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/fieldset/PropertyGroup.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/fieldset/PropertyGroup.java
@@ -40,6 +40,8 @@
 import org.apache.causeway.viewer.wicket.model.models.UiObjectWkt;
 import org.apache.causeway.viewer.wicket.ui.components.actionmenu.entityactions.ActionLinksPanel;
 import org.apache.causeway.viewer.wicket.ui.components.scalars.ScalarPanelAbstract;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationBehavior;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationDescriptor;
 import org.apache.causeway.viewer.wicket.ui.panels.HasDynamicallyVisibleContent;
 import org.apache.causeway.viewer.wicket.ui.panels.PanelAbstract;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
@@ -69,6 +71,10 @@ public class PropertyGroup extends PanelAbstract<ManagedObject, UiObjectWkt> imp
     public PropertyGroup(final String id, final UiObjectWkt model, final FieldSet fieldSet) {
         super(id, model);
         this.fieldSet = fieldSet;
+        WicketRenderObservationBehavior.addTo(this,
+                WicketRenderObservationDescriptor.fieldset(
+                        model.getTypeOfSpecification().logicalTypeName(),
+                        fieldSet.getId()));
 
         // the UI is only ever built once.
         childComponents = buildGui();
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java
index 590d06bf7e0..65f3dd07def 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java
@@ -52,6 +52,8 @@
 import org.apache.causeway.viewer.wicket.ui.components.scalars.bool.BooleanPanel;
 import org.apache.causeway.viewer.wicket.ui.components.scalars.choices.ObjectChoicesSelect2Panel;
 import org.apache.causeway.viewer.wicket.ui.components.scalars.choices.ValueChoicesSelect2Panel;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationBehavior;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationPolicy;
 import org.apache.causeway.viewer.wicket.ui.panels.PanelAbstract;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt.EventTopic;
@@ -245,6 +247,9 @@ protected void setupFormatModifiers(final EnumSet<FormatModifier> modifiers) {}
     protected ScalarPanelAbstract(final String id, final ScalarModel scalarModel) {
         super(id, scalarModel);
 
+        WicketRenderObservationPolicy.propertyDescriptor(scalarModel)
+                .ifPresent(descriptor->WicketRenderObservationBehavior.addTo(this, descriptor));
+
         val formatModifiers = EnumSet.noneOf(FormatModifier.class);
         setupFormatModifiers(formatModifiers);
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java
index e08a01f9f3d..f9978800c20 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java
@@ -42,6 +42,8 @@
 import org.apache.causeway.viewer.wicket.ui.components.layout.bs.BSGridPanel;
 import org.apache.causeway.viewer.wicket.ui.components.scalars.ScalarFragmentFactory.FrameFragment;
 import org.apache.causeway.viewer.wicket.ui.exec.FormExecutorDefault;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationBehavior;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationPolicy;
 import org.apache.causeway.viewer.wicket.ui.pages.entity.EntityPage;
 import org.apache.causeway.viewer.wicket.ui.panels.PanelUtil;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
@@ -104,6 +106,8 @@ private ActionLink(
         this.commonContext = model.getMetaModelContext();
 
         this.where = where;
+        WicketRenderObservationPolicy.actionDescriptor(model, where)
+                .ifPresent(descriptor->WicketRenderObservationBehavior.addTo(this, descriptor));
         
         final boolean useIndicatorForNoArgAction = getSettings().isUseIndicatorForNoArgAction();
         this.indicatorAppenderIfAny =
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationBehavior.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationBehavior.java
new file mode 100644
index 00000000000..c1cd6c27bbb
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationBehavior.java
@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.wicket.ui.observation;
+
+import java.util.Objects;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.causeway.core.config.observation.CausewayObservationIntegration;
+import org.apache.causeway.core.config.observation.ObservationClosure;
+import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
+import org.apache.causeway.viewer.wicket.ui.CausewayModuleViewerWicketUi;
+
+/**
+ * Opens a semantic observation for the actual rendering of one Wicket component subtree.
+ *
+ * @since 2.2
+ */
+public final class WicketRenderObservationBehavior extends Behavior {
+
+    private static final long serialVersionUID = 1L;
+
+    public static <T extends Component> T addTo(
+            final T component,
+            final WicketRenderObservationDescriptor descriptor) {
+        component.add(new WicketRenderObservationBehavior(descriptor));
+        return component;
+    }
+
+    private final WicketRenderObservationDescriptor descriptor;
+    private transient ObservationClosure activeClosure;
+
+    public WicketRenderObservationBehavior(final WicketRenderObservationDescriptor descriptor) {
+        this.descriptor = Objects.requireNonNull(descriptor, "descriptor");
+    }
+
+    @Override
+    public void beforeRender(final Component component) {
+        if(activeClosure != null) {
+            throw new IllegalStateException("Wicket render observation lifecycle is already active");
+        }
+        if(!(component instanceof HasMetaModelContext)) {
+            return;
+        }
+
+        final CausewayObservationIntegration integration = ((HasMetaModelContext) component)
+                .lookupService(CausewayObservationIntegration.class)
+                .orElse(null);
+        if(integration == null || integration.isNoop()) {
+            return;
+        }
+
+        final ObservationClosure closure = new ObservationClosure();
+        closure.startAndOpenScope(descriptor.customize(integration.provider(
+                getClass(),
+                CausewayObservationIntegration.withModuleName(
+                        CausewayModuleViewerWicketUi.NAMESPACE))
+                .get(descriptor.getRegion().getObservationName())));
+
+        activate(closure);
+        try {
+            WicketRenderObservationTracker.register(component.getRequestCycle(), this, closure);
+        } catch (RuntimeException | Error ex) {
+            clearActiveClosure();
+            closure.onError(ex);
+            closure.close();
+            throw ex;
+        }
+    }
+
+    @Override
+    public void afterRender(final Component component) {
+        if(activeClosure != null) {
+            WicketRenderObservationTracker.complete(component.getRequestCycle(), this);
+        }
+    }
+
+    void activate(final ObservationClosure closure) {
+        activeClosure = closure;
+    }
+
+    void clearActiveClosure() {
+        activeClosure = null;
+    }
+
+    WicketRenderObservationDescriptor descriptor() {
+        return descriptor;
+    }
+
+    boolean isActive() {
+        return activeClosure != null;
+    }
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationDescriptor.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationDescriptor.java
new file mode 100644
index 00000000000..4187b86f35b
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationDescriptor.java
@@ -0,0 +1,111 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.wicket.ui.observation;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import io.micrometer.observation.Observation;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Serializable, instance-data-free description of a semantic Wicket render region.
+ *
+ * @since 2.2
+ */
+@Getter
+public final class WicketRenderObservationDescriptor implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final String OBJECT_TYPE_TAG = "causeway.object.type";
+
+    @RequiredArgsConstructor
+    @Getter
+    public enum Region {
+        PAGE("causeway.wicket.page.render", null),
+        FIELDSET("causeway.wicket.fieldset.render", "causeway.fieldset.id"),
+        PROPERTY("causeway.wicket.property.render", "causeway.property.id"),
+        COLLECTION("causeway.wicket.collection.render", "causeway.collection.id"),
+        ACTION("causeway.wicket.action.render", "causeway.action.id");
+
+        private final String observationName;
+        private final String memberTag;
+    }
+
+    public static WicketRenderObservationDescriptor page(final String objectType) {
+        return new WicketRenderObservationDescriptor(Region.PAGE, objectType, null);
+    }
+
+    public static WicketRenderObservationDescriptor fieldset(
+            final String objectType,
+            final String fieldsetId) {
+        return new WicketRenderObservationDescriptor(
+                Region.FIELDSET,
+                objectType,
+                fieldsetId != null && !fieldsetId.isEmpty() ? fieldsetId : "<default>");
+    }
+
+    public static WicketRenderObservationDescriptor property(
+            final String objectType,
+            final String propertyId) {
+        return new WicketRenderObservationDescriptor(Region.PROPERTY, objectType, propertyId);
+    }
+
+    public static WicketRenderObservationDescriptor collection(
+            final String objectType,
+            final String collectionId) {
+        return new WicketRenderObservationDescriptor(Region.COLLECTION, objectType, collectionId);
+    }
+
+    public static WicketRenderObservationDescriptor action(
+            final String objectType,
+            final String actionId) {
+        return new WicketRenderObservationDescriptor(Region.ACTION, objectType, actionId);
+    }
+
+    private final Region region;
+    private final String objectType;
+    private final String memberId;
+
+    private WicketRenderObservationDescriptor(
+            final Region region,
+            final String objectType,
+            final String memberId) {
+        this.region = Objects.requireNonNull(region, "region");
+        this.objectType = Objects.requireNonNull(objectType, "objectType");
+        if(region.getMemberTag() != null) {
+            this.memberId = Objects.requireNonNull(memberId, "memberId");
+        } else {
+            this.memberId = null;
+        }
+    }
+
+    public Observation customize(final Observation observation) {
+        Observation customized = observation
+                .contextualName(region.getObservationName())
+                .lowCardinalityKeyValue(OBJECT_TYPE_TAG, objectType);
+        if(region.getMemberTag() != null) {
+            customized = customized.lowCardinalityKeyValue(region.getMemberTag(), memberId);
+        }
+        return customized;
+    }
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationPolicy.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationPolicy.java
new file mode 100644
index 00000000000..dd91edab047
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationPolicy.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.wicket.ui.observation;
+
+import java.util.Optional;
+
+import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.viewer.commons.model.hints.RenderingHint;
+import org.apache.causeway.viewer.wicket.model.models.ActionModel;
+import org.apache.causeway.viewer.wicket.model.models.ScalarModel;
+import org.apache.causeway.viewer.wicket.model.models.ScalarPropertyModel;
+
+/**
+ * Centralizes the bounded member-level inclusion policy for Wicket render observations.
+ *
+ * @since 2.2
+ */
+public final class WicketRenderObservationPolicy {
+
+    private WicketRenderObservationPolicy() {}
+
+    public static Optional<WicketRenderObservationDescriptor> propertyDescriptor(
+            final ScalarModel scalarModel) {
+        if(!(scalarModel instanceof ScalarPropertyModel)
+                || scalarModel.getRenderingHint() != RenderingHint.REGULAR) {
+            return Optional.empty();
+        }
+        final var featureId = scalarModel.getMetaModel().getFeatureIdentifier();
+        return Optional.of(WicketRenderObservationDescriptor.property(
+                featureId.logicalTypeName(),
+                featureId.getLogicalIdentityString("#")));
+    }
+
+    public static Optional<WicketRenderObservationDescriptor> actionDescriptor(
+            final ActionModel actionModel,
+            final Where where) {
+        if(where != Where.OBJECT_FORMS || actionModel.getAssociatedParameter().isPresent()) {
+            return Optional.empty();
+        }
+        final var featureId = actionModel.getAction().getFeatureIdentifier();
+        return Optional.of(WicketRenderObservationDescriptor.action(
+                featureId.logicalTypeName(),
+                featureId.getLogicalIdentityString("#")));
+    }
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTracker.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTracker.java
new file mode 100644
index 00000000000..a0daae41424
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/observation/WicketRenderObservationTracker.java
@@ -0,0 +1,144 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.wicket.ui.observation;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.request.cycle.RequestCycle;
+
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.core.config.observation.ObservationClosure;
+
+/**
+ * Request-local ownership of render observations whose normal Wicket completion callback can be skipped.
+ *
+ * @since 2.2
+ */
+public final class WicketRenderObservationTracker {
+
+    private WicketRenderObservationTracker() {}
+
+    private static final MetaDataKey<State> STATE_KEY = new MetaDataKey<State>() {
+        private static final long serialVersionUID = 1L;
+    };
+
+    static void register(
+            final RequestCycle requestCycle,
+            final WicketRenderObservationBehavior owner,
+            final ObservationClosure closure) {
+        state(requestCycle).register(owner, closure);
+    }
+
+    static void complete(
+            final RequestCycle requestCycle,
+            final WicketRenderObservationBehavior owner) {
+        final State state = requestCycle.getMetaData(STATE_KEY);
+        if(state == null) {
+            owner.clearActiveClosure();
+            return;
+        }
+        state.complete(owner);
+        if(state.isEmpty()) {
+            requestCycle.setMetaData(STATE_KEY, null);
+        }
+    }
+
+    public static void cleanup(
+            final RequestCycle requestCycle,
+            final @Nullable Throwable failure) {
+        final State state = requestCycle.getMetaData(STATE_KEY);
+        requestCycle.setMetaData(STATE_KEY, null);
+        if(state != null) {
+            state.cleanup(failure);
+        }
+    }
+
+    static State state(final RequestCycle requestCycle) {
+        State state = requestCycle.getMetaData(STATE_KEY);
+        if(state == null) {
+            state = new State();
+            requestCycle.setMetaData(STATE_KEY, state);
+        }
+        return state;
+    }
+
+    static final class State {
+
+        private final Deque<ActiveObservation> activeObservations = new ArrayDeque<>();
+
+        void register(
+                final WicketRenderObservationBehavior owner,
+                final ObservationClosure closure) {
+            activeObservations.addLast(new ActiveObservation(owner, closure));
+        }
+
+        void complete(final WicketRenderObservationBehavior owner) {
+            ActiveObservation matching = null;
+            for (ActiveObservation active : activeObservations) {
+                if(active.owner == owner) {
+                    matching = active;
+                }
+            }
+            if(matching != null) {
+                activeObservations.remove(matching);
+                matching.close(null);
+            } else {
+                owner.clearActiveClosure();
+            }
+        }
+
+        void cleanup(final @Nullable Throwable failure) {
+            while(!activeObservations.isEmpty()) {
+                activeObservations.removeLast().close(failure);
+            }
+        }
+
+        boolean isEmpty() {
+            return activeObservations.isEmpty();
+        }
+    }
+
+    private static final class ActiveObservation {
+
+        private final WicketRenderObservationBehavior owner;
+        private final ObservationClosure closure;
+
+        private ActiveObservation(
+                final WicketRenderObservationBehavior owner,
+                final ObservationClosure closure) {
+            this.owner = owner;
+            this.closure = closure;
+        }
+
+        private void close(final @Nullable Throwable failure) {
+            try {
+                closure.onError(failure);
+            } finally {
+                try {
+                    closure.close();
+                } finally {
+                    owner.clearActiveClosure();
+                }
+            }
+        }
+    }
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/entity/EntityPage.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/entity/EntityPage.java
index b720bc01e82..c8c1ee81e4f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/entity/EntityPage.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/entity/EntityPage.java
@@ -41,6 +41,8 @@
 import org.apache.causeway.viewer.wicket.model.models.UiObjectWkt;
 import org.apache.causeway.viewer.wicket.model.util.PageParameterUtils;
 import org.apache.causeway.viewer.wicket.ui.components.entity.icontitle.EntityIconAndTitlePanel;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationBehavior;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationDescriptor;
 import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
 import org.apache.wicket.Application;
@@ -120,6 +122,9 @@ private EntityPage(
             final UiObjectWkt entityModel) {
         super(pageParameters, null/*titleString*/, UiComponentType.ENTITY);
         this.model = entityModel;
+        WicketRenderObservationBehavior.addTo(this,
+                WicketRenderObservationDescriptor.page(
+                        entityModel.getTypeOfSpecification().logicalTypeName()));
     }
 
     @Override
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java
index d97b5169e5c..cb74bdef0f4 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java
@@ -65,6 +65,7 @@
 import org.apache.causeway.viewer.wicket.model.models.HasCommonContext;
 import org.apache.causeway.viewer.wicket.model.models.PageType;
 import org.apache.causeway.viewer.wicket.ui.errors.ExceptionModel;
+import org.apache.causeway.viewer.wicket.ui.observation.WicketRenderObservationTracker;
 import org.apache.causeway.viewer.wicket.ui.pages.PageClassRegistry;
 import org.apache.causeway.viewer.wicket.ui.pages.error.ErrorPage;
 import org.apache.causeway.viewer.wicket.ui.pages.login.WicketSignInPage;
@@ -275,6 +276,8 @@ public void onRequestHandlerExecuted(final RequestCycle requestCycle, final IReq
     @Override
     public synchronized void onEndRequest(final RequestCycle requestCycle) {
 
+        WicketRenderObservationTracker.cleanup(requestCycle, null);
+
         if(log.isDebugEnabled()) {
             val metricsServiceIfAny = getMetaModelContext().lookupService(MetricsService.class);
             long took = timings.get().took();
@@ -299,14 +302,20 @@ public synchronized void onEndRequest(final RequestCycle requestCycle) {
 
     @Override
     public void onDetach(final RequestCycle requestCycle) {
-        // detach the current @RequestScope, if any
-        IRequestCycleListener.super.onDetach(requestCycle);
+        try {
+            WicketRenderObservationTracker.cleanup(requestCycle, null);
+        } finally {
+            // detach the current @RequestScope, if any
+            IRequestCycleListener.super.onDetach(requestCycle);
+        }
     }
 
 
     @Override
     public IRequestHandler onException(final RequestCycle cycle, final Exception ex) {
 
+        WicketRenderObservationTracker.cleanup(cycle, ex);
+
         if(log.isDebugEnabled()) {
             log.debug("onException {}  took: {}ms", ex.getClass().getSimpleName(), timings.get().took());
         }


(causeway) 03/03: CAUSEWAY-4058: archives Wicket render observations

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4058
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit e7961a5bdfc3782083cfc64739c6254234df247d
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Sep 23 06:40:10 2026 +0100

    CAUSEWAY-4058: archives Wicket render observations
---
 .../2026-09-23-add-wicket-region-render-spans}/.openspec.yaml       | 0
 .../2026-09-23-add-wicket-region-render-spans}/design.md            | 0
 .../2026-09-23-add-wicket-region-render-spans}/proposal.md          | 0
 .../specs/wicket-region-render-observation/spec.md                  | 0
 .../2026-09-23-add-wicket-region-render-spans}/tasks.md             | 0
 .../2026-09-23-add-wicket-region-render-spans}/validation.md        | 0
 .../specs/wicket-region-render-observation/spec.md                  | 6 +++++-
 7 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/openspec/changes/add-wicket-region-render-spans/.openspec.yaml b/openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/.openspec.yaml
similarity index 100%
rename from openspec/changes/add-wicket-region-render-spans/.openspec.yaml
rename to openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/.openspec.yaml
diff --git a/openspec/changes/add-wicket-region-render-spans/design.md b/openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/design.md
similarity index 100%
rename from openspec/changes/add-wicket-region-render-spans/design.md
rename to openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/design.md
diff --git a/openspec/changes/add-wicket-region-render-spans/proposal.md b/openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/proposal.md
similarity index 100%
rename from openspec/changes/add-wicket-region-render-spans/proposal.md
rename to openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/proposal.md
diff --git a/openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md b/openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
similarity index 100%
copy from openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
copy to openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
diff --git a/openspec/changes/add-wicket-region-render-spans/tasks.md b/openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/tasks.md
similarity index 100%
rename from openspec/changes/add-wicket-region-render-spans/tasks.md
rename to openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/tasks.md
diff --git a/openspec/changes/add-wicket-region-render-spans/validation.md b/openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/validation.md
similarity index 100%
rename from openspec/changes/add-wicket-region-render-spans/validation.md
rename to openspec/changes/archive/2026-09-23-add-wicket-region-render-spans/validation.md
diff --git a/openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md b/openspec/specs/wicket-region-render-observation/spec.md
similarity index 97%
rename from openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
rename to openspec/specs/wicket-region-render-observation/spec.md
index 4ddc06ddea4..7e4d8f05317 100644
--- a/openspec/changes/add-wicket-region-render-spans/specs/wicket-region-render-observation/spec.md
+++ b/openspec/specs/wicket-region-render-observation/spec.md
@@ -1,5 +1,8 @@
-## ADDED Requirements
+# wicket-region-render-observation Specification
 
+## Purpose
+TBD - created by archiving change add-wicket-region-render-spans. Update Purpose after archive.
+## Requirements
 ### Requirement: Entity-page render observation
 When observation is active, the Wicket viewer SHALL create one `causeway.wicket.page.render` observation for each server-side render of an entity page.
 The observation SHALL join the current Java-agent-owned trace and SHALL use the Wicket viewer module metadata convention.
@@ -96,3 +99,4 @@ It SHALL NOT be presented as covering component construction, initialization, co
 - **WHEN** component preparation completes before an instrumented region begins actual rendering
 - **THEN** the region render observation starts only for the actual render callback
 - **AND** operational documentation states that preparation time can remain outside region render spans
+