# `MishkaCmsCore.Runtime.Compilers.LiveViewCompiler`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/compilers/live_view_compiler.ex#L5)

Runtime compiler for Phoenix LiveView modules with dynamic content compilation.

This module provides comprehensive functionality to dynamically compile Phoenix LiveView modules
at runtime from configuration data. It generates complete LiveView modules with proper template
compilation, event handling, lifecycle management, and metadata for CMS pages.

## Features

- **Dynamic LiveView module generation** from configuration parameters
- **HEEx template compilation** with Phoenix LiveView integration
- **Event handler compilation** for interactive functionality (`handle_event/3`)
- **Info handler compilation** for message processing (`handle_info/2`)
- **Page metadata and information** functions for runtime introspection
- **Helper function integration** for custom page logic
- **SEO and schema metadata** with customizable tags
- **Multi-site page support** with site-specific configurations
- **Template rendering utilities** for runtime template processing

## LiveView Module Structure

Generated LiveView modules include:
- `__page_information__/0` - Returns complete page metadata
- `__page_information__/1` - Returns specific metadata by key or path
- `render/1` - Main template rendering function
- `templates/1` - Template collection function
- `handle_event/3` - Event handlers (if defined)
- `handle_info/2` - Message handlers (if defined)
- Helper functions (if defined in configuration)
- Proper Phoenix LiveView and MishkaCMS integration

## Basic Usage

The primary entry point is `build_module/1` which takes page configuration
and returns a complete AST ready for compilation:

    params = %{
      title: "Home Page",
      page_module: MyApp.Pages.Home,
      template: ~S'''
      <div class="home-page">
        <h1><%= @title %></h1>
        <p>Welcome to our site!</p>

        <button phx-click="increment">
          Count: <%= @count %>
        </button>

        <%= for item <- @items do %>
          <div class="item"><%= item.name %></div>
        <% end %>
      </div>
      ''',
      events: [
        %{
          name: "increment",
          code: ~S'''
          count = (socket.assigns[:count] || 0) + 1
          {:noreply, assign(socket, :count, count)}
          '''
        }
      ],
      site: "main_site",
      site_id: 1
    }

    ast = LiveViewCompiler.build_module(params)
    MishkaCmsCore.Runtime.Compilers.ModuleCompiler.compile_module(ast)

## Page Information Access

Once compiled, pages provide comprehensive metadata access:

    # Get complete page information
    info = MyApp.Pages.Home.__page_information__()

    # Access specific sections
    page_data = MyApp.Pages.Home.__page_information__(:page)
    permissions = MyApp.Pages.Home.__page_information__(:acl)

    # Access nested data with dot notation
    site_id = MyApp.Pages.Home.__page_information__("private.site_id")
    schema = MyApp.Pages.Home.__page_information__("page.seo.schema")

## SEO and Schema Metadata

Pages support comprehensive SEO and structured data:

    seo_tags: [
      %{name: "description", content: "Page description"},
      %{name: "keywords", content: "keyword1, keyword2"},
      %{property: "og:title", content: "Social Media Title"},
      %{property: "og:image", content: "https://example.com/image.png"}
    ]

    schema: [
      %{
        "@context" => "https://schema.org",
        "@type" => "Article",
        "headline" => "Article Title",
        "author" => %{"@type" => "Person", "name" => "John Doe"},
        "datePublished" => "2024-01-15"
      }
    ]

## Helper Functions in Pages

Helper functions provide custom logic within pages:

    helpers: [
      %{
        name: "calculate_discount",
        args: "price, discount_percent",
        code: ~S'''
        discount_amount = price * (discount_percent / 100)
        price - discount_amount
        '''
      },
      %{
        name: "render_status_badge",
        args: "status",
        code: ~S'''
        class = case status do
          "active" -> "badge-success"
          "pending" -> "badge-warning"
          "inactive" -> "badge-danger"
          _ -> "badge-secondary"
        end

        ~H"""
        <span class={"badge " <> class}>
          <%= String.capitalize(status) %>
        </span>
        """
        '''
      }
    ]

## Error Handling and Safety

The compiler includes comprehensive error handling:

- **Template compilation errors** are caught and reported with context
- **Event handler compilation** validates syntax before module creation
- **Catch-all handlers** prevent crashes from unhandled events/messages
- **Safe code evaluation** with proper error boundaries
- **Runtime template rendering** with graceful error handling

## Performance Considerations

- **Template compilation** is performed once during module compilation
- **Event handlers** are compiled as efficient pattern-matched functions
- **Helper functions** are compiled as regular module functions
- **Metadata access** uses compiled functions for optimal performance
- **Template rendering utilities** use optimized Phoenix engines

## Multi-Site Page Management

Pages support multi-site configurations:

    # Site-specific page configuration
    params = %{
      title: "Site A Home",
      page_module: MyApp.Pages.SiteA.Home,
      template: site_a_template,
      site: "site_a",
      site_id: 1,
      host: "sitea.example.com"
    }

# `build_assigns`

Builds page information and metadata functions.

Creates AST for the `__page_information__` functions that provide comprehensive
access to page metadata, private information, ACL permissions, and extra data.
These functions are essential for runtime introspection and page management.

## Parameters

- `params` - Atomized page configuration map containing all page data

## Returns

Returns a tuple `{params, functions_ast}` where:
- `params` - Original parameters (passed through for pipeline)
- `functions_ast` - List containing the page information function ASTs

