# Add Admin Page — Detailed Procedure

## Default Flow Position

This skill participates in the [Default Coding Flow](../../../../AGENTS.md#default-coding-flow) at the **`implement`** step.

`approach options` upstream is **conditionally mandatory**: required when the page exposes sensitive fields, introduces a new admin auth surface, or wires unprecedented service contracts. Skip for routine config-only admin pages.

After implementation, route to:
- `verify` — `/test-domain {name} run`
- `self-review` — `/security-review` if the page exposes fields that may carry secrets / PII / tokens
- `completion gate` — `/sync-guidelines` if shared docs reference admin patterns

## Analysis

1. Identify the domain name from arguments
2. Verify the domain exists: `src/{name}/` present
3. Verify admin files do NOT already exist: `src/{name}/interface/admin/configs/{name}_admin_config.py`
4. Read the domain's DTO (`src/{name}/domain/dtos/{name}_dto.py`) to identify available fields
5. Ask the user:
   - Which fields to display (default: all)
   - Which fields to mask (e.g., password, secret, token)
   - Which fields to make searchable
   - Material icon name (default: "list")

## Reference
- `src/user/interface/admin/configs/user_admin_config.py` — config pattern
- `src/user/interface/admin/pages/user_page.py` — page route pattern
- Admin Page Pattern: refer to `docs/ai/shared/project-dna.md` section 11
- **Design system** (tokens + component builders + DO/DON'T): `docs/ai/shared/admin-design-system.md`. Custom (non-CRUD) pages MUST compose `admin.components` builders, not raw `ui.*`.

## Implementation Order

### 1. Create Directory Structure

Create `__init__.py` for each new directory:
- `src/{name}/interface/admin/__init__.py`
- `src/{name}/interface/admin/configs/__init__.py`
- `src/{name}/interface/admin/pages/__init__.py`

> Skip directories that already exist (e.g., `interface/` likely exists).

### 2. Create Admin Config File

`src/{name}/interface/admin/configs/{name}_admin_config.py`

Read the user reference file first, then replicate the pattern:

```python
from src._core.infrastructure.admin.base_admin_page import (
    BaseAdminPage,
    ColumnConfig,
)

{name}_admin_page = BaseAdminPage(
    domain_name="{name}",
    display_name="{Name}",
    icon="{icon}",
    columns=[
        ColumnConfig(field_name="id", header_name="ID", width=80),
        # ... one ColumnConfig per DTO field
        # searchable=True for text fields users will search by
        # masked=True for sensitive fields (password, secret, token)
        # hidden=True for fields not shown in list view
    ],
    searchable_fields=[...],       # field_name list
    sortable_fields=[...],         # field_name list
    default_sort_field="id",
)
```

### 3. Create Page Route File

`src/{name}/interface/admin/pages/{name}_page.py`

Read the user reference file first, then replicate the pattern:

```python
from nicegui import ui

from src._core.infrastructure.admin.auth import require_auth
from src._core.infrastructure.admin.base_admin_page import BaseAdminPage
from src._core.infrastructure.admin.error_handler import admin_error_boundary
from src._core.infrastructure.admin.layout import admin_layout
from src.{name}.interface.admin.configs.{name}_admin_config import {name}_admin_page

# Injected by bootstrap_admin() after discovery
page_configs: list[BaseAdminPage] = []


@ui.page("/admin/{name}")
@admin_error_boundary(context="{name}_list")
async def {name}_list_page(page: int = 1, search: str = ""):
    session = await require_auth(page_key="{name}")  # MUST be the first statement
    if session is None:
        return
    admin_layout(page_configs, current_domain="{name}", session=session)
    await {name}_admin_page.render_list(page=page, search=search)


@ui.page("/admin/{name}/{record_id}")
@admin_error_boundary(context="{name}_detail")
async def {name}_detail_page(record_id: int):
    session = await require_auth(page_key="{name}")  # MUST be the first statement
    if session is None:
        return
    admin_layout(page_configs, current_domain="{name}", session=session)
    await {name}_admin_page.render_detail(record_id=record_id)
```

> `@admin_error_boundary` is required on every admin route (project-dna §). The
> auth gate must be the route's **first statement** — enforced by
> `tests/unit/_core/infrastructure/admin/test_route_coverage.py`.

### Custom / non-CRUD pages

For summary pages, dashboard widgets, playgrounds, or write actions, do **not**
hand-roll `ui.card` / `ui.aggrid` / `ui.dialog`. Compose the design-system
builders so the page matches every other admin surface:

```python
from src._core.infrastructure.admin import components as c

@ui.page("/admin/{name}/summary")
@admin_error_boundary(context="{name}_summary")
async def {name}_summary_page():
    session = await require_auth(page_key="{name}")
    if session is None:
        return
    admin_layout(page_configs, current_domain="{name}", session=session)
    c.page_header("{Name} Summary")
    with ui.row().classes("q-gutter-md q-mb-md"):
        c.stat_card("Total", total)
    c.data_grid(column_defs, rows, compact=True)
```

Catalog + DO/DON'T + the `confirm_dialog` contract: `docs/ai/shared/admin-design-system.md`.

## Core Rules
- Config file contains ONLY the `BaseAdminPage` instance declaration (no routes, no `ui` import)
- Page file contains ONLY `@ui.page` routes (no `BaseAdminPage` instantiation)
- No `@inject`/`Provide` — service is resolved internally via `BaseAdminPage._service_provider`
- `bootstrap_admin()` handles auto-discovery, DI wiring, and `page_configs` injection
- **No manual bootstrap registration needed** — auto-discovery handles everything
- Config variable must be named `{name}_admin_page` (discovery convention)
- DI pattern: see `docs/ai/shared/project-dna.md` §11

## Post-completion Verification
1. Run pre-commit: `pre-commit run --files src/{name}/interface/admin/**/*.py`
2. Verify config import: `python -c "from src.{name}.interface.admin.configs.{name}_admin_config import {name}_admin_page; print('OK')"`
3. Start server and visit `/admin/{name}` to confirm the page loads
