---
name: Generate - Author content, then run scaffold-doc
description: Author the doc page + i18n content from the extract-doc report, then run scaffold-doc to write i18n/manifest and emit the wiring plan
next_step: step-03-validate.md
---

## STEP 02: Generate — author content, then `scaffold-doc`

### Division of labour

| Deterministic (CLI) | LLM (you) |
|---------------------|-----------|
| i18n locale files (4 langs, FLAT), `docs-manifest.json` upsert, wiring **plan** | the doc page body (faithful Mock UI), the i18n **content**, curated rules/use-cases/FAQ |

So: **you author** the page + the i18n content, then **`scaffold-doc` writes the files + manifest and hands back a wiring plan** you apply.

### A. Author the doc page

#### `user` type — standalone annotated Mock UI

Generate a **standalone TSX page** (~400-600 lines) that faithfully reproduces
the real page read in step-01. **Do NOT use DocRenderer.** Follow `templates.md`
for the full structure (**8 sections — 9 with `--tech`**) and the canonical
`Annotation` component.

- **Location + namespace are mode-dependent** (step-01 `resolved.frontendMode`):
  - `source` → `src/pages/docs/business/{platform|personal}/<app>/<module>/index.tsx`
    + `useTranslation('<camelCase namespace>')`.
  - `client` → `src/pages/docs/business/<app>/<module>/index.tsx` (**NO**
    `platform|personal` segment) + `useTranslation('<kebab namespace from
    step-01>')` (e.g. `'docs-gaf-affaires'`). Author the page BEFORE running
    scaffold-doc (§C) — the CLI refuses to register a phantom page.