## Generated Functions

The generated module will contain these information functions:

### `__page_information__/0`
Returns complete page metadata as a structured map with sections:
- `:page` - Core page data (title, path, language, content, SEO, schema)
- `:private` - Private metadata (IDs, module info, site data, whether the page reads the
  signed-in user — see `MishkaCmsCore.Runtime.Compilers.UserClosure`)
- `:acl` - Access control permissions
- `:extra` - Additional custom metadata

### `__page_information__/1` (atom key)
Returns specific top-level section by atom key.

### `__page_information__/1` (list of keys)
Returns nested data using a list of keys for deep access.

### `__page_information__/1` (dot-notation string)
Returns nested data using dot-notation string paths like "private.site_id".

## Page Information Structure

    %{
      page: %{
        title: "Page Title",
        path: "/pages/example",
        section: "blog",
        language: "en",
        language_group_id: "0192f1d5-…",
        timestamp: ~N[2024-01-15 10:30:00],
        content: "page content",
        seo: %{
          description: "Shown by search engines and link previews",
          meta_tags: [%{name: "description", content: "..."}],
          schema: [%{"@type" => "Article", "headline" => "..."}]
        }
      },
      private: %{
        id: 123,
        page_module: MyApp.Pages.Example,
        priority: 0,
        site: "main_site",
        site_id: 1,
        host: "example.com",
        format: :heex,
        reads_user: false
      },
      acl: %{permissions: ["page_view", "page_edit"]},
      extra: %{analytics_id: "page_123", cache_ttl: 3600}
    }

## Examples

    # After compilation, access page information:
    info = MyPage.__page_information__()
    page_data = MyPage.__page_information__(:page)
    site_id = MyPage.__page_information__("private.site_id")
    seo_tags = MyPage.__page_information__([:page, :seo, :meta_tags])

## Error Handling

The string-based accessor includes error handling for invalid paths,
returning `nil` when the path cannot be resolved or atoms don't exist.

This function is typically called as the first stage in the compilation pipeline.

# `build_event_handlers`

Builds event handler functions for LiveView interactivity.

Compiles event handler definitions into `handle_event/3` functions that respond
to user interactions like clicks, form submissions, and keyboard events.

## Parameters

- `{params, functions_ast}` - Tuple containing:
  - `params` - Page configuration including `:events` list
  - `functions_ast` - Existing function ASTs from previous pipeline stages

## Returns

Returns updated tuple `{params, functions_ast}` with event handler functions added.

## Event Handler Format

Each event handler should be defined as a map with:
- `:name` - Event name as string (required)
- `:code` - Handler code as Elixir code string (required)

## Examples

    events: [
      %{
        name: "increment",
        code: ~S'''
        current_count = socket.assigns[:count] || 0
        {:noreply, assign(socket, :count, current_count + 1)}
        '''
      },
      %{
        name: "save_form",
        code: ~S'''
        case MyApp.Forms.save(params) do
          {:ok, result} ->
            {:noreply,
             socket
             |> put_flash(:info, "Saved successfully!")
             |> assign(:data, result)}

          {:error, changeset} ->
            {:noreply, assign(socket, :changeset, changeset)}
        end
        '''
      },
      %{
        name: "delete_item",
        code: ~S'''
        item_id = String.to_integer(params["id"])

        case MyApp.Items.delete(item_id) do
          :ok ->
            items = Enum.reject(socket.assigns.items, &(&1.id == item_id))
            {:noreply, assign(socket, :items, items)}

          {:error, _reason} ->
            {:noreply, put_flash(socket, :error, "Failed to delete item")}
        end
        '''
      }
    ]

## Generated Functions

Each event definition creates a `handle_event/3` function:

    def handle_event("increment", _params, socket) do
      current_count = socket.assigns[:count] || 0
      {:noreply, assign(socket, :count, current_count + 1)}
    end

## Template Usage

Event handlers are triggered from templates using Phoenix LiveView bindings:

    <button phx-click="increment">+1</button>

    <form id="save-form" phx-submit="save_form">
      <input type="text" name="title" />
      <button type="submit">Save</button>
    </form>

    <button phx-click="delete_item" phx-value-id={item.id}>
      Delete
    </button>

## Available Variables

Event handler code has access to:
- `params` - Event parameters from the client
- `socket` - The LiveView socket with assigns and state
- All imported functions from Phoenix.LiveView
- Helper functions defined in the same module

## Return Values

Event handlers must return valid LiveView responses:
- `{:noreply, socket}` - Update socket and re-render
- `{:reply, map, socket}` - Send reply to client and update socket
- `{:noreply, socket, timeout}` - Update with timeout

## Catch-All Handler

A catch-all `handle_event/3` is automatically added that:
- Handles any unmatched events gracefully
- Returns `{:noreply, socket}` to prevent crashes
- Can be enhanced with logging in future versions

## Error Handling and Safety

- **Syntax validation** - Event code is parsed for syntax errors during compilation
- **Runtime errors** - Errors in event handlers are caught by Phoenix LiveView
- **Parameter validation** - Access to `params` should include nil checks
- **Socket state** - Always return valid socket to maintain LiveView state

## Performance Considerations

- Event handlers are compiled as efficient pattern-matched functions
- No runtime overhead for event dispatch
- Direct access to socket assigns without serialization
- Optimized for high-frequency user interactions

This function is called as the second stage in the compilation pipeline.

# `build_helpers`

