diff --git a/docs/plans/2026-05-22-execution-tree-node-modal-design.md b/docs/plans/2026-05-22-execution-tree-node-modal-design.md
new file mode 100644
index 0000000..4ce989b
--- /dev/null
+++ b/docs/plans/2026-05-22-execution-tree-node-modal-design.md
@@ -0,0 +1,91 @@
+# Execution-Tree Node Detail Modal (Design)
+
+**Date:** 2026-05-22
+**Status:** Validated — ready for implementation planning.
+
+## Problem
+
+On the Central UI execution-chain tree page (`/audit/execution-tree`, the
+`ParentExecutionId` feature's Task 10), each node represents one execution and
+shows a small inline summary. The only interaction is the short-id link, which
+navigates away to `/audit/log?executionId=…`. There is no way to inspect an
+execution's actual audit rows without leaving the tree.
+
+## Decision
+
+Double-clicking a tree node opens a **modal** showing that execution's audit
+rows. The modal mirrors the `/audit/log` detail experience: a list of the
+execution's rows, and clicking a row reveals that row's full field/payload
+detail — the exact content the Audit Log drilldown drawer shows.
+
+Resolved during brainstorming:
+- **Modal content** — the execution's audit rows, with per-row full detail.
+- **Multi-row executions** — list the rows; clicking one shows its detail. A
+ single-row execution opens straight to the detail view.
+- **Trigger** — double-click anywhere on the node. The short-id link keeps its
+ single-click navigation to the Audit Log grid (unchanged).
+
+### Considered and rejected
+
+- **Reuse `AuditDrilldownDrawer` directly.** The drawer renders one
+ `AuditEvent` by design; bending it into a list-or-detail hybrid is more
+ invasive to a well-tested component than a purpose-built modal.
+- **Inline expansion under the node.** The user asked for a modal, and an
+ inline panel inside the recursive tree fights the existing expand/collapse
+ toggle and is visually messy.
+
+## Components
+
+| Component | Change |
+|---|---|
+| `AuditEventDetail.razor` | **New.** The single-`AuditEvent` field/payload/drill-in-button block, extracted verbatim from `AuditDrilldownDrawer`'s body. |
+| `AuditDrilldownDrawer.razor` | **Modified.** Keeps its offcanvas chrome + close button; its body becomes ``. The one refactor with regression risk — existing drawer bUnit + Playwright tests guard it. |
+| `ExecutionDetailModal.razor` (+ `.razor.cs` + `.razor.css`) | **New.** A custom Bootstrap modal — hand-rolled `modal` / `modal-backdrop` markup, Blazor-toggled, no component framework (the same way `AuditDrilldownDrawer` hand-rolls `offcanvas`). |
+| `ExecutionTree.razor` / `.razor.cs` | **Modified.** `@ondblclick` on the node body invokes a new `OnNodeActivated` `EventCallback`; recursive child instances re-raise it upward so the event bubbles to the root. |
+| `ExecutionTreePage.razor` / `.razor.cs` | **Modified.** Hosts one `ExecutionDetailModal`; wires the tree's `OnNodeActivated` to open it. |
+
+No database, repository, or service changes — purely Central UI. The
+`IAuditLogQueryService.QueryAsync` method already filters by `ExecutionId`; the
+modal reuses it (no new service method).
+
+## Data flow
+
+1. Double-click a node → `ExecutionTree` invokes `OnNodeActivated(node.ExecutionId)`.
+2. The event bubbles up the recursive `ExecutionTree` instances to
+ `ExecutionTreePage`.
+3. The page opens `ExecutionDetailModal` with the `ExecutionId`.
+4. The modal calls `IAuditLogQueryService.QueryAsync(new AuditLogQueryFilter(ExecutionId: id), new AuditLogPaging(PageSize: 100))` → `IReadOnlyList`.
+5. Render by row count:
+ - **≥ 2 rows** — a compact row list (kind / status / target / time, each row a button); clicking a row swaps to its `` with a "← Back to rows" control.
+ - **1 row** — opens straight to the detail view.
+ - **0 rows** — a stub execution; a friendly empty state.
+6. Close via the X button, the backdrop, or Esc.
+
+The list rows are full `AuditEvent` objects (that is what `QueryAsync` returns),
+so the list→detail transition needs no second fetch.
+
+## Error handling
+
+- A `QueryAsync` failure surfaces an inline error inside the modal ("Couldn't
+ load this execution's rows") and never tears down the SignalR circuit —
+ mirroring the tree page's existing `try/catch` degrade-gracefully pattern.
+- An empty result renders the friendly empty state, not an error.
+
+## Testing
+
+- **bUnit** — `ExecutionTree` raises `OnNodeActivated` on `@ondblclick` and
+ bubbles it through a nested instance; `ExecutionDetailModal` list renders from
+ a fake query service, row click → detail, 1-row jump-straight, 0-row empty
+ state, close; `AuditEventDetail` renders the field block; the existing
+ `AuditDrilldownDrawer` tests stay green after the body extraction.
+- **Playwright** — on `/audit/execution-tree`, double-click a node → modal opens
+ → (multi-row) row list → click a row → detail → close. Uses a seeded chain.
+- `frontend-design` skill for the modal markup/CSS — clean corporate aesthetic,
+ custom Blazor + Bootstrap, no component frameworks.
+
+## Constraints
+
+- Central UI only — no DB / repository / service-contract changes.
+- Custom Blazor + Bootstrap; no component frameworks.
+- The short-id link's single-click navigation to `/audit/log?executionId=…` is
+ unchanged.