# `PhoenixKitProjects.Web.PopupHostLive`
[🔗](https://github.com/BeamLabEU/phoenix_kit_projects/blob/v0.14.0/lib/phoenix_kit_projects/web/popup_host_live.ex#L1)

Opinionated wrapper LV that pairs the `<.popup_host>` function
component with the emit-mode PubSub contract.

A host app mounts this LV (typically via `live_render`) and gets
popup-driven UX for free — no host-side `handle_info` subscription,
no modal-stack state management, no frame-ref bookkeeping.

## What it does

1. Subscribes to a host-supplied PubSub topic on connect.
2. Optionally renders a "root view" inline (the always-visible LV;
   `OverviewLive` is the typical pick) by passing `session["root_view"]`.
3. On `{:projects, :opened, ...}` — pushes a frame onto the modal
   stack and renders the target LV inside a `<dialog>` overlay.
4. On `{:projects, :closed | :saved | :deleted, %{frame_ref: ref}}`
   — pops the top frame iff `ref` matches (race-safe against stale
   events).
5. Generates a unique `frame_ref` per push and stamps it into the
   child LV's session along with `mode: "emit"` and the host topic,
   so the child's own emits flow back through this LV.
6. Caps stack depth at `session["max_stack_depth"]` (defaults to 5;
   accepts an integer in `1..20`, anything outside that band resets
   to the default) to prevent runaway recursion if a misbehaved LV
   emits `:opened` on every mount.

## Session contract

- `"pubsub_topic"` (required) — PubSub topic string. The host owns
  the topic name; this LV does not invent it (so two embeds on the
  same page can use different topics if needed).
- `"root_view"` (optional) — `%{"lv" => "Module.Name", "session" =>
  %{...}}`. The always-visible LV. `:lv` is whitelist-validated.
- `"wrapper_class"` (optional) — outer div class. Defaults to
  `"flex flex-col w-full"`.
- `"max_stack_depth"` (optional) — positive integer in `1..20`
  overriding the default 5-frame cap. Values outside that band are
  clamped to the default with a logged warning.
- `"modal_box_class"` (optional) — daisyUI `modal-box` sizing
  overrides. Defaults to `"w-11/12 max-w-6xl"` (91% viewport,
  capped at 72rem). Pass a different size class (`"max-w-4xl"`,
  `"max-w-7xl"`, etc.) if a host page wants a narrower or wider
  modal.

## Example: dashboard root view (`OverviewLive`)

For a dedicated admin page that lists projects/tasks/templates
with modal-stacked detail views:

    live "/orders/:id/projects", MyApp.OrderProjectsLive

    # ... and in MyApp.OrderProjectsLive's render:
    {Phoenix.Component.live_render(@socket, PhoenixKitProjects.Web.PopupHostLive,
       id: "projects-popup-host",
       session: %{
         "pubsub_topic" => "host:orders:" <> @order_id,
         "root_view" => %{
           "lv" => "PhoenixKitProjects.Web.OverviewLive",
           "session" => %{
             "wrapper_class" => "flex flex-col w-full px-4 py-6 gap-6"
           }
         }
       })}

## Example: single project show inside a host record's edit page

The common host-app shape — a host record (order, ticket, etc.)
has one linked project and the edit page embeds its detail view:

    {Phoenix.Component.live_render(@socket, PhoenixKitProjects.Web.PopupHostLive,
       id: "embedded-project-host-#{@host_record.uuid}",
       session: %{
         "pubsub_topic" => "host:foo:" <> @host_record.uuid,
         "root_view" => %{
           "lv" => "PhoenixKitProjects.Web.ProjectShowLive",
           "session" => %{"id" => @host_record.project_uuid}
         },
         "locale" => @locale
       })}

Whenever the embedded LV (or anything it transitively opens) emits
`:opened`, this LV renders the target inside a modal on the host's
existing page. No URL change. No DOM replacement.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