Builds helper functions for custom page logic.

Processes helper function definitions from the page configuration and compiles
them into the LiveView module. Helper functions provide custom logic that can be
called from within page templates and event handlers.

## Parameters

- `{params, functions_ast}` - Tuple containing:
  - `params` - Page configuration including `:helpers` list
  - `functions_ast` - Existing function ASTs from previous pipeline stages

## Returns

Returns updated tuple `{params, functions_ast}` with helper functions added.

## Helper Function Format

Each helper function should be defined as a map with:
- `:name` - Function name as string (required)
- `:args` - Function arguments as string (required)
- `:code` - Function body as Elixir code string (required)

## Examples

    helpers: [
      %{
        name: "format_date",
        args: "date, format \\ :short",
        code: ~S'''
        case format do
          :short -> Calendar.strftime(date, "%m/%d/%Y")
          :long -> Calendar.strftime(date, "%B %d, %Y")
          :iso -> Date.to_iso8601(date)
          custom when is_binary(custom) -> Calendar.strftime(date, custom)
          _ -> Date.to_string(date)
        end
        '''
      },
      %{
        name: "calculate_total",
        args: "items, tax_rate \\ 0.08",
        code: ~S'''
        subtotal = Enum.reduce(items, 0, fn item, acc ->
          acc + (item.price * item.quantity)
        end)

        tax = subtotal * tax_rate
        subtotal + tax
        '''
      },
      %{
        name: "render_status_indicator",
        args: "status",
        code: ~S'''
        {class, icon, text} = case String.downcase(status) do
          "active" -> {"text-green-600", "check-circle", "Active"}
          "pending" -> {"text-yellow-600", "clock", "Pending"}
          "inactive" -> {"text-red-600", "x-circle", "Inactive"}
          "processing" -> {"text-blue-600", "refresh", "Processing"}
          _ -> {"text-gray-600", "question-mark-circle", "Unknown"}
        end

        ~H"""
        <div class={"flex items-center space-x-2 " <> class}>
          <svg class="w-4 h-4" data-icon={icon}>
            <!-- Icon SVG would be here -->
          </svg>
          <span><%= text %></span>
        </div>
        """
        '''
      },
      %{
        name: "generate_pagination",
        args: "current_page, total_pages, window \\ 2",
        code: ~S'''
        start_page = max(1, current_page - window)
        end_page = min(total_pages, current_page + window)

        pages = Enum.to_list(start_page..end_page)

        %{
          current: current_page,
          total: total_pages,
          pages: pages,
          prev: if(current_page > 1, do: current_page - 1),
          next: if(current_page < total_pages, do: current_page + 1),
          has_prev: current_page > 1,
          has_next: current_page < total_pages
        }
        '''
      }
    ]

## Template Usage

Helper functions can be called directly from templates:

    template = ~S'''
    <div class="order-summary">
      <h2>Order Summary</h2>
      <div class="date">
        Order Date: <%= format_date(@order.inserted_at, :long) %>
      </div>

      <div class="items">
        <%= for item <- @order.items do %>
          <div class="item">
            <span><%= item.name %></span>
            <span>$<%= item.price %> x <%= item.quantity %></span>
          </div>
        <% end %>
      </div>

      <div class="total">
        Total: $<%= calculate_total(@order.items, @tax_rate) %>
      </div>

      <div class="status">
        <%= render_status_indicator(@order.status) %>
      </div>
    </div>
    '''

## Event Handler Usage

Helper functions can also be called from event handlers:

    events: [
      %{
        name: "change_page",
        code: ~S'''
        new_page = String.to_integer(params["page"])
        pagination = generate_pagination(new_page, socket.assigns.total_pages)

        {:noreply,
         socket
         |> assign(:current_page, new_page)
         |> assign(:pagination, pagination)}
        '''
      }
    ]

## Advanced Helper Examples

    helpers: [
      # Helper with pattern matching and guards
      %{
        name: "safe_divide",
        args: "a, b",
        code: ~S'''
        cond do
          b == 0 -> {:error, :division_by_zero}
          is_number(a) and is_number(b) -> {:ok, a / b}
          true -> {:error, :invalid_arguments}
        end
        '''
      },

      # Helper with HEEx components
      %{
        name: "render_breadcrumbs",
        args: "breadcrumbs",
        code: ~S'''
        breadcrumbs
        |> Enum.with_index()
        |> Enum.map(fn {crumb, index} ->
          if index == length(breadcrumbs) - 1 do
            ~H"<span class='breadcrumb-current'><%= crumb.title %></span>"
          else
            ~H"""
            <a href={crumb.url} class='breadcrumb-link'>
              <%= crumb.title %>
            </a>
            """
          end
        end)
        |> Enum.intersperse(~H" / ")
        '''
      },

      # Helper with external API calls
      %{
        name: "get_weather",
        args: "location",
        code: ~S'''
        case MyApp.WeatherAPI.get_current(location) do
          {:ok, weather} ->
            %{
              temperature: weather.temp,
              description: weather.description,
              icon: weather.icon_url
            }

          {:error, _} ->
            %{
              temperature: "N/A",
              description: "Weather unavailable",
              icon: "/images/weather-unknown.svg"
            }
        end
        '''
      }
    ]

## Function Compilation

