markwalet / markwalet/nova-modal-response

v2: interactive modal responses (action dispatch + form blocks)

Open
#69 0 comments 0 reactions 1 assignee View on GitHub

@markwalet is already working on this.

Since May 27, 2026.

enhancement
Dominant language
PHP
Stars
17
Forks
9
PR merge metrics
No merged PRs in 30d

Description

# Interactive modal responses

Umbrella for turning the modal response from a **one-shot render** into an **interactive round-trip**: buttons inside the modal that dispatch Nova actions and re-render with the result. High-level overview only — progress tracked via the sub-issues below.

## Why

Today a `ModalResponse` is terminal: an action runs server-side, returns a payload, and `ModalActionResponse.vue` renders the stack once. There is no path back to the server from inside the modal. CONTEXT.md deliberately reserved this gap (the link block "does not trigger a Nova action"). This work fills it.

Motivating flow: *preview an API request → press Execute → see the response in the same modal* — and the more general "press a button, dispatch an action, render what comes back."

## Shape

Two new blocks, sharing one **dispatch core**:

- **`Block::action($label?, ActionClass)`** — a fieldless dispatcher. Throws if the target action declares `fields()` (use a form instead). Inlineable (an atom) — safe by construction. Label defaults to the action's name.
- **`Block::form($label?, ActionClass)`** — renders the target action's `fields()` inline (essentially Nova's confirm-action modal, inline), collects values, submits. Block-level, not an atom. Submit label defaults to the action's `confirmButtonText`.

### Core mechanism (shared)

- **Single-modal constraint**: Nova is not built to stack multiple modals on top of each other, so we don't. **Dispatching any action always closes the current modal.** If the action returns a modal response, it opens in place of the old one (close-then-open); if it returns a non-modal response (toast, redirect, download, openInNewTab), the modal still closes and Nova handles the rest.
- **Intercept**: the Vue side POSTs to Nova's action endpoint itself, captures the response, and drives the outcome — chosen over deferring to Nova's runner, because deferring forfeits the close-then-open sequencing the form block also needs.
- **Origin context**: every dispatch runs against the parent modal's resource + selection, captured server-side at serialize time.
- **Failed dispatch**: surface the error via `Nova.error` and leave the modal open so the user can retry — the only case where the modal stays.
- **Confirmation guard**: honor the action's `confirmText` + a `->confirm()` knob (interception otherwise bypasses Nova's confirm — matters most for fieldless actions).
- **Resource reload**: emit Nova's action-completed event so the resource view refreshes; `->withoutReload()` opt-out.

## Phasing

1. **Action block** (fieldless) — ships the whole dispatch core + default behavior end-to-end.
2. **Form block** (inline fields) — gated by a feasibility spike on rendering Nova action fields detached from `ConfirmActionModal`.

## Security

Action uriKey + resource + ids ride the (tamperable) payload, but Nova's endpoint still enforces `authorizedToRun` + validation — no privilege escalation.

## Out of scope (for now)

- Server-set `->withInputs()` on the action block (selection-only); arbitrary-resource / standalone targets; client-only buttons (copy-to-clipboard, tabs).
- **Return-to-previous-modal after dispatch** (#86) — tracked as a separate explore ticket. Today the user re-triggers the originating resource action manually if they want to get back.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.