create-momentum-app 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/templates/analog/src/server/utils/momentum-init.ts.tmpl +2 -13
- package/templates/shared/.claude/agents.md +11 -10
- package/templates/shared/.claude/skills/admin-config/SKILL.md +6 -0
- package/templates/shared/.claude/skills/admin-customize/SKILL.md +185 -0
- package/templates/shared/.claude/skills/headless-ui/SKILL.md +77 -0
package/README.md
CHANGED
|
@@ -32,3 +32,4 @@ npx create-momentum-app my-app --flavor angular --database postgres
|
|
|
32
32
|
- Drizzle ORM with PostgreSQL or SQLite
|
|
33
33
|
- Docker Compose setup for PostgreSQL (when using postgres)
|
|
34
34
|
- Tailwind CSS with the Momentum admin theme
|
|
35
|
+
- Project-local Claude Code skills under `.claude/skills`, including `headless-ui` for custom app UI built on `@momentumcms/headless`
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import 'dotenv/config';
|
|
|
2
2
|
import {
|
|
3
3
|
initializeMomentumAPI,
|
|
4
4
|
registerWebhookHooks,
|
|
5
|
+
syncDatabaseSchema,
|
|
5
6
|
} from '@momentumcms/server-core';
|
|
6
7
|
import { initializeMomentumLogger, createLogger } from '@momentumcms/logger';
|
|
7
8
|
import { PluginRunner } from '@momentumcms/plugins-core';
|
|
@@ -44,19 +45,7 @@ async function initialize(): Promise<void> {
|
|
|
44
45
|
|
|
45
46
|
registerWebhookHooks(momentumConfig.collections);
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
log.info('Initializing database schema...');
|
|
49
|
-
await momentumConfig.db.adapter.initialize(momentumConfig.collections);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
momentumConfig.db.adapter.initializeGlobals &&
|
|
54
|
-
momentumConfig.globals &&
|
|
55
|
-
momentumConfig.globals.length > 0
|
|
56
|
-
) {
|
|
57
|
-
log.info(`Initializing globals table for ${momentumConfig.globals.length} global(s)...`);
|
|
58
|
-
await momentumConfig.db.adapter.initializeGlobals(momentumConfig.globals);
|
|
59
|
-
}
|
|
48
|
+
await syncDatabaseSchema(momentumConfig, log);
|
|
60
49
|
|
|
61
50
|
log.info('Initializing API...');
|
|
62
51
|
const api = initializeMomentumAPI(momentumConfig);
|
|
@@ -17,16 +17,17 @@ Momentum CMS is a headless CMS built with Angular. You define collections in Typ
|
|
|
17
17
|
|
|
18
18
|
## Available Skills
|
|
19
19
|
|
|
20
|
-
| Skill
|
|
21
|
-
|
|
|
22
|
-
| `/collection <name>`
|
|
23
|
-
| `/momentum-api <op>`
|
|
24
|
-
| `/add-plugin <name>`
|
|
25
|
-
| `/admin-config <what>`
|
|
26
|
-
| `/
|
|
27
|
-
| `/
|
|
28
|
-
| `/
|
|
29
|
-
| `/
|
|
20
|
+
| Skill | Usage | Description |
|
|
21
|
+
| ------------------------- | ------------------------------ | ---------------------------------------------------------------- |
|
|
22
|
+
| `/collection <name>` | `/collection products` | Generate a new collection with fields, access control, and hooks |
|
|
23
|
+
| `/momentum-api <op>` | `/momentum-api crud posts` | Guide for using `injectMomentumAPI()` in Angular components |
|
|
24
|
+
| `/add-plugin <name>` | `/add-plugin analytics` | Add and configure a Momentum CMS plugin |
|
|
25
|
+
| `/admin-config <what>` | `/admin-config field-renderer` | Wire admin routes, plugin imports, and custom field renderers |
|
|
26
|
+
| `/admin-customize <what>` | `/admin-customize slot` | Swappable pages, layout slots, per-collection overrides |
|
|
27
|
+
| `/api-route <name>` | `/api-route health` | Generate API route handlers for Express or Analog.js |
|
|
28
|
+
| `/component <name>` | `/component post-card` | Generate an Angular component with signals and OnPush |
|
|
29
|
+
| `/e2e-test <feature>` | `/e2e-test posts` | Write Playwright E2E tests (dashboard-first, no blind tests) |
|
|
30
|
+
| `/migrations <op>` | `/migrations generate` | Run migrations, generate schemas, and manage code generation |
|
|
30
31
|
|
|
31
32
|
## Common Workflows
|
|
32
33
|
|
|
@@ -46,6 +46,12 @@ browserImports: {
|
|
|
46
46
|
}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
### Component loaders in generated config
|
|
50
|
+
|
|
51
|
+
The generator emits `admin.components` (global) and per-collection `admin.components` as lazy-loading functions with rewritten import paths. This means `momentum.config.ts` is the single source of truth for page overrides and layout slots.
|
|
52
|
+
|
|
53
|
+
For swappable pages and layout slots, see `/admin-customize`.
|
|
54
|
+
|
|
49
55
|
## Custom Field Renderers
|
|
50
56
|
|
|
51
57
|
Field renderers are lazily loaded via `FieldRendererRegistry`. Built-in renderers are registered with `provideMomentumFieldRenderers()`.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: admin-customize
|
|
3
|
+
description: Customize the admin UI with swappable pages and layout slots. Use when replacing built-in pages (dashboard, list, edit, view), injecting content into layout slots (header, footer, sidebar, before/after), or registering per-collection overrides.
|
|
4
|
+
argument-hint: <page-override|slot|per-collection>
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Admin Customization — Swappable Pages & Layout Slots
|
|
8
|
+
|
|
9
|
+
Guide for customizing the Momentum CMS admin UI via page replacements and layout slot injection.
|
|
10
|
+
|
|
11
|
+
## Arguments
|
|
12
|
+
|
|
13
|
+
- `$ARGUMENTS` - What to customize: `page-override`, `slot`, or `per-collection`
|
|
14
|
+
|
|
15
|
+
## Two Registration Methods
|
|
16
|
+
|
|
17
|
+
### 1. Config-level (momentum.config.ts)
|
|
18
|
+
|
|
19
|
+
Registered in the server config. The code generator emits these as loader functions in the browser-safe generated config. This is the recommended approach — single source of truth.
|
|
20
|
+
|
|
21
|
+
After changes, run: `npm run generate`
|
|
22
|
+
|
|
23
|
+
### 2. Provider-level (app.config.ts)
|
|
24
|
+
|
|
25
|
+
Registered via Angular DI providers. Takes effect immediately, no generation needed. Useful for app-specific customizations or login page slots.
|
|
26
|
+
|
|
27
|
+
## Swappable Pages (Full Replacement)
|
|
28
|
+
|
|
29
|
+
### Available Page Keys
|
|
30
|
+
|
|
31
|
+
| Key | Built-in Page |
|
|
32
|
+
| ----------------- | --------------- |
|
|
33
|
+
| `dashboard` | Dashboard |
|
|
34
|
+
| `login` | Login |
|
|
35
|
+
| `media` | Media Library |
|
|
36
|
+
| `collection-list` | Collection List |
|
|
37
|
+
| `collection-edit` | Collection Edit |
|
|
38
|
+
| `collection-view` | Collection View |
|
|
39
|
+
| `global-edit` | Global Edit |
|
|
40
|
+
|
|
41
|
+
### Config-level (momentum.config.ts)
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
admin: {
|
|
45
|
+
components: {
|
|
46
|
+
dashboard: () =>
|
|
47
|
+
import('./app/custom-dashboard.component').then((m) => m.CustomDashboard),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Provider-level (app.config.ts)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { provideAdminComponent } from '@momentumcms/admin';
|
|
56
|
+
|
|
57
|
+
providers: [
|
|
58
|
+
provideAdminComponent('dashboard', () =>
|
|
59
|
+
import('./custom-dashboard.component').then((m) => m.CustomDashboard),
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Per-Collection Page Overrides
|
|
65
|
+
|
|
66
|
+
Override pages for a specific collection only:
|
|
67
|
+
|
|
68
|
+
#### Config-level (on collection admin.components)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
admin: {
|
|
72
|
+
components: {
|
|
73
|
+
list: () => import('./custom-articles-list.component').then((m) => m.CustomArticlesList),
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Provider-level
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
provideAdminComponent('collections/articles/list', () =>
|
|
82
|
+
import('./custom-articles-list.component').then((m) => m.CustomArticlesList),
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Resolution Chain
|
|
87
|
+
|
|
88
|
+
1. Per-collection override (`collections/{slug}/{type}`)
|
|
89
|
+
2. Global override (`collection-list`)
|
|
90
|
+
3. Built-in default
|
|
91
|
+
|
|
92
|
+
## Layout Slots (Additive Injection)
|
|
93
|
+
|
|
94
|
+
Slots inject content around existing pages. Multiple components can register for the same slot.
|
|
95
|
+
|
|
96
|
+
### Available Slots
|
|
97
|
+
|
|
98
|
+
| Slot Key | Position |
|
|
99
|
+
| -------------------------------------- | ------------------------------- |
|
|
100
|
+
| `shell:header` | Top of main content area |
|
|
101
|
+
| `shell:footer` | Bottom of main content area |
|
|
102
|
+
| `shell:nav-start` | After Dashboard link in sidebar |
|
|
103
|
+
| `shell:nav-end` | After plugin routes in sidebar |
|
|
104
|
+
| `dashboard:before/after` | Around dashboard content |
|
|
105
|
+
| `collection-list:before/after` | Around list tables |
|
|
106
|
+
| `collection-edit:before/after/sidebar` | Around edit forms |
|
|
107
|
+
| `collection-view:before/after` | Around view pages |
|
|
108
|
+
| `login:before/after` | Around login form |
|
|
109
|
+
|
|
110
|
+
### Config-level (momentum.config.ts — admin.components)
|
|
111
|
+
|
|
112
|
+
| Config Key | Slot Position |
|
|
113
|
+
| ------------------ | ------------------ |
|
|
114
|
+
| `beforeNavigation` | `shell:nav-start` |
|
|
115
|
+
| `afterNavigation` | `shell:nav-end` |
|
|
116
|
+
| `header` | `shell:header` |
|
|
117
|
+
| `footer` | `shell:footer` |
|
|
118
|
+
| `beforeDashboard` | `dashboard:before` |
|
|
119
|
+
| `afterDashboard` | `dashboard:after` |
|
|
120
|
+
| `beforeLogin` | `login:before` |
|
|
121
|
+
| `afterLogin` | `login:after` |
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
admin: {
|
|
125
|
+
components: {
|
|
126
|
+
beforeDashboard: () =>
|
|
127
|
+
import('./app/announcement-banner.component').then((m) => m.AnnouncementBanner),
|
|
128
|
+
footer: () =>
|
|
129
|
+
import('./app/custom-footer.component').then((m) => m.CustomFooter),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Per-collection config keys
|
|
135
|
+
|
|
136
|
+
`beforeList`, `afterList`, `beforeEdit`, `afterEdit`, `editSidebar`, `beforeView`, `afterView`
|
|
137
|
+
|
|
138
|
+
### Provider-level (app.config.ts)
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { provideAdminSlot } from '@momentumcms/admin';
|
|
142
|
+
|
|
143
|
+
providers: [
|
|
144
|
+
provideAdminSlot('shell:header', () =>
|
|
145
|
+
import('./env-banner.component').then((m) => m.EnvBanner),
|
|
146
|
+
),
|
|
147
|
+
// Per-collection:
|
|
148
|
+
provideAdminSlot('collection-list:before:articles', () =>
|
|
149
|
+
import('./articles-filter.component').then((m) => m.ArticlesFilter),
|
|
150
|
+
),
|
|
151
|
+
],
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Component Template
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
158
|
+
import type { CollectionConfig } from '@momentumcms/core';
|
|
159
|
+
|
|
160
|
+
@Component({
|
|
161
|
+
selector: 'app-custom-slot',
|
|
162
|
+
host: { class: 'block' },
|
|
163
|
+
template: `
|
|
164
|
+
<div class="p-4 bg-mcms-muted rounded-lg">
|
|
165
|
+
@if (collection(); as col) {
|
|
166
|
+
<p>Collection: {{ col.slug }}</p>
|
|
167
|
+
}
|
|
168
|
+
</div>
|
|
169
|
+
`,
|
|
170
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
171
|
+
})
|
|
172
|
+
export class CustomSlotComponent {
|
|
173
|
+
readonly collection = input<CollectionConfig>();
|
|
174
|
+
readonly entityId = input<string>();
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Exports from @momentumcms/admin
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import {
|
|
182
|
+
provideAdminComponent, // Register page override via DI
|
|
183
|
+
provideAdminSlot, // Register slot component via DI
|
|
184
|
+
} from '@momentumcms/admin';
|
|
185
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: headless-ui
|
|
3
|
+
description: Use @momentumcms/headless inside generated Momentum apps. Use when building custom public UI, composing accessible primitives, configuring global styles for hdl-* elements, or adding app-level tests around headless interactions.
|
|
4
|
+
argument-hint: <feature-or-primitive>
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Use Headless UI In Generated Apps
|
|
8
|
+
|
|
9
|
+
Use this skill when a generated app needs custom UI built on `@momentumcms/headless`.
|
|
10
|
+
|
|
11
|
+
## Prefer Headless For
|
|
12
|
+
|
|
13
|
+
- Public-facing UI that should not look like the admin
|
|
14
|
+
- App-specific design systems that still need solid keyboard and ARIA behavior
|
|
15
|
+
- Custom menus, dialogs, comboboxes, chips, tabs, and form fields
|
|
16
|
+
|
|
17
|
+
Use `@momentumcms/admin` when you want the built-in admin screens instead of custom surfaces.
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. Import the needed primitive classes into the Angular component that renders them.
|
|
22
|
+
2. Style them from `src/styles.css` or your global Tailwind layer, not from the library.
|
|
23
|
+
3. Target `data-slot`, `data-state`, `data-disabled`, and overlay classes such as `.hdl-dialog-panel`.
|
|
24
|
+
4. Add visible state readouts for important interactions so browser tests can prove the UI intention.
|
|
25
|
+
5. Update app routes or showcase pages if you are adding a reusable demo surface.
|
|
26
|
+
|
|
27
|
+
## Styling Rules
|
|
28
|
+
|
|
29
|
+
- Start with design tokens in `@layer base`.
|
|
30
|
+
- Add shared recipes in `@layer components`.
|
|
31
|
+
- Use host `class=""` only for local one-off overrides.
|
|
32
|
+
- For projected child visuals, style the child markup you render inside the primitive.
|
|
33
|
+
- Keep `[hidden]` behavior intact for slots that collapse or unmount visually.
|
|
34
|
+
|
|
35
|
+
## Example Shape
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
|
|
39
|
+
import { HdlCombobox, HdlComboboxInput, HdlComboboxPopup, HdlOption } from '@momentumcms/headless';
|
|
40
|
+
|
|
41
|
+
@Component({
|
|
42
|
+
selector: 'app-filter-combobox',
|
|
43
|
+
imports: [HdlCombobox, HdlComboboxInput, HdlComboboxPopup, HdlOption],
|
|
44
|
+
template: `
|
|
45
|
+
<hdl-combobox [value]="selected()" (valueChange)="selected.set($event)">
|
|
46
|
+
<input hdlComboboxInput [value]="query()" (input)="query.set(search.value)" #search />
|
|
47
|
+
<hdl-combobox-popup>
|
|
48
|
+
@for (item of filteredItems(); track item) {
|
|
49
|
+
<hdl-option [value]="item">{{ item }}</hdl-option>
|
|
50
|
+
}
|
|
51
|
+
</hdl-combobox-popup>
|
|
52
|
+
</hdl-combobox>
|
|
53
|
+
|
|
54
|
+
<p>Selected: {{ selected() || 'none' }}</p>
|
|
55
|
+
`,
|
|
56
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
57
|
+
})
|
|
58
|
+
export class FilterComboboxComponent {
|
|
59
|
+
readonly items = ['Articles', 'Pages', 'Authors', 'Media'];
|
|
60
|
+
readonly query = signal('');
|
|
61
|
+
readonly selected = signal('');
|
|
62
|
+
readonly filteredItems = computed(() => {
|
|
63
|
+
const query = this.query().trim().toLowerCase();
|
|
64
|
+
return query ? this.items.filter((item) => item.toLowerCase().includes(query)) : this.items;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Test Expectations
|
|
70
|
+
|
|
71
|
+
- Unit tests should cover app-specific state mapping and conditional rendering.
|
|
72
|
+
- Browser tests should assert the intended behavior, not just the presence of `hdl-*` tags.
|
|
73
|
+
- Good assertions: combobox filtering works, menu clicks update the readout, chips remove correctly, dialog actions dismiss as expected.
|
|
74
|
+
|
|
75
|
+
## If You Need More Than Consumption
|
|
76
|
+
|
|
77
|
+
If the app task actually requires changing the library itself, switch to the repo skill for maintaining `libs/headless` instead of patching around the library from the app.
|