Suprnova's Inertia bridge has one rule: the props a handler sends and the
props a component receives are the same type, and a machine checks it.

On the Rust side, a page's props are a struct:

```rust
#[derive(InertiaProps)]
pub struct DocsPageProps {
    pub groups: Vec<DocsGroup>,
    pub chapter: DocsChapterProps,
}
```

`inertia_response!("docs/Index", DocsPageProps { .. })` refuses to compile
if `frontend/src/pages/docs/Index.vue` doesn't exist - dead page references
are a build error, not a 3 a.m. discovery.

Then `suprnova generate-types` walks every `InertiaProps` struct and emits
TypeScript interfaces:

```ts
export interface DocsPageProps {
  groups: Array<DocsGroup>;
  chapter: DocsChapterProps;
}
```

Rename a Rust field and the TypeScript compiler points at every component
that needs updating. `suprnova serve` runs the generator as a watcher, so
the loop is: edit the struct, save, watch vue-tsc light up.

## Nested types ride along

The generator resolves nested DTOs transitively - any struct your project
defines gets a precise named interface when a prop reaches it, whether or
not it derives `InertiaProps` or `Data`. A plain `struct DocsGroup` two
levels down still comes out as `Array<DocsGroup>`, not `unknown`. Only
types the generator genuinely can't see into - external crate types,
enums, tuple structs - degrade to `unknown`, and each one warns loudly:

```text
⚠ Prop type `serde_json::Value` (referenced by `AdminProps.payload`)
  isn't a struct this project defines - emitting `unknown`.
```

This site is built with Suprnova, and the whole surface - docs, blog,
community, resources, admin - runs on exactly this pipeline. The generated
file is committed, so a type drift shows up in review as a diff, not as a
runtime surprise.

Read more in the [Frontend chapter](/docs/frontend) of the manual.