Helper functions are compiled as regular module functions with:
- **Full Elixir syntax support** - Use any valid Elixir code
- **Access to imports** - Phoenix.Component, HEEx, etc.
- **Pattern matching** - Multiple function heads supported
- **Guard clauses** - When conditions supported
- **Default arguments** - Using `\\` syntax
- **Private scope** - Helper functions are public within the LiveView module

## Error Handling

- **Syntax errors** in helper code will prevent module compilation
- **Invalid arguments** will cause compilation failures with detailed messages
- **Missing dependencies** in helper code will be reported during compilation
- **Runtime errors** in helpers will be caught and logged during template rendering

## Performance Considerations

- Helper functions are compiled once during module compilation
- No runtime overhead for function calls within templates
- Complex helpers should be optimized for template rendering performance
- Consider caching expensive computations within helper implementations

## Security Considerations

- Helper functions have full access to the module's context
- Validate inputs in helpers that process user data
- Be cautious with helpers that make external API calls
- Use proper escaping for helpers that return HTML content

This function is called as the fifth stage in the compilation pipeline.

# `build_info_handlers`

Builds info handler functions for LiveView message processing.

Compiles info handler definitions into `handle_info/2` functions that respond
to messages sent to the LiveView process from other parts of the system.

## Parameters

- `{params, functions_ast}` - Tuple containing:
  - `params` - Page configuration including `:infos` list
  - `functions_ast` - Existing function ASTs from previous pipeline stages

## Returns

Returns updated tuple `{params, functions_ast}` with info handler functions added.

## Info Handler Format

Each info handler should be defined as a map with:
- `:name` - Message pattern as string (required)
- `:code` - Handler code as Elixir code string (required)

## Examples

    infos: [
      %{
        name: ":refresh_data",
        code: ~S'''
        data = MyApp.DataSource.get_latest()
        {:noreply, assign(socket, :data, data)}
        '''
      },
      %{
        name: "{:user_updated, user}",
        code: ~S'''
        if socket.assigns.user_id == user.id do
          {:noreply, assign(socket, :user, user)}
        else
          {:noreply, socket}
        end
        '''
      },
      %{
        name: "{:broadcast, :data_changed, payload}",
        code: ~S'''
        case payload do
          %{type: "products"} ->
            products = MyApp.Products.list_products()
            {:noreply, assign(socket, :products, products)}

          %{type: "users"} ->
            users = MyApp.Users.list_users()
            {:noreply, assign(socket, :users, users)}

          _ ->
            {:noreply, socket}
        end
        '''
      },
      %{
        name: "{:timeout, ref, :periodic_update}",
        code: ~S'''
        # Schedule next update
        timer_ref = Process.send_after(self(), {:timeout, make_ref(), :periodic_update}, 30_000)

        # Update data
        stats = MyApp.Stats.get_current()

        {:noreply,
         socket
         |> assign(:stats, stats)
         |> assign(:timer_ref, timer_ref)}
        '''
      }
    ]

## Generated Functions

Each info definition creates a `handle_info/2` function with pattern matching:

    def handle_info(:refresh_data, socket) do
      data = MyApp.DataSource.get_latest()
      {:noreply, assign(socket, :data, data)}
    end

    def handle_info({:user_updated, user}, socket) do
      if socket.assigns.user_id == user.id do
        {:noreply, assign(socket, :user, user)}
      else
        {:noreply, socket}
      end
    end

## Message Sending

Info handlers can be triggered from anywhere in the system:

    # From controllers, contexts, or other processes
    send(live_view_pid, :refresh_data)
    send(live_view_pid, {:user_updated, updated_user})

    # From other LiveViews
    Phoenix.PubSub.broadcast(MyApp.PubSub, "data_updates",
      {:broadcast, :data_changed, %{type: "products"}})

    # Scheduled messages
    Process.send_after(self(), {:timeout, make_ref(), :periodic_update}, 5000)

## Common Use Cases

### Real-time Updates
```elixir
infos: [
  %{
    name: "{:live_update, data}",
    code: ~S'''
    {:noreply, assign(socket, :live_data, data)}
    '''
  }
]
```

### PubSub Integration
```elixir
# In mount/3 or event handler:
Phoenix.PubSub.subscribe(MyApp.PubSub, "updates")

# Info handler:
infos: [
  %{
    name: "{:update_notification, payload}",
    code: ~S'''
    {:noreply, put_flash(socket, :info, payload.message)}
    '''
  }
]
```

### Periodic Tasks
```elixir
infos: [
  %{
    name: ":tick",
    code: ~S'''
    # Schedule next tick
    Process.send_after(self(), :tick, 1000)

    # Update timestamp
    {:noreply, assign(socket, :current_time, DateTime.utc_now())}
    '''
  }
]
```

## Available Variables

Info handler code has access to:
- `socket` - The LiveView socket with assigns and state
- Pattern-matched message variables (e.g., `user` in `{:user_updated, user}`)
- All imported functions from Phoenix.LiveView
- Helper functions defined in the same module

## Return Values

Info handlers must return valid LiveView responses:
- `{:noreply, socket}` - Update socket and re-render
- `{:noreply, socket, timeout}` - Update with timeout for hibernation

## Catch-All Handler

A catch-all `handle_info/2` is automatically added that:
- Handles any unmatched messages gracefully
- Returns `{:noreply, socket}` to prevent crashes
- Can be enhanced with logging in future versions

## Error Handling and Safety

- **Pattern matching** - Use specific patterns to avoid unexpected messages
- **Guard clauses** - Add guards for additional message validation
- **Defensive coding** - Check socket assigns before accessing nested data
- **Message validation** - Validate message payload structure in handler code

