/** * cli:scaffold-component — generate.ts * * Emits: * - React pages (list, detail, form) under src/features/{module}/{entity}/pages * conforming to the SmartStack customisation-ui baseline * (`@/components/ui/PageTemplate` + `@/components/ui/ResponsiveDataTable` + * token-based Tailwind, lucide-react icons, PermissionGuard + Slot/Fill). * - Locale JSON files under src/i18n/locales/{fr,en,it,de}/{module}.json * (relative to the web root, the SAME base as the pages — projectPath IS the * web root, enforced by assertWebProjectRoot) * — one file per MODULE (the entities of a module share it). The CLI writer * (index.ts) deep-merges into the existing file, so generating one entity never * wipes a sibling entity's keys. Concurrent generation of two entities in the * SAME module is therefore unsafe (read-modify-write race) — serialize within a * module (see ba-develop/SKILL.md § Phase 3a). * * Baseline anatomy (derived verbatim from D:\01 - projets\SmartStack.app\features\customisation-ui * pages administration/uiConfiguration/{ThemesListPage, PresetsListPage}, * administration/tenants/OrganisationCreatePage, administration/tenants/TenantDetailPage): * * - Every page is wrapped in `} actions={...}>`. * - List views use `` * (wraps the local : columns carry `minBreakpoint` so the default * code/label/actions stay visible and extra columns appear as the screen * widens, plus `truncate` for ellipsis + tooltip on overflowing cells). * - Detail views use card sections with `rounded-[var(--radius-card)] border * border-[var(--border-color)] bg-[var(--bg-card)]`. * - Form views use `.input` class for fields, primary CTA with the * `.btn .btn-primary` helper class (the theme owns the accent fill + hover + * radius + padding — never re-assemble it inline; ui-polish R21 enforces this). * - All colors through CSS vars — no Tailwind color palette (`bg-red-500` etc.). * - Icons from lucide-react only. * - i18n namespace = {module}, keys nested under entity key (lowercased). * - PermissionGuard keys: `{module}.{section}.{action}` — no appCode prefix. * - Slot/Fill points: {entity}.header.actions, {entity}.table.columns.before/after, * {entity}.form.fields.before/after, {entity}.detail.header/sidebar. * * No legacy patterns from the prior generate.ts are carried forward. */ import type { ScaffoldComponentInput, GeneratedFile, GenerateContext } from './types.js' import { buildRenderContext } from './render/context.js' import { renderList } from './render/list.js' import { renderDetail } from './render/detail.js' import { renderForm } from './render/form.js' import { renderDashboard } from './render/dashboard.js' import { renderReconduction } from './render/reconduction.js' import { renderHomes } from './render/home.js' import { renderI18nFiles } from './render/i18n-files.js' export function generate(spec: ScaffoldComponentInput, ctx: GenerateContext = {}): GeneratedFile[] { if (spec.fields.length === 0) { throw new Error(`scaffold-component: entity '${spec.entity}' has no fields — scaffolding would produce an empty UI`) } const files: GeneratedFile[] = [] const rc = buildRenderContext(spec, ctx) // View order is part of the contract (files[] order → writer order). files.push(...renderList(rc)) files.push(...renderDetail(rc)) files.push(...renderForm(rc)) files.push(...renderDashboard(rc)) // The kanban board is rendered by renderList (the `kanban` viewMode of the // list page) — the standalone renderKanban page is retired. files.push(...renderReconduction(rc)) files.push(...renderHomes(rc)) files.push(...renderI18nFiles(rc)) // (the status/boolean pill primitive) is emitted by shared render // helpers that list, detail AND form compose — inject its import wherever a // page actually renders one. Central so no template forgets it, conditional // so a pill-less page never ships an unused import (type-check gate). const BADGE_IMPORT = `import { Badge } from '@/components/ui/Badge'` const PAGE_TEMPLATE_IMPORT = `import { PageTemplate } from '@/components/ui/PageTemplate'` for (const f of files) { if (f.path.endsWith('.tsx') && f.content.includes('