/** * cli:scaffold-component — render/page-files.ts * * THE page-file naming of the scaffolder as a pure function — the ONE place * that says which `.tsx` an (entity, view) becomes. The renderers keep their * literal names (drift-locked by `__tests__/page-files-drift.test.ts` against * the REAL generate() output); scaffold-routes (page-existence cross-check) * and audit-dev-frontend DEV-UI-001/008 (pagespec ↔ page parity) import THIS * instead of re-typing the names. * * Audit 2026-09-24, B-WARN-13 / C-CRIT-06: the routes check looked for * `${entity}sListPage` under `src/pages/{module}/…` — a plural and a directory * the generator never used — so it warned on every canonical run and never * caught a real miss; DEV-UI-001/008 read a `--prd-slice` nothing produced. * * No dependency but lib/string-utils — importable from any pack. */ import { pluralize } from '../../../../../../lib/string-utils.js' /** Views the scaffolder materialises as ONE page file each. */ export const PAGE_FILE_VIEWS = ['list', 'detail', 'form', 'dashboard', 'reconduction', 'app-home', 'module-home', 'section-home'] as const export type PageFileView = (typeof PAGE_FILE_VIEWS)[number] /** * View tokens that never become a page file of their own: `kanban`/`card` are * viewModes of the LIST page (`?view=kanban`); `create`/`edit` are the * scaffold-routes aliases of `form`. */ export const FOLDED_VIEWS: Readonly> = { kanban: null, card: null, create: 'form', edit: 'form' } /** A materialised view → itself; `create`/`edit` → `form`; `kanban`/`card`/unknown → null (no file). */ export function pageFileViewOf(view: string): PageFileView | null { if ((PAGE_FILE_VIEWS as readonly string[]).includes(view)) return view as PageFileView return FOLDED_VIEWS[view] ?? null } /** The plural the renderers use — the explicit `pluralName` when given, else the shared `pluralize()`. */ export function pluralOf(entity: string, pluralName?: string | null): string { return pluralName ?? pluralize(entity) } /** File NAME (no directory) of a view's page — byte-identical to what render/*.ts emits. */ export function pageFileName(view: PageFileView, entity: string, pluralName?: string | null): string { const plural = pluralOf(entity, pluralName) switch (view) { case 'list': return `${plural}ListPage.tsx` case 'detail': return `${entity}DetailPage.tsx` case 'form': return `${entity}FormPage.tsx` case 'dashboard': return `${entity}DashboardPage.tsx` case 'reconduction': return `${entity}ReconductionPage.tsx` case 'app-home': return `${plural}AppHomePage.tsx` case 'module-home': return `${plural}ModuleHomePage.tsx` case 'section-home': return `${plural}SectionHomePage.tsx` } } /** Canonical page DIRECTORY — `src/pages/{appLower}/{module}/{section}`, with the parent section of a nested resource when there is one. */ export function pageDirFor(appCode: string, module: string, section: string, parentSection?: string | null): string { const app = appCode.toLowerCase() return parentSection ? `src/pages/${app}/${module}/${parentSection}/${section}` : `src/pages/${app}/${module}/${section}` } export interface PageFileLocation { appCode: string module: string section: string entity: string view: PageFileView pluralName?: string | null parentSection?: string | null /** A pagespec `filePath` naming THIS view's file — honoured verbatim (POSIX separators). */ filePath?: string | null } /** Project-relative path of a view's page: the pagespec `filePath` when given, else the canonical directory + name. */ export function pageFilePathFor(loc: PageFileLocation): string { if (loc.filePath) return loc.filePath.replace(/\\/g, '/') return `${pageDirFor(loc.appCode, loc.module, loc.section, loc.parentSection)}/${pageFileName(loc.view, loc.entity, loc.pluralName)}` }