## Performance Considerations

- Info handlers are compiled as efficient pattern-matched functions
- Direct message passing without serialization overhead
- Optimized for high-frequency real-time updates
- Process mailbox efficiency depends on message pattern specificity

This function is called as the third stage in the compilation pipeline.

# `build_module`

Builds a complete LiveView module AST from configuration parameters.

This is the primary entry point for the LiveView compilation process. It orchestrates
the entire compilation pipeline to transform page configuration into a complete
Phoenix LiveView module AST ready for compilation.

## Parameters

- `params` - Map containing page configuration with keys:
  - `:title` - Page title/name (required)
  - `:page_module` - Target module name (required)
  - `:template` - HEEx template string (required)
  - `:path` - Page path/URL (optional)
  - `:section` - Page section/category (optional)
  - `:events` - List of event handler definitions (optional)
  - `:infos` - List of info handler definitions (optional)
  - `:helpers` - List of helper function definitions (optional)
  - `:description` - The page's meta description, stored as `page.seo.description` (optional)
  - `:seo_tags` - List of SEO meta tags (optional)
  - `:schema` - List of structured data schema (optional)
  - `:site`, `:site_id`, `:host` - Site identification (optional)
  - `:permissions` - Access control permissions (optional)
  - `:extra` - Additional metadata (optional)
  - `:priority` - Page priority for ordering (optional)
  - `:format` - Template format (:heex, default)

## Returns

Returns the complete AST for the LiveView module that can be compiled using
`MishkaCmsCore.Runtime.Compilers.ModuleCompiler.compile_module/1`.

## Examples

    # Basic interactive page
    params = %{
      title: "Counter Page",
      page_module: MyApp.Pages.Counter,
      template: ~S'''
      <div>
        <h1>Count: <%= @count %></h1>
        <button phx-click="increment">+</button>
        <button phx-click="decrement">-</button>
      </div>
      ''',
      events: [
        %{name: "increment", code: "{:noreply, update(socket, :count, &(&1 + 1))}"},
        %{name: "decrement", code: "{:noreply, update(socket, :count, &(&1 - 1))}"}
      ]
    }

    ast = build_module(params)

    # Complex dashboard page
    params = %{
      title: "Analytics Dashboard",
      page_module: MyApp.Pages.Dashboard,
      template: dashboard_template,
      events: dashboard_events,
      infos: dashboard_message_handlers,
      helpers: dashboard_helpers,
      seo_tags: [%{name: "description", content: "Analytics dashboard"}]
    }

    ast = build_module(params)

## Compilation Pipeline

The function processes page data through these stages:
1. **Assigns building** - Creates page information and metadata functions
2. **Page forms** - Lifts the template's own forms into `__page_forms__/0` and generates their
   change and submit handlers
3. **Event handlers** - Compiles interactive event handling functions
4. **Info handlers** - Compiles message processing functions
5. **Render functions** - Compiles template and creates render functions
6. **Helper functions** - Processes and compiles helper functions
7. **Module creation** - Assembles final module AST with imports

Each stage adds specific functionality to the final compiled LiveView module.

# `build_page_forms`

Compiles the page's own forms into `__page_forms__/0`, and generates the change and submit
handlers for every form whose event names the author has NOT declared.

The template is parsed ONCE PER COMPILE — a save, not a keystroke — so a page never re-reads its
own markup to answer an event. The specs are put back into `params` under `:page_forms` for the
same reason: `build_renders/1` needs nothing from them but "does this page have a form", and asking
`PageForms.declared/1` again for that boolean tokenized, parsed and walked the whole page template a
second time to re-derive a fact this stage already holds.

An author who declares an event of the same name on the Events tab suppresses generation for it
and owns the clause outright; they keep the whole seam by calling
`MishkaCmsCore.Runtime.PageForm.validate/4` or `MishkaCmsCore.Runtime.PageForm.submit/5` from
their own body. Generation is skipped rather than the clause being shadowed, because two
`handle_event/3` heads with the same name would make the author's unreachable and their page would
silently stop responding.

The author's entries are first renamed with `MishkaCmsCore.Runtime.PageBuilder.PageForms.follow/2`
and put back into `params[:events]`, so an entry stored under a renamed form's old tagged event
compiles under the form's current one, and an entry for a deleted form never answers a new form
that happens to reuse its seed.

Runs before `build_event_handlers/1`, which appends the author's clauses and then the catch-all,
so every generated clause is reachable and the catch-all still answers everything else.

## Generated Functions

    def __page_forms__, do: [%{assign: :contact_form, as: "contact", ...}]

    def handle_event("contact-k7m2q-validate", params, socket) do
      {:noreply, PageForm.validate(socket, __page_forms__(), "contact-k7m2q-validate", params)}
    end

    def handle_event("contact-k7m2q-submit", params, socket) do
      PageForm.submit(socket, __page_forms__(), "contact-k7m2q-submit", params)
    end

# `build_page_messages`

Gives a page the `handle_event/3` clauses for whatever server actions it uses.

## What the author did

Picked an action in the builder's On click panel — "Show a message" is core's one — which wrote a
`JS.push(…)` into the element's `phx-click`. That is the whole of the client half, and it is the
same push a developer would write by hand.

## Why the registry rather than a name

