# Proute
Proute generates Elm Land-inspired routing code for Lustre apps, including
SPAs and server components, from Gleam page file paths.
The source of truth is the page tree:
```text
src/public/pages/home_.gleam
src/public/pages/games.gleam
src/public/pages/games/id_.gleam
src/public/pages/teams/slug_.gleam
```
Mounts are derived from config. `output_root` defaults to
`src/generated/proute`, so apps only set it when they want a different generated
module subtree:
```toml
[proute]
pages_root = "src/server"
[[proute.mounts]]
name = "public"
route_root = "/"
[[proute.mounts]]
name = "admin"
```
Each mount gets a conventional page shared-state type. For the example above,
`public` defaults to `public/page_shared_state.PublicPageSharedState` and
`admin` defaults to `admin/page_shared_state.AdminPageSharedState`. Set
`page_shared_state_type` on a mount only when the app keeps that type somewhere
else.
That config resolves to:
```text
public pages = src/server/public/pages
public routes = src/generated/proute/public/routes.gleam
public glue = src/generated/proute/public/pages.gleam
public input = src/generated/proute/public/page_input.gleam
public root = /
admin pages = src/server/admin/pages
admin routes = src/generated/proute/admin/routes.gleam
admin glue = src/generated/proute/admin/pages.gleam
admin input = src/generated/proute/admin/page_input.gleam
admin root = /admin
```
Generated route modules expose a route type, path parser, path builders, absolute URL builders with an explicit origin, and route-specific helpers:
```gleam
pub type Route {
Home
Games
GamesId(id: String)
TeamsSlug(slug: String)
NotFound
}
pub fn parse_path(path: String) -> Route
pub fn route_to_path(route: Route) -> String
pub fn route_to_url(route route: Route, origin origin: String) -> String
pub fn games_id_path(id id: String) -> String
```
## Example
[Rally Scoreboard](https://github.com/pairshaped/rally-scoreboard-example) is the canonical example for
Proute in a Rally app. It uses Proute-generated public and admin mounts under
`src/generated/proute/**`, with page shared state, generated route params, and
Rally-generated load, SSR, browser, and broadcast glue layered on top.
## Inspiration
Proute is inspired by Elm Land-style file routes and by [sporto/gleam-roundabout](https://github.com/sporto/gleam-roundabout). Roundabout’s generated path helpers and validation discipline are especially good ideas. Proute keeps a different source of truth: page files instead of a route DSL.
[Elm Land’s page conventions](https://elm.land/concepts/pages.html) are the main
routing inspiration: page files map to URL paths, trailing underscores mark
dynamic routes, `home_` represents the mount root, and `not_found_` reserves the
custom 404 page. `all_` is reserved for future catch-all routes, but it is not
generated yet. Elm Land's generated
[Route helpers](https://elm.land/concepts/route.html) also shape the goal:
removed pages should break callers at compile time.
Page modules must expose the conventional Lustre API that generated
`pages.gleam` calls:
```gleam
pub type Model
pub type Message
pub fn initial_model(page_shared_state, query_params) -> Model
pub fn update(model, msg) -> #(Model, Effect(Message))
pub fn view(model) -> Element(Message)
```
Dynamic routes receive generated route params between page shared state and query
params:
```gleam
pub fn initial_model(page_shared_state, route_params, query_params) -> Model
```
Pages may also expose `init` with the same inputs when they need page-specific
startup effects:
```gleam
pub fn init(page_shared_state, query_params) -> #(Model, Effect(Message))
pub fn init(page_shared_state, route_params, query_params) -> #(Model, Effect(Message))
```
Use `init` for client-side escape hatches such as browser APIs, local storage,
focus, measurement, or page-local event listeners. Most Rally pages should omit
it and let generated load glue layer data onto `initial_model`.
Pages that need app dependencies during update may use:
```gleam
pub fn update(page_shared_state, model, msg) -> #(Model, Effect(Message))
```
Generated-code snapshot tests are part of the intended implementation approach.
They make the generated API reviewable and keep accidental output churn visible.