# $pageAdmin

## Import

```typescript
import { $pageAdmin } from "@alepha/ui/admin";
```

## Overview

`$pageNav` already parented to `AdminRouter`'s `/admin` shell: the
one-call form of "a page inside the shared admin shell". It exists because
the alternative (injecting `AdminRouter` and writing
`parent: this.admin.layout` by hand on every page) puts the rules of that
composition nowhere an author will read them.

It carries the `$` prefix to sit with the framework's other declarations at
a call site, but it is a plain function wrapping `$pageNav` (which in turn
wraps `$page`) rather than a `createPrimitive` primitive. Nothing about
its lifecycle differs from declaring `$page` yourself.

The page appears in the shell's sidebar with no separate registration
step: `useNavEntries` walks the parent chain and reads each page's own
`nav`, the same as any other page hung off `AdminRouter.layout`.

**Its tab reads `Admin - <title>`**, the way every built-in page's does: a
static `head.title` or the title a `head` function returns is prefixed,
with the prefix in the reader's language (`admin.title`). The title itself
is the application's to translate: a static one is used as written.
When the page names no `nav.label` and no `label`, its unprefixed static
title becomes the `label`, so its sidebar entry and breadcrumb do not carry
the prefix.

**Calling this registers `AdminRouter`.** Declaring even one page this way
mounts the whole `/admin` shell, including its fourteen built-in pages (Users,
Sessions, Jobs, …): an admin page without the admin shell around it is
not a thing. This is the intended reading, but it is a real side effect:
an application that wants `/admin` to carry only its own pages, with none
of the built-ins, must build its own layout page instead of reaching for
this helper.

**Stay below `order: 1000`, or declare your own `nav.group`.** The
built-ins are parked in a reserved high band, `Identity` (1000-1003) and
`System` (1010-1016), precisely so an application's own pages come first:
the conventional `order: 100` sorts above them. `useNavEntries` sorts groups
by their smallest member, so only an order of 1000 or more sinks a page in
among the built-ins.

**Gate with `can: () => this.someApi.someAction.can()`, not with
`permission` alone.** A permission named by this page's own `$secure` is
self-declaring: `$secure` registers it into `SecurityProvider` at
definition time, so an admin holding the `*` wildcard is granted it
whether or not any controller backing the page exists, and the entry would
stay visible over a dead API. An action name resolves against
`/api/_links`, a registry built only from actions the server actually
registered, so a page whose backend never shipped never appears for
anyone to be granted.

```tsx
class ShopAdminRouter {
  products = $pageAdmin({
    path: "/products",
    nav: { label: "Catalogue", icon: <Gem />, group: "Commerce", order: 100 },
    can: () => this.productApi.commerceAdminProductList.can(),
    lazy: () => import("./AdminProducts.tsx"),
  });
}
```