Because the list is not core's to close. `MishkaCmsCore.Runtime.PageBuilder.Interactions` collects
every action through `builder:interactions`, and each server one names the event it pushes and the
`{module, function}` that answers it — so an installed package's action compiles into a page the
same way core's does, with nothing here knowing that package exists.

The module named is the action's own RUNTIME module, never the editor code that wrote the click:
what goes here is compiled into every published page using the action.

## Why every page, and not only the ones whose markup carries the push

Because a page's markup is not where the click has to be. An author who puts the button inside a
COMPONENT saves it in the component's markup; the page that uses that component says only
`<.component component_name="promo-card" />`, and the word the gate was looking for is nowhere in
it. The card rendered, the button rendered, the visitor clicked, and the event arrived at a module
with no clause for it — swallowed by the catch-all, nothing on screen, nothing in the log. The same
is true of a button in a layout, and of one on an error page.

Looking harder was not an option worth the price: the page would have to load and scan every
component it names, on every compile, to answer a question whose wrong answer is silence.

So the clause is always there. A `handle_event/3` that nothing pushes costs nothing — it is three
lines of bytecode in a module that already exists, no process, no query, and never runs. An author
who declares an event of that name on the Events tab still suppresses it, exactly as they do for a
form's: theirs is the more specific answer, and generating a second clause would make it
unreachable.

# `build_page_state`

Compiles the page's `state` into the assigns it starts with — see
`MishkaCmsCore.Runtime.PageState`, which the mounting LiveView calls.

## Generated

    def __page_state__, do: %{count: 0, roles: roles()}
    def __page_state_keys__, do: [:count, :roles]

`__page_state__/0` is evaluated each time a visitor mounts the page, so an expression may call the
page's helpers and a timestamp is fresh. `__page_state_keys__/0` is a literal, read without running
any of it. Both are emitted whether or not anything is declared.

Each value passes through the same signature check as a handler: unsigned code, where signing is
on, compiles to `nil`. The names become atoms here, at compile time, from rows that passed
`MishkaCmsCore.Runtime.Resources.Validations.PageStateNames`; anything that is not a name is
skipped rather than minted.

# `build_renders`

Builds render functions for LiveView template rendering.

Compiles the HEEx template and creates render functions that handle template
rendering within the LiveView lifecycle. The functions integrate with Phoenix
LiveView's rendering system and provide access to compiled templates.

## Parameters

- `{params, functions_ast}` - Tuple containing:
  - `params` - Page configuration with template and metadata
  - `functions_ast` - Existing function ASTs from previous pipeline stages

## Returns

Returns updated tuple `{params, functions_ast}` with render functions added.

## Generated Functions

### `render/1` - Main template rendering function
The primary render function that Phoenix LiveView calls to render the page.
Takes assigns and returns the rendered template.

    def render(assigns) when is_map(assigns) do
      [primary] = templates(assigns)
      primary
    end

### `templates/1` - Template collection function
Returns all compiled templates for the page. Currently returns a single
primary template but structured to support multiple templates in the future.

    def templates(assigns) when is_map(assigns) do
      [compiled_template]
    end

## Template Compilation

Templates are compiled using Phoenix's HEEx engine with:
- **XSS protection** through automatic escaping
- **LiveView integration** for dynamic updates
- **Component support** for MishkaCMS runtime components
- **Proper error handling** with detailed compilation feedback

## Template Structure

The compiled template has access to:
- **All assigns** - LiveView socket assigns
- **Helper functions** - Custom page helper functions
- **MishkaCMS components** - Runtime compiled components
- **Phoenix components** - Standard Phoenix UI components

## Example Templates

    # Interactive template with event bindings
    template = ~S'''
    <div class="page">
      <h1><%= @title %></h1>

      <div class="counter">
        <button phx-click="decrement">-</button>
        <span><%= @count %></span>
        <button phx-click="increment">+</button>
      </div>

      <div class="items">
        <%= for item <- @items do %>
          <div class="item" id={"item-#{item.id}"}>
            <h3><%= item.name %></h3>
            <p><%= item.description %></p>
            <button phx-click="delete" phx-value-id={item.id}>
              Delete
            </button>
          </div>
        <% end %>
      </div>
    </div>
    '''

    # Template with helper functions and components
    template = ~S'''
    <div class="dashboard">
      <header>
        <h1><%= format_title(@page_title, @site_name) %></h1>
        <div class="stats">
          <%= render_stats(@statistics) %>
        </div>
      </header>

      <main>
        <.component name="data_table"
                   data={@table_data}
                   columns={@columns} />
      </main>
    </div>
    '''

## Template Engine Configuration

The compilation uses these settings:
- **Engine**: Phoenix.LiveView.TagEngine for HEEx support
- **Tag Handler**: Phoenix.LiveView.HTMLEngine for proper HTML handling
- **File Context**: Generated file name for debugging
- **Source Trimming**: Enabled for cleaner output
- **Line Tracking**: Accurate line numbers for errors

## Error Handling

Template compilation errors include:
- Syntax errors with line numbers
- Invalid HEEx expressions
- Missing closing tags
- Malformed component calls
- Undefined variable references

Errors are propagated up with full context for debugging.

## Integration with LiveView Lifecycle

The render function integrates with Phoenix LiveView's rendering cycle:
1. **Socket assigns** are passed to render/1
2. **Template compilation** happens once during module compilation
3. **Dynamic updates** are handled by LiveView's diff engine
4. **Component integration** allows nested component rendering

