# PkApp: the app frame over the module catalog

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from Components/PkApp.razor. Do not edit: change the source and run the generator.

A hand-written component that wraps `PkAppFrame`. Register modules with `services.AddPlainKitApp(a => a.Brand("Demo").Module<OrdersModule>().Home("orders").Footer("..."))` (each module is an `IPkModule` whose `Manifest` is a `PkModuleManifest`; a bad or duplicate id throws at startup) and put the router body inside `PkApp`. It draws the brand, the module picker (with more than one module), the side nav of the active module, search over its nav titles, the breadcrumb (`PkNavModel.Locate`), the footer and a theme toggle (`IPkTheme`). On each route change, once the new page has rendered, it makes one bridge call (`routeChanged`) that sets the document title (`<page> - <brand>`), moves focus to the page `h1` (else `#pk-main`) and announces "<page>, page loaded" in a polite live region it creates: the SDK app own behaviour, so give each page one `h1`. Pages stay ordinary `@page` components under `/<moduleId>/...`.

```razor
<Router AppAssembly="typeof(App).Assembly">
    <Found Context="route"><PkApp><RouteView RouteData="route" /></PkApp></Found>
</Router>
```

## `PkApp`

| Parameter | Type | Description |
|---|---|---|
| `ChildContent` | `RenderFragment?` | The page: the router's body. |

## `PkRouteGuard`

Wrap the router body: `<PkModuleBoundary><PkRouteGuard><RouteView RouteData="route" /></PkRouteGuard></PkModuleBoundary>` (the boundary outside, so it also catches a failed module download). Each new page asks the module of the address (`IPkModuleGuard.CanAsync(PkModuleContext)`, implemented by the module; a UX guard only, authorization stays `[Authorize]` on the server) and shows a not-allowed state when it answers false. A registered `IPkModuleLoader` runs first (WebAssembly lazy assemblies: `PkLazyModuleLoader` over `LazyAssemblyLoader` for an `IPkLazyModule`, also wired as the router's `OnNavigateAsync` through `LoadForPathAsync`; a failed load throws to the boundary and is retried on the next navigation). `PkModuleBoundary` shows an error with a Try again button when a page throws, and the frame stays.

| Parameter | Type | Description |
|---|---|---|
| `ChildContent` | `RenderFragment?` | The page: the router's body. |

## `PkModuleDashboard`

A dashboard composed from what the modules contribute (#1296). A module lists widgets under `contributes` in its manifest JSON (`key`, `for` surfaces, `tab`, `label`, `kind` stat or chart, `size`, `order`, `needs`) and implements `IPkWidgets.LoadWidget(key)` on its class, returning the data a stat or chart draws (never markup; a throw shows that card's error state). The page names only the surface: `<PkModuleDashboard Surface="overview" Heading="Overview" Tabs="@tabs" />`. `PkDashboardSurface.Compose` orders the widgets (`order`, then module position, then declaration) and adds an implicit tab for one the route did not declare; `PkDashboardPage` owns the cards, tabs, filters and lazy start. Only a composed widget of a registered module is answered. Route curation (`order`, `allow`, `deny`) is not carried over yet.

| Parameter | Type | Description |
|---|---|---|
| `Surface` | `string` | The surface id the modules' `contributes` name in `for`. |
| `Heading` | `string?` | The page heading. |
| `Tabs` | `IReadOnlyList<PkDashboardTab>?` | The tabs of this dashboard (the route owns the labels); a contribution naming another tab adds one titled by its id. |