- **Header is a FIXED block** (identical across pages — see `templates.md` "Header
  obligatoire"): title + icon, then a **REQUIRED accent tagline** `t('summary')`
  (`text-xl text-[var(--color-accent-600)] font-medium`), then `t('subtitle')`, then
  a **stats line** rendered **inline** as `<span>{N} {t('header.businessRules')}</span> · …`
  — count is a sibling text node, **never** pills and **never** `t('header.x', {count})`
  (the number vanishes if the i18n value lacks `{{count}}`). Omitting the tagline fails
  the step-03 gate (`existingDoc.missingRequired` = `['summary']`).
- **Section 1 is ALWAYS "Objectif"** — a short factual description of what the
  module does / what it is for, written as an objective (no problème/solution, no
  pitch). It is its **own numbered section** rendering `t('objective')` under the
  `t('sections.objective')` heading. The header `subtitle` does **not** replace it —
  even when the subtitle already summarises the module, the Objectif section stays.
  Omitting it fails the step-03 gate (`existingDoc.missingRequired` = `['objective']`).
- **Section 2 is « Accès & rôles »** — the Rôle | Peut faire | Portée table
  authored from `report.accessRoles.rows` ONLY (the code is the source of
  truth — NEVER invent a grant), plus the page URL on one `<code
  className="… break-all">` line. No breadcrumb (the doc opens as a panel on
  the page itself). `source === 'ba'` → render the `access.unverified` caveat;
  `unmappedCodePermissions` → render as a caveat; the join `warnings[]` are
  REPORTED to the user, never published. **Fallback** `source === 'none'` →
  keep the plain access card (URL + the required-permissions list from
  `apiEndpoints[].permission`). See `templates.md` § SECTION 2.
- **You MAY adapt the middle "interface" sections** (the template's Interface /
  Création / Détail) to the real page's shape: a tabbed settings page becomes
  one section per tab; a Kanban becomes a board mock; etc. What is **fixed**:
  Section 1 = Objectif, then Accès & rôles, and the tail (Cas d'usage,
  Fonctionnalités, FAQ — plus the Référence technique appendix when `--tech`).
  Adapt the body, never drop the Objectif.
- Reproduce the **real page** (`report.resolved.pageTsxPath`) — same layout family (cards / table / kanban / dashboard), not a generic KPI/table.
- For every chart, match `report.charts[].mockUiPattern` (recharts type → faithful Mock UI). See `templates.md` → recharts mapping + expressive mock data.
- Annotate every Mock UI block; keep annotations INSIDE the same padded container.
- **Only when `--tech`**: use the curated `report.apiEndpoints` / `businessRules`
  in the collapsible Technical Reference appendix (Section 9 — the permissions
  themselves now live in Section 2). Without `--tech`, author NO Section 9 —
  but NEVER remove one already present in an existing doc, and keep the header
  stats (`businessRules` + `apiEndpoints`) in both cases.

#### `developer | database | testing` — DocRenderer

Generate `doc-data.ts` (structured `DocData`) + a ~10-line `index.tsx` wrapper
that renders `<DocRenderer data={docData} i18nNamespace="…" />`. See `templates.md`.

#### Correcting an existing doc (when `report.existingDoc` flags issues)

`extract-doc` reports two mirror structural defects on a doc already on disk. Fix
both as you regenerate — documentation leads with a factual **objective** and
carries no pitch (SKILL.md rule #12):

**a. Forbidden sections** (`report.existingDoc.forbiddenSections` non-empty) —
banned "sales" framing (problème/solution, Bénéfices, Avant/Après). For each entry,
**remove it** — do NOT carry it over:

- **`user`** — drop the section from the standalone TSX, and **omit** the
  `benefits` / `beforeAfter` / `problem` / `solution` keys from the i18n content you
  author. `scaffold-doc` **overwrites** each locale file with your authored content,
  so omitting the keys deletes them.
- **`developer | database | testing`** — remove the `benefits` / `beforeAfter`
  fields and `overview.problem` / `overview.solution` from `doc-data.ts`, and omit
  the matching keys from the authored i18n.

**b. Missing required elements** (`report.existingDoc.missingRequired` non-empty):

- **`"objective"`** — the mandatory Section 1 is absent (a stale doc predating the
  objective convention, or one that folded it into the subtitle). **Add it:** author
  `sections.objective` + `objective` (user) or `overview.objective` (DocRenderer) and
  render it as the first numbered section. Write the objective from the real page's
  purpose — what it does / what it is for — not a sales angle.
- **`"summary"`** (user) — the header accent tagline is missing (the header isn't
  identical to the other pages). **Add it:** author the `summary` key and render it
  between the title and subtitle as `<p class="text-xl text-[var(--color-accent-600)]
  font-medium">{t('summary')}</p>`, plus the inline stats line. If the doc used the
  legacy `valueProposition` key, rename it to `summary`.

**c. Non-blocking advice** (never gated — follow it while you are here anyway):

- **`adviseRolesTable: true`** — the existing `user` doc predates the
  « Accès & rôles » table while this project HAS a roles source: add the
  Section 2 table from `report.accessRoles` at this regeneration.
- **`overflowRisks[]`** — `<code>`/`font-mono` elements without a break
  utility: add `break-all`/`break-words` (DocPanel is ~480px wide).

The step-03 gate re-runs the scan and will not let you finish while any
**blocking** defect (a/b) remains.

### B. Author the i18n content

Author the content for the 4 languages (FR source, then EN/DE/IT) **FLAT** — keys at
the root (`title`, `sections`, `objective`, …). `scaffold-doc` writes it as-is for
`user` docs (resolved via `t('title')`) and **wraps it under the namespace key** for
`developer/database/testing` docs (DocRenderer resolves `t('<namespace>.path')`).

### C. Run `scaffold-doc`

Pass the authored i18n content + the metadata from step-01's report:

```bash
npx --prefer-offline tsx skills/documentation/cli/scaffold-doc/index.ts --spec '{
  "type":"user","target":"<module>","application":"<app>",
  "namespace":"<report.namespace>","routePath":"<app>/<module>",
  "componentName":"<Module>DocPage",
  "pageImportPath":"@/pages/docs/business/<group>/<app>/<module>",
  "i18n":{"fr":{…},"en":{…},"de":{…},"it":{…}},
  "manifest":{"id":"<module>","name":"…","description":"…","version":"1.0.0"},
  "docMappings":["<app>/<module>","<app>/<module>/list","<app>/<module>/create"],
  "appDocDefault":false,
  "projectPath":"<project-root>"
}'
```

- The CLI auto-detects the frontend mode (`data.mode`: `source` | `client`) and
  generates accordingly; pass `"mode":"source"|"client"` only if detection says
  `unknown`. **A missing wiring target or violated client invariant FAILS the
  run (exit 1, `errors[]`)** — read the errors, fix, re-run. Never continue past
  a failed scaffold-doc.
- **Source monorepo:** `<group>` is the page-folder grouping (`platform` for
  admin/support/api, `personal` for myspace) — note the **route omits it**.
- **Client project:** there is NO `<group>` segment — the page folder mirrors
  the route: `pageImportPath` MUST be `@/pages/docs/business/<app>/<module>`,
  and `namespace` MUST be the kebab form `docs-<app>-<module>` (from step-01;
  the CLI enforces `kebab(namespace) === 'docs-' + routePath.join('-')`). The
  first route segment must be the client app's own code (never `administration`,
  `myspace`, `hr`, `api`, `support`, `platform` — platform-owned roots). The
  page must already exist on disk (author §A BEFORE this call). It writes the
  i18n files + `src/extensions/<kebab>Registry.ts` (PageRegistry `docs.*` seam)
  + creates/upserts `docs-manifest.json`.
- Use `--dry_run` first to preview `filesCreated` + the wiring plan without writing.
- It returns `data.wiring` + `data.namespace` (the EFFECTIVE namespace the page
  must use with `useTranslation()`).

### D. Apply the wiring plan

`report.data.wiring[]` lists each instruction with a `kind` and a `severity`.
For each **`severity: "required"`** instruction:
1. If `idempotencyMarker` already appears in `file` → **skip** (already wired).
2. Else apply it (see per-kind below).

**Source monorepo** (kinds `i18n-config`, `doc-routes`, `doc-panel-mapping`,
`user-index`) — in-place edits the CLI does NOT make itself (you do, adapting to
the real file): open `file`, find `anchor`, apply `insert` with `Edit`. Targets:
`i18n/config.ts` (4 points), `DocRoutes.tsx` (lazy import + child route under
`/docs/business`), `DocPanelContext.tsx` (docMapping), `UserIndexPage.tsx`
(module entry, user type).

**Client project** (kind `aggregate`) — `insert` is a **command, not an edit**:
run it (the aggregate-component-registry CLI, WITHOUT `--modules`) so
`componentRegistry.generated.ts` imports the new `<kebab>Registry` and
`moduleResources.generated.ts` registers the kebab i18n namespace. Then verify
both markers are present in the generated files. The `panel-limitation`
instruction (`severity: "info"`) is a documented limitation — surface it in your
final report to the user, apply nothing, and NEVER edit files under
`node_modules` to work around it.

### Colour / theme — delegated to ui-polish

Do **not** hand-map Tailwind colours. After the page exists, theme compliance is
enforced **deterministically** by the ui-polish CLI in step-03. While authoring,
just follow the doc-specific intents in `templates.md`: dashboard chart series use
the `--dataviz-*` palette, highlight blocks use the discreet accent pattern, brand
colour is the `--color-accent-*` ramp (never `--color-primary-*`).

### Absolute rules (kept)

1. **NEVER** hardcoded text → `useTranslation('<namespace>')`.
2. **ALWAYS** read the REAL page TSX (step-01) before writing Mock UI.
3. **ALWAYS** annotate every Mock UI block (canonical `Annotation`, children-based).
4. **ALWAYS** author i18n FLAT in all 4 languages — the CLI nests it under the namespace for DocRenderer types.
5. **ALWAYS** apply the `scaffold-doc` wiring plan, then run ui-polish (step-03).

### Annotation component contract

```tsx
import { Info } from 'lucide-react';
import type { ReactNode } from 'react';

function Annotation({ children }: { children: ReactNode }) {
  return (
    <div className="flex items-start gap-2 px-3 py-2 rounded-md border-l-2 border-[var(--color-accent-500)] bg-[var(--bg-secondary)] text-xs text-[var(--text-secondary)]">
      <Info className="w-3.5 h-3.5 mt-0.5 flex-shrink-0 text-[var(--text-tertiary)]" />
      <span className="leading-relaxed">{children}</span>
    </div>
  );
}
```

### Next step

→ **step-03-validate.md** — verify completeness + run ui-polish + final checks.