## Performance Considerations

- Template compilation is performed once during module compilation
- Render function is optimized for repeated calls
- LiveView's diff engine minimizes DOM updates
- Helper function calls are direct module function calls

This function is called as the fourth stage in the compilation pipeline.

# `build_uploads`

Compiles the page's upload declarations into `__page_uploads__/0`, for the LiveView that mounts it
to register.

`allow_upload/3` is a `Phoenix.LiveView` function, and until this stage a compiled page had
nowhere to call one: the compiler emits no `mount/3`, so `@uploads` never existed and
`chelekom-file-field`'s live mode — the one stateful capability the kit already ships — had
nothing to be handed. A live component cannot supply it either; `allow_upload/3` is not one of
its functions.

## Why the module only says what it wants, and registers nothing

A compiled page is not the LiveView that renders it: `MishkaCmsWeb.RuntimeLive` mounts, compiles
the module and then calls its `render/1` and `handle_event/3`. So a `mount/3` or an `on_mount`
hook emitted here would never run, and `@uploads` would never exist — which is how this was first
written and what a page on the dev site said, in a 500 reading `key :uploads not found`.

`MishkaCmsCore.Runtime.PageUpload.allow/2` is called by the mounting LiveView and reads this.

## Generated

    def __page_uploads__, do: [%{name: :attachment, accept: "image/*", max_entries: 2}]

Emitted whether or not anything is declared, so a reader can ask without `function_exported?/3`.
The name is turned into an atom HERE, at compile time, and never at runtime: the set of atoms is
then bounded by what authors have saved rather than by what visitors send.

# `confirmable`

```elixir
@spec confirmable(String.t() | nil, [map()] | nil, boolean() | nil) ::
  String.t() | nil
```

The template a page really compiles: its own markup, with the confirmation region in front of it
when the page declares a form.

Public because THE CLASS SCANNER HAS TO READ THE SAME STRING. Tailwind matches class-shaped text in
the `.template` files `MishkaCmsCore.Runtime.Handlers.BootHandler.create_template_file/4` writes,
and that file was built from the STORED template while the region was prepended on the way into
`Phoenix.LiveView.TagEngine.compile/2` — so `bg-emerald-50`, `max-w-3xl`, `shadow-lg` and the rest
existed in no scanned file, no rule was generated for them, and the confirmation rendered as bare
unstyled body text. Same class of bug as an attribute's default: markup that only ever exists after
the scanner has read the file.

`specs` is what `build_page_forms/1` already read out of the same template. Passing `nil` re-reads
it, which is what a caller holding only a stored template has to do.

# `create_module_ast`

Creates the final LiveView module AST with all components assembled.

This is the final stage of the compilation pipeline that assembles all generated
function ASTs into a complete LiveView module definition with proper imports and structure.

## Parameters

- `{params, functions_ast}` - Tuple containing:
  - `params` - Page configuration with `:page_module` target
  - `functions_ast` - All compiled function ASTs from previous stages

## Returns

Returns the complete module AST ready for compilation.

## Generated Module Structure

The final module includes:

```elixir
defmodule TargetModule do
  use MishkaCmsWeb, :live_view  # whatever :web_module names
  import MishkaCmsCore.Runtime.LiveViewHelpers, only: [component: 1, component: 2]

  # Page information functions
  def __page_information__() do ... end
  def __page_information__(key) when is_atom(key) do ... end
  def __page_information__(keys) when is_list(keys) do ... end
  def __page_information__(key) when is_binary(key) do ... end

  # Event handlers
  def handle_event(event, params, socket) do ... end

  # Info handlers
  def handle_info(message, socket) do ... end

  # Render functions
  def render(assigns) when is_map(assigns) do ... end
  def templates(assigns) when is_map(assigns) do ... end

  # Helper functions (if defined)
  def helper_function_name(args) do ... end
end
```

## Module Dependencies

The generated module automatically includes:

- **MishkaCmsWeb :live_view** - Full Phoenix LiveView functionality
- **MishkaCmsCore.Runtime.LiveViewHelpers** - Runtime component access

These imports enable the LiveView to:
- Use LiveView features like event handling and socket management
- Access Phoenix.Component functionality and HEEx templates
- Call runtime-compiled MishkaCMS components
- Handle user interactions and real-time updates

## Example Generated Module

    defmodule MyApp.Pages.ProductList do
      use MishkaCmsWeb, :live_view  # whatever :web_module names
      import MishkaCmsCore.Runtime.LiveViewHelpers, only: [component: 1, component: 2]

      def __page_information__() do
        %{
          page: %{title: "Product List", path: "/products"},
          private: %{site: "main", format: :heex},
          acl: %{permissions: []},
          extra: nil
        }
      end

      def handle_event("search", %{"query" => query}, socket) do
        results = MyApp.Products.search(query)
        {:noreply, assign(socket, :results, results)}
      end

      def render(assigns) do
        # Compiled template code here
      end

      def format_price(price) do
        # Helper function code here
      end
    end

## Integration Points

The module structure ensures compatibility with:
- **Phoenix LiveView** - Standard LiveView module pattern
- **Phoenix Router** - Can be mounted with `live/3` macro
- **MishkaCMS Runtime** - Component and page system integration
- **Phoenix Components** - Full component library access
- **Elixir OTP** - Standard module loading and hot code swapping

## Compilation Process

