# Controllers

Controllers are ordinary Idris functions. Iwf does not require controller
classes or fixed action constructors. The canonical app groups handlers under
`Web/Controller/*` because that is easy to navigate.

## 1. A Handler

A normal HTML handler takes a `ControllerContext` and returns a `Response`:

```idris
home : ControllerContext -> Response
home context =
  render context homePage
```

A route with decoded input receives the input first:

```idris
articleShow : ArticleInput -> ControllerContext -> Response
articleShow input context =
  render context (articlePage input.slug)
```

## 2. ControllerContext

`ControllerContext` contains request-level framework state:

- the original request.
- the database pool for request-scoped database helpers.
- signed session data.
- flash messages.
- CSRF token.
- the current user, when the app runner can load one from the session.
- login path.
- access-denied handler.

You pass this context to helpers such as `render`, `renderFragment`, auth
checks, flash helpers, and CSRF helpers.

## 3. Render A Page

Use `render` for normal pages:

```idris
settings : ControllerContext -> Response
settings context =
  render context (settingsPage context emptySettingsForm [])
```

For direct browser requests, `render` returns the full HTML document. For htmx
HTML requests, it returns only the `#main-content` fragment plus out-of-band
flash updates. Requests that prefer JSON do not receive HTML fragments.

Use `renderFragment` for endpoints that are fragment-only but still need flash
and Auto Refresh handling.

Views return page HTML. The framework shell is added by `render` for full
browser requests.

## 4. Layouts

Shared layouts are configured once on the app. They receive the normalized page
and can add head entries, body attributes, or shell HTML around the main
content:

```idris
siteLayout : PageLayout
siteLayout context page =
  { pageHead := page.pageHead ++ [descriptionMeta page.pageTitle]
  , pageShellBefore := page.pageShellBefore ++ [<nav><a href="/login">Sign in</a></nav>]
  } page

dashboardView : ControllerContext -> Html
dashboardView context =
  fragment
    [ <title>Dashboard</title>
    , <section class="page">
        <h1>Dashboard</h1>
      </section>
    ]

dashboard : ControllerContext -> Response
dashboard context =
  render context (dashboardView context)

main : IO ()
main =
  runApp (app "My App" appRoutes |> withPageLayout siteLayout)
```

The layout is applied to full browser responses. HTMX responses return only the
page's main content plus out-of-band shell updates.

## 5. Responses

Common response helpers:

- `okText`
- `okHtml`
- `okJson`
- `badRequest`
- `notFound`
- `methodNotAllowed`
- `serverError`
- `redirectTo`
- `seeOther`
- `created`
- `respondNoContent`

Use `seeOther` for normal POST/redirect/GET:

```idris
saveSettings : SettingsForm -> ControllerContext -> ControllerApp Response
saveSettings input context =
  pure
    (flashInContext "notice" "Settings saved" context
      (seeOther (pathTo settingsRoute)))
```

Use HTMX helpers when the browser needs a client-side event or target change:

- `htmxTrigger`
- `htmxCloseModal`
- `htmxHardRedirect`
- `htmxRefresh`
- `htmxRetarget`

Use `htmxHardRedirect` only when the whole shell must reload, for example after
logout.

## 6. Early Returns

Controller actions run in `ControllerApp`, so guards and failure helpers can
stop the action without hand-written `Either` branches:

```idris
showArticle : ArticleSlugInput -> ControllerContext -> ControllerApp Response
showArticle input context = do
  result <- findArticle input.slug context
  maybeArticle <- requireRight result
  article <- requireFound maybeArticle
  pure (render context (articlePage article))
```

Common helpers:

- `respondNow`
- `redirectNow`
- `abortBadRequest`
- `abortNotFound`
- `abortForbidden`
- `failValidation`
- `failDb`
- `requireRight`
- `requireFound`
- `requireValid`

## 7. Access Control

For one handler:

```idris
settings : ControllerContext -> ControllerApp Response
settings context = do
  user <- requireCurrentUser context
  pure (render context (settingsPage context user))
```

For grouped route hooks, import the advanced controller and route modules
explicitly so raw dispatch policy stays out of ordinary handlers.

## Next

Read [Views and IDRX](views-and-idrx.md).
