---
name: scaffold-layout
description: Scaffold the page wrapper (PageTemplate) of the generated client app. The app chrome — desktop header/sidebar AND the mobile shell — is rendered by @atlashub/smartstack and is never scaffolded locally.
group: D
phase: devFrontend
kind: main
section_label: 'SCAFFOLD-LAYOUT (PageTemplate — the page wrapper every generated page mounts inside)'
allowed-tools: [Read, Write, Edit, Glob, Grep]
---

# scaffold-layout — Page wrapper scaffolder

## Context

You are scaffolding the **page wrapper** of a SmartStack-generated React app:

- `src/components/ui/PageTemplate.tsx` — the wrapper every page mounts inside (`<PageTemplate title=…>`). Carries the breadcrumb, page title, action slot and content slot. The path matches what `scaffold-component` imports (`@/components/ui/PageTemplate`); a wrong path here triggers the silent-spinner subagent ad-lib that drops props.

Without this file, the BA dev pipeline (`audit-dev-frontend` rule DEV-UI-005) fails: pages reference `<PageTemplate>` but the import resolves to nothing.

## Chrome comes from the package — never scaffold it

The **application chrome is not the client app's concern**. `@atlashub/smartstack` renders it around `<DynamicRouter />`:

| Viewport | What the package renders |
|---|---|
| Desktop | App header, sidebar (hierarchical nav read from `/api/navigation/menu`), tenant switcher, user menu, content outlet. |
| Mobile (`mobile: { enabled: true }`) | The "descente par paliers" shell: `MobileShell` + hierarchical Applications → Modules → Sections pages, `MobileHeader` with hierarchical back + breadcrumbs, the transverse bottom bar (Applications / Tâches / Activité / Compte), tenant chip + sheet, transverse search, offline banner + outbox sheet. |

The generated `App.tsx` renders `<DynamicRouter />` **alone** — the chrome wraps it from inside the package.

Earlier versions of this scaffolder also emitted `src/components/AppShell.tsx` and `src/components/Sidebar.tsx`. They **duplicated** the package chrome, were **never mounted** by the generated `App.tsx`, carried **no mobile branch**, and drifted away from the package on every release. They are no longer emitted.

> **Existing apps**: if your app already carries those two files on disk, they are simply **orphans** — dead code no route mounts. Nothing deletes them (no upgrade migration removes client files); delete them yourself when convenient. If you *did* wire `<AppShell>` by hand, you are rendering a second chrome inside the package's: unwire it before deleting.

**Per-app branding** (logo, colors, header slots) goes through the platform's own seams — theme tokens (`scaffold-theme`), tenant branding settings, and the package's page/extension seams — not through a forked shell.

## Invocation

```
npx --prefer-offline tsx skills/development/frontend/layout/cli/scaffold-layout/index.ts \
  --spec '{"projectPath":"<abs>","appCode":"<app>"}'
```

`--spec` JSON:

| Field | Type | Required | Notes |
|---|---|---|---|
| `projectPath` | string | yes | Absolute path to the web root (holds `src/`). |
| `appCode` | string | yes | Accepted for call-site compatibility; no longer read (it only branded the removed AppShell header). |
| `force` | boolean | no | Overwrite even when content matches. Default false. |

## What it produces

- `src/components/ui/PageTemplate.tsx` — page wrapper. Renders `title`, `subtitle`, `icon`, `actions`, `breadcrumbs`, `className`, `contentClassName`, `width` and `children` (every prop the page templates pass).

The `width` contract has exactly two values: `standard` (full-width fluid — lists, tables, dashboards) and `focused` (centered column capped at `--content-max-narrow` — create/edit forms, wizards, onboarding). The horizontal gutter (`--content-gutter-x`) is applied **once** by the package's scroll container (desktop `<main>`, mobile content area) and must never be re-applied by PageTemplate, or it doubles.

## Idempotency contract

Same as scaffold-theme: `/* @customised */` (or `// @customised`) marker at the top of the file = preserve. Otherwise overwrite when content drifts. AUTO-GENERATED header included.

## When NOT to use

- Adding sidebar/menu items → those come from the BA navigation tables → `scaffold-core-seed` populates `/api/navigation/menu` → the package chrome renders them automatically, on desktop AND in the mobile paliers.
- Enabling the mobile shell → that is `mobile: { enabled: true }` in the SmartStackProvider config, written by `/pwa` (`scaffold-pwa`). Nothing to scaffold here.
- Per-page layout (split-pane, tabs) → page-level concerns; don't touch the wrapper.