After this function returns the AST:

    ast = create_module_ast({params, functions_ast})
    {:ok, module} = MishkaCmsCore.Runtime.Compilers.ModuleCompiler.compile_module(ast)

The module is then available for use in routes, testing, and the application.

## LiveView Lifecycle Integration

The generated module works seamlessly with Phoenix LiveView's lifecycle:

    # In router
    live "/dashboard", MyApp.Pages.Dashboard

    # LiveView handles mount, events, and rendering automatically
    # Page information is available for metadata and SEO
    # Event handlers respond to user interactions
    # Info handlers process real-time updates

This is the final stage in the compilation pipeline and produces the
complete LiveView module ready for runtime compilation and usage.

# `form_handler_sample`

```elixir
@spec form_handler_sample(:validate | :submit, String.t()) :: String.t()
```

The default code for a form's event on the Events tab: print the params, then run
`form_handler_source/2`, so the form keeps working.

# `form_handler_source`

```elixir
@spec form_handler_source(:validate | :submit, String.t()) :: String.t()
```

The body of a form's generated `handle_event/3` clause, as source: `kind` is `:validate` or
`:submit`, and `event` is the event name the clause matches, which is also how the body finds its
form in `__page_forms__/0`.

# `helper_args`

```elixir
@spec helper_args(String.t() | nil) :: [Macro.t()]
```

The quoted argument list of a helper whose `args` is `args`, for
`def name(unquote_splicing(…))`: `""` or `nil` is no argument, `"assigns"` one,
`"assigns, suffix"` two, defaults included.

Read as the argument list of a call, so `args` must parse as one. Pages, layouts and error pages
all build their helpers' heads with it.

    LiveViewCompiler.helper_args("")
    #=> []

    LiveViewCompiler.helper_args("assigns, size")
    #=> [{:assigns, [line: 1], nil}, {:size, [line: 1], nil}]

# `make_env`

Creates compilation environment for template and code processing.

Builds a macro environment with proper imports and context for HEEx template
compilation and code evaluation. This environment determines what modules and
functions are available within compiled templates and code.

## Parameters

- `imports` - List of modules to import (default: `[]`)

## Returns

Returns a `Macro.Env` struct configured for compilation with the specified imports.

## Environment Configuration

The environment includes:
- **Base Environment** - Standard Elixir compilation environment
- **Dynamic Imports** - Configurable module imports
- **Error Handling** - Strict error handling for import failures

## Import Processing

Each import is processed with:
1. **Module availability check** - Verifies module is loaded
2. **Safe import definition** - Uses `Macro.Env.define_import/3`
3. **Strict error handling** - Raises on import failures

## Common Usage

    # Template compilation environment
    env = make_env([Phoenix.Component, Phoenix.HTML])

    # Code evaluation environment
    env = make_env([Phoenix.LiveView, MyApp.Helpers])

## Error Handling

Import failures will raise exceptions with detailed error information.
This ensures that compilation fails early if required modules are not available.

# `render_html_template`

```elixir
@spec render_html_template(String.t(), map()) :: String.t() | {:error, term()}
```

Renders `template` with the classic `Phoenix.HTML.Engine` (not HEEx), matching how the runtime
serves error-page templates (`MishkaCmsCore.Runtime.Compilers.ErrorCompiler` compiles them with
the same engine). Unlike `render_template/2`, this engine does not tokenize HTML structurally, so
it allows EEx interpolation inside attribute values (`content="<%= ... %>"`) — which a
server-rendered root layout relies on and which HEEx rejects.

Returns the rendered HTML string, or `{:error, message}` on failure.

# `render_template`

```elixir
@spec render_template(String.t(), map()) :: String.t() | {:error, term()}
```

Renders isolated HEEx template with assigns for runtime operations not page compiling.

## Example

    render_template("<div>{@name}</div>", %{name: "John"})
    #=> "<div>John</div>"

    render_template("<.link patch='/users'>{@text}</.link>", %{text: "Users"})
    #=> "<a href="/users" data-phx-link="patch" data-phx-link-state="push">Users</a>"

# `render_template!`

```elixir
@spec render_template!(String.t(), map()) :: String.t()
```

Renders a HEEx template with assigns, raises on error.

# `shows_messages?`

```elixir
@spec shows_messages?(map() | nil) :: boolean()
```

Whether a layout already shows messages — what `confirmable/3` takes as its third argument.

## Asked of the parsed template

Because a wrong yes is a visitor who is told NOTHING, and this used to be
`String.contains?(template, "@flash")` — which said yes to a layout that merely MENTIONED it: in an
HTML comment, inside a string, in a `<pre>` showing somebody how to write one. No spelling of that
check tells those apart, because the difference is not in the characters. So the template is
tokenised and its expressions are parsed, and an assign is a node rather than a run of text.

## The two answers that are not about a template

`nil` is a page with no layout of its own, which `MishkaCmsWeb.RuntimeLive` renders inside
`Layouts.app` — and that carries a `<.flash_group>`, so it counts as a layout that shows messages.

Anything unreadable counts as one that does not, so the page keeps its own region: being told twice
is a blemish, being told nothing is the bug.

# `untouched_handler?`

```elixir
@spec untouched_handler?(:validate | :submit, term(), term()) :: boolean()
```

Whether `code` is `form_handler_source/2` or `form_handler_sample/2` for `event` as written,
whitespace aside. A quoted name carrying the same token and kind as `event` counts as `event`, so
an untouched body still reads as untouched after its form is renamed.

---

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