---
name: frontend-extension-config
description: >
  Generates ExtensionConfig (@atlashub/smartstack) with slot definitions that
  mirror the <Slot name="..."> calls emitted by frontend/component.
phase: development/frontend
cli: cli/scaffold-extension-config
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: CLI invocation
---

# Extension Config — Slots + Columns + Form Fields

Generates the `ExtensionConfig` that publishes the module's extension points
(slots, table columns, form fields) to SmartStack's extension system.

`ExtensionConfig` type comes from `@atlashub/smartstack` (npm).

## Slot catalog contract

The slot catalog emitted here MUST match exactly the `<Slot name="...">` calls
emitted by `frontend/component/scaffold-component`. Renaming in one place
without the other breaks composition silently (fills will never render).

The names come from the SSOT `skills/lib/slot-names.ts` (drift-tested against the real
renderer: `lib/__tests__/slot-names-drift.test.ts` — and since 2026-09-24 the renderers themselves
name every slot through `slotName.<view>()` from that same module, so a drift is impossible by
construction). They carry NO module prefix —
`{entityLower}` is the entity with its first letter lowercased (`Employee` → `employee`).
Per entity, the seven slots of the default views are registered:

| Slot name | Rendered by |
|-----------|-------------|
| `{entityLower}.header.actions` | ListPage header, next to the create button |
| `{entityLower}.table.columns.before` | ListPage, above the table / cards / kanban host |
| `{entityLower}.table.columns.after` | ListPage, below the table host |
| `{entityLower}.detail.header` | DetailPage, top of the fiche |
| `{entityLower}.detail.sidebar` | DetailPage, side column |
| `{entityLower}.form.fields.before` | FormPage, before the scaffolded fields |
| `{entityLower}.form.fields.after` | FormPage, after the scaffolded fields |

The optional views render more (listed as comments in the emitted file): hub pages
`home.header` / `home.kpis` / `home.kpis.after` / `home.navigation` / `home.footer`, dashboard
`dashboard.header` / `dashboard.widgets.before` / `dashboard.widgets.after` / `dashboard.footer`,
reconduction `reconduction.header` / `reconduction.footer`. The emitted const is
`{moduleCamel}Config` (`time-entry` → `timeEntryConfig`).

## Output pattern

```ts
import type { ExtensionConfig } from '@atlashub/smartstack';

/**
 * Extension configuration for the hrm module.
 *
 * Slot catalog (each <Slot name="..."> call must match exactly):
 *   - 'hrm.employee.list.before': Rendered above the Employee list table.
 *       context: {}
 *   - 'hrm.employee.detail.before': Rendered at the top of the Employee detail page.
 *       context: { data: Employee }
 *   - ...
 *
 * Add Fill components in your app entrypoint to inject content into any slot:
 *   <Fill slot="hrm.employee.list.before">{yourJsx}</Fill>
 */
export const hrmConfig: ExtensionConfig = {
  slots: {
    'hrm.employee.list.before': null,
    'hrm.employee.list.after': null,
    'hrm.employee.detail.before': null,
    'hrm.employee.detail.after': null,
    'hrm.employee.form.fields.before': null,
    'hrm.employee.form.fields.after': null,
    // ... one group per entity
  },
  tableColumns: {},
  formFields: {},
  hooks: {},
};
```

The `tableColumns` and `formFields` are left empty for the client to populate
with domain-specific column extensions and custom `ext.*` form fields. `hooks`
is reserved for future behavioural extension points.

## ⚠ BLOCKING — Key Rules

1. **Module name**: kebab-case, starts with a letter (regex `/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/`).
2. **Entity names**: PascalCase (regex `/^[A-Z][A-Za-z0-9]*$/`).
3. **Slot naming**: must match the pattern `{module}.{entityLower}.{view}.{position}` (five parts for form fields). The component generator emits exactly this shape — any deviation breaks the contract.

**`@customised` outputs are never overwritten.** A file this CLI would emit whose first line is
`// @customised` (or `/* @customised */`) is skipped and reported — `data.skipped[]` plus one warning
per file (`lib/generated-writes`, one loop for every scaffolder). Remove the marker to regenerate it.

## Invocation

```bash
npx --prefer-offline tsx skills/development/frontend/extension-config/cli/scaffold-extension-config/index.ts \
  --spec '{"module":"hrm","appCode":"myapp","entities":["Employee","Contract"],"projectPath":"/path"}'
```

`appCode` is validated (kebab-case) but not written into the emitted file: the config
identifier is `{module}Config` (camelCase) and the slot names carry no app prefix
(`lib/slot-names`). Flags: `--spec` / `--spec-file`, `--outdir`, `--dry_run`.

## Output

- `src/extensions/{module}Extensions.ts` — one file per module listing all entities' slots.

## Client usage

Register the config with the SmartStack extension system (usually in `main.tsx`
after the registry imports):

```ts
import { registerExtensionConfig } from '@atlashub/smartstack';
import { hrmConfig } from './extensions/hrmExtensions';

registerExtensionConfig('hrm', hrmConfig);
```

Then fills can be provided anywhere in the app:

```tsx
<Fill slot="hrm.employee.list.before">
  <MyCustomBanner />
</Fill>
```
