@smartsoft001-mobilems/claude-plugins 2.86.0 → 2.88.0
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/package.json +1 -1
- package/plugins/mobilems/.claude-plugin/plugin.json +1 -1
- package/plugins/mobilems/skills/mobilems-articles/SKILL.md +108 -0
- package/plugins/mobilems/skills/mobilems-articles/references/lib-contents.md +164 -0
- package/plugins/mobilems/skills/mobilems-articles/references/module-and-routes.md +146 -0
- package/plugins/mobilems/skills/mobilems-articles/references/shared-components.md +56 -0
- package/plugins/mobilems/skills/mobilems-articles/scripts/detect-init.sh +29 -0
- package/plugins/mobilems/skills/mobilems-articles/scripts/verify-module.sh +61 -0
- package/plugins/mobilems/skills/mobilems-events/SKILL.md +65 -0
- package/plugins/mobilems/skills/mobilems-events/references/lib-contents.md +212 -0
- package/plugins/mobilems/skills/mobilems-events/references/module-and-routes.md +113 -0
- package/plugins/mobilems/skills/mobilems-events/scripts/detect-init.sh +29 -0
- package/plugins/mobilems/skills/mobilems-events/scripts/verify-module.sh +59 -0
- package/plugins/mobilems/skills/mobilems-object-collections/SKILL.md +77 -0
- package/plugins/mobilems/skills/mobilems-object-collections/references/lib-contents.md +76 -0
- package/plugins/mobilems/skills/mobilems-object-collections/references/module-and-routes.md +116 -0
- package/plugins/mobilems/skills/mobilems-object-collections/references/shared-components.md +61 -0
- package/plugins/mobilems/skills/mobilems-object-collections/scripts/detect-init.sh +29 -0
- package/plugins/mobilems/skills/mobilems-object-collections/scripts/verify-module.sh +44 -0
- package/plugins/mobilems/skills/mobilems-objects/SKILL.md +80 -0
- package/plugins/mobilems/skills/mobilems-objects/references/lib-contents.md +68 -0
- package/plugins/mobilems/skills/mobilems-objects/references/module-and-routes.md +108 -0
- package/plugins/mobilems/skills/mobilems-objects/references/shared-components.md +69 -0
- package/plugins/mobilems/skills/mobilems-objects/scripts/detect-init.sh +29 -0
- package/plugins/mobilems/skills/mobilems-objects/scripts/verify-module.sh +51 -0
- package/plugins/mobilems/skills/mobilems-partners/SKILL.md +65 -0
- package/plugins/mobilems/skills/mobilems-partners/references/lib-contents.md +160 -0
- package/plugins/mobilems/skills/mobilems-partners/references/module-and-routes.md +125 -0
- package/plugins/mobilems/skills/mobilems-partners/scripts/detect-init.sh +29 -0
- package/plugins/mobilems/skills/mobilems-partners/scripts/verify-module.sh +59 -0
- package/plugins/mobilems/skills/mobilems-project-init/SKILL.md +151 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/app-shell.md +115 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/claude-config.md +44 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/config-providers.md +203 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/npm-deps.md +66 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/qa-hardening.md +124 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/server-env.md +97 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/shared-lib.md +125 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/tailwind-styles.md +112 -0
- package/plugins/mobilems/skills/mobilems-project-init/scripts/classify-shared-components.py +114 -0
- package/plugins/mobilems/skills/mobilems-project-init/scripts/detect-base.sh +49 -0
- package/plugins/mobilems/skills/mobilems-project-init/scripts/verify-init.sh +81 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/SKILL.md +60 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/references/lib-contents.md +134 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/references/module-and-routes.md +107 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/scripts/detect-init.sh +29 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/scripts/verify-module.sh +56 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# libs/museum-events — structure to create
|
|
2
|
+
|
|
3
|
+
Copy from the reference MobilEMS app's `libs/museum-events` and thread `<PREFIX>`. Events ships a
|
|
4
|
+
single project: `shell/angular`. It is a standalone booking module — it has no `domain` library, no
|
|
5
|
+
NgRx `+state`, and no shared-module `forFeature`. Everything it needs lives in `shell/angular`.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
libs/museum-events/
|
|
9
|
+
└── shell/angular/ # project: museum-events-shell-angular
|
|
10
|
+
├── eslint.config.mjs
|
|
11
|
+
├── jest.config.ts
|
|
12
|
+
├── project.json
|
|
13
|
+
├── tsconfig.json
|
|
14
|
+
├── tsconfig.lib.json
|
|
15
|
+
├── tsconfig.spec.json
|
|
16
|
+
└── src/
|
|
17
|
+
├── index.ts # export * from './lib';
|
|
18
|
+
├── test-setup.ts # setupZoneTestEnv(...) from jest-preset-angular
|
|
19
|
+
├── __mocks__/
|
|
20
|
+
│ └── @smartsoft001-mobilems/
|
|
21
|
+
│ ├── angular.ts # jest mock — keep
|
|
22
|
+
│ └── models.ts # jest mock — keep
|
|
23
|
+
└── lib/
|
|
24
|
+
├── index.ts # export * from './events.module'; export * from './events.routes';
|
|
25
|
+
├── events.module.ts # MuseumEventsModule (references/module-and-routes.md)
|
|
26
|
+
├── events.routes.ts # eventsRoutes — lazy loadComponent per page + Masonry on list
|
|
27
|
+
├── models/
|
|
28
|
+
│ └── events.models.ts # IEvent, IEventResponse, IEventsSearchResponse, IEventPeriod,
|
|
29
|
+
│ # IEventPeriodsResponse, IReservationRequest, IReservationResponse,
|
|
30
|
+
│ # IReservationValidationError
|
|
31
|
+
├── pages/
|
|
32
|
+
│ ├── list/ # EventsListComponent (+ template/styles)
|
|
33
|
+
│ ├── item/ # EventItemComponent
|
|
34
|
+
│ ├── booking/ # BookingComponent (+ .spec)
|
|
35
|
+
│ ├── confirm/ # ConfirmComponent (+ .spec)
|
|
36
|
+
│ └── cancel/ # CancelComponent (+ .spec)
|
|
37
|
+
├── services/
|
|
38
|
+
│ └── events-reservation.service.ts # EventsReservationService (+ .spec)
|
|
39
|
+
└── pipes/
|
|
40
|
+
└── time-format.pipe.ts # TimeFormatPipe (standalone)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> Copy only what the reference has. There is **no `domain/` directory** for events and **no
|
|
44
|
+
> `+state/` directory** — its event/reservation shapes are plain TypeScript interfaces in
|
|
45
|
+
> `models/events.models.ts`.
|
|
46
|
+
|
|
47
|
+
## `shell/angular/src/index.ts` and `lib/index.ts`
|
|
48
|
+
|
|
49
|
+
The public barrel re-exports the lib barrel, which exports the module + the routes:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// src/index.ts
|
|
53
|
+
export * from './lib';
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// src/lib/index.ts
|
|
58
|
+
export * from './events.module';
|
|
59
|
+
export * from './events.routes';
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Models — `lib/models/events.models.ts` (verbatim — pure types)
|
|
63
|
+
|
|
64
|
+
Plain interfaces describing the events API and the reservation flow. Thread nothing; copy verbatim.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
export interface IEvent {
|
|
68
|
+
id: number;
|
|
69
|
+
name: string;
|
|
70
|
+
image: string | null;
|
|
71
|
+
description: string | null;
|
|
72
|
+
startDate: string | null;
|
|
73
|
+
endDate: string | null;
|
|
74
|
+
ticketsLeft: number;
|
|
75
|
+
price: number;
|
|
76
|
+
location: {
|
|
77
|
+
name: string | null;
|
|
78
|
+
latitude: number | null;
|
|
79
|
+
longitude: number | null;
|
|
80
|
+
} | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface IEventResponse {
|
|
84
|
+
data: IEvent;
|
|
85
|
+
status: number;
|
|
86
|
+
message: string | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface IEventsSearchResponse {
|
|
90
|
+
data: {
|
|
91
|
+
paginatorDetails: {
|
|
92
|
+
currentPage: number;
|
|
93
|
+
maxPerPage: number;
|
|
94
|
+
totalItemsCount: number;
|
|
95
|
+
totalPagesCount: number;
|
|
96
|
+
};
|
|
97
|
+
items: IEvent[];
|
|
98
|
+
};
|
|
99
|
+
status: number;
|
|
100
|
+
message: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface IEventPeriod {
|
|
104
|
+
id: number;
|
|
105
|
+
start_time: string;
|
|
106
|
+
end_time: string;
|
|
107
|
+
available_seats: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface IEventPeriodsResponse {
|
|
111
|
+
periods: IEventPeriod[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface IReservationRequest {
|
|
115
|
+
email: string;
|
|
116
|
+
firstName: string;
|
|
117
|
+
lastName?: string;
|
|
118
|
+
numberOfParticipants: number;
|
|
119
|
+
periodId: number;
|
|
120
|
+
eventId: number;
|
|
121
|
+
date: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface IReservationResponse {
|
|
125
|
+
message: string;
|
|
126
|
+
code: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface IReservationValidationError {
|
|
130
|
+
errors?: Record<string, string>;
|
|
131
|
+
message?: string;
|
|
132
|
+
code: number;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Service — `lib/services/events-reservation.service.ts`
|
|
137
|
+
|
|
138
|
+
`EventsReservationService` is `@Injectable({ providedIn: 'root' })`. It injects `HttpClient`, reads
|
|
139
|
+
`environment.apiUrl` from `@<PREFIX>/angular`, and calls the events API: `getEvent`,
|
|
140
|
+
`getEvents` (paged search), `getAvailableDates` and `getPeriods` (the calendar / time-slot picker),
|
|
141
|
+
and `register` / `confirm` / `cancel` (the reservation lifecycle). Copy verbatim and thread `<PREFIX>`
|
|
142
|
+
on the `environment` import:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { environment } from '@<PREFIX>/angular';
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
It depends on no shared module — the booking flow is entirely self-contained.
|
|
149
|
+
|
|
150
|
+
## Pipe — `lib/pipes/time-format.pipe.ts` (verbatim)
|
|
151
|
+
|
|
152
|
+
A small standalone pipe that trims a time string to `HH:mm`:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { Pipe, PipeTransform } from '@angular/core';
|
|
156
|
+
|
|
157
|
+
@Pipe({ name: 'timeFormat', standalone: true })
|
|
158
|
+
export class TimeFormatPipe implements PipeTransform {
|
|
159
|
+
transform(value: string): string {
|
|
160
|
+
if (!value) return value;
|
|
161
|
+
return value.substring(0, 5);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Pages — `lib/pages/{list,item,booking,confirm,cancel}` (copy verbatim, thread `<PREFIX>`)
|
|
167
|
+
|
|
168
|
+
All five are standalone components, lazy-loaded by `events.routes.ts`:
|
|
169
|
+
|
|
170
|
+
- **list** — `EventsListComponent`: the Masonry grid of events (uses `getEvents`).
|
|
171
|
+
- **item** — `EventItemComponent`: a single event's detail (uses `getEvent`).
|
|
172
|
+
- **booking** — `BookingComponent`: the reservation form (date picker via `getAvailableDates`,
|
|
173
|
+
time-slot via `getPeriods`, submit via `register`).
|
|
174
|
+
- **confirm** — `ConfirmComponent`: confirmation landing for the `:id/potwierdzenie` route
|
|
175
|
+
(`confirm(token)`).
|
|
176
|
+
- **cancel** — `CancelComponent`: cancellation landing for the `:id/anulowanie` route
|
|
177
|
+
(`cancel(token)`).
|
|
178
|
+
|
|
179
|
+
Thread `<PREFIX>` on any `@<PREFIX>/angular` imports. The components also use `@ngx-translate/core`,
|
|
180
|
+
`@smartsoft001-mobilems/angular`, `@smartsoft001/angular`, and `@thisissoon/angular-masonry` — all
|
|
181
|
+
foundation packages.
|
|
182
|
+
|
|
183
|
+
## Jest mocks — `src/__mocks__/@smartsoft001-mobilems/{angular,models}.ts` (keep)
|
|
184
|
+
|
|
185
|
+
Copy both mock files verbatim. The jest config maps `@smartsoft001-mobilems/angular` and
|
|
186
|
+
`@smartsoft001-mobilems/models` to these mocks (see `moduleNameMapper` below) so the lib's specs run
|
|
187
|
+
without pulling the real packages.
|
|
188
|
+
|
|
189
|
+
## Wiring
|
|
190
|
+
|
|
191
|
+
- **tsconfig.base.json path** — add the single shell path (events has no domain path):
|
|
192
|
+
```json
|
|
193
|
+
"@<PREFIX>/museum-events/shell/angular": ["libs/museum-events/shell/angular/src/index.ts"]
|
|
194
|
+
```
|
|
195
|
+
- **project.json** — copy the project config (name `museum-events-shell-angular`) and normalize every
|
|
196
|
+
path field to the lib's real location `libs/museum-events/shell/angular/...`. The reference's
|
|
197
|
+
`sourceRoot` is stale (`libs/museum-events/src` — missing the `shell/angular` segment); set it to
|
|
198
|
+
`libs/museum-events/shell/angular/src`. The `jestConfig` / `tsConfig` target options already point
|
|
199
|
+
at `libs/museum-events/shell/angular/...` — verify each field against a known-good sibling and fix
|
|
200
|
+
only the stale ones.
|
|
201
|
+
- **jest.config.ts** — copy with `displayName: 'museum-events-shell-angular'`,
|
|
202
|
+
`coverageDirectory: '../../../../coverage/libs/museum-events/shell/angular'`, and the
|
|
203
|
+
`moduleNameMapper` pointing `@smartsoft001-mobilems/angular` and `@smartsoft001-mobilems/models` at
|
|
204
|
+
the `src/__mocks__/...` files. Copy `tsconfig.json`, `tsconfig.lib.json`, `tsconfig.spec.json`,
|
|
205
|
+
`eslint.config.mjs`, and `test-setup.ts` as-is.
|
|
206
|
+
- **commit scope** — `commitlint.config.js` reads `libs/` dir names, so `museum-events` becomes a
|
|
207
|
+
valid scope automatically; no edit needed.
|
|
208
|
+
|
|
209
|
+
Events imports only foundation packages (`@smartsoft001-mobilems/angular`,
|
|
210
|
+
`@smartsoft001-mobilems/models`, `@smartsoft001/angular`, `@thisissoon/angular-masonry`,
|
|
211
|
+
`@ngx-translate/core`), all installed by `mobilems-project-init`. It adds no new npm dependency (see
|
|
212
|
+
SKILL.md step 1).
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# MuseumEventsModule + route registration
|
|
2
|
+
|
|
3
|
+
## `libs/museum-events/shell/angular/src/lib/events.module.ts`
|
|
4
|
+
|
|
5
|
+
`MuseumEventsModule` is a pure routing host: it imports `RouterModule.forChild(eventsRoutes)` and
|
|
6
|
+
nothing else. There is no shared-module `forFeature` and no NgRx state — each page is a standalone
|
|
7
|
+
component pulled in lazily by the routes, and data access goes through the standalone
|
|
8
|
+
`EventsReservationService` (`providedIn: 'root'`). Copy this module verbatim; it has no `<PREFIX>` to
|
|
9
|
+
thread.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { NgModule } from '@angular/core';
|
|
13
|
+
import { RouterModule } from '@angular/router';
|
|
14
|
+
|
|
15
|
+
import { eventsRoutes } from './events.routes';
|
|
16
|
+
|
|
17
|
+
@NgModule({
|
|
18
|
+
imports: [RouterModule.forChild(eventsRoutes)],
|
|
19
|
+
})
|
|
20
|
+
export class MuseumEventsModule {}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## `events.routes.ts`
|
|
24
|
+
|
|
25
|
+
Every page is loaded lazily with `loadComponent`. The list route additionally provides the `Masonry`
|
|
26
|
+
SSR factory (the real library in the browser, a no-op mock during SSR). Copy verbatim — there is no
|
|
27
|
+
`<PREFIX>` to thread here either.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Route } from '@angular/router';
|
|
31
|
+
import { Masonry } from '@thisissoon/angular-masonry';
|
|
32
|
+
|
|
33
|
+
export const eventsRoutes: Route[] = [
|
|
34
|
+
{
|
|
35
|
+
path: ':id/potwierdzenie',
|
|
36
|
+
loadComponent: () =>
|
|
37
|
+
import('./pages/confirm/confirm.component').then(
|
|
38
|
+
(m) => m.ConfirmComponent,
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
path: ':id/anulowanie',
|
|
43
|
+
loadComponent: () =>
|
|
44
|
+
import('./pages/cancel/cancel.component').then((m) => m.CancelComponent),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
path: ':id/booking',
|
|
48
|
+
loadComponent: () =>
|
|
49
|
+
import('./pages/booking/booking.component').then(
|
|
50
|
+
(m) => m.BookingComponent,
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: ':id/:slug',
|
|
55
|
+
loadComponent: () =>
|
|
56
|
+
import('./pages/item/item.component').then((m) => m.EventItemComponent),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
path: ':id',
|
|
60
|
+
loadComponent: () =>
|
|
61
|
+
import('./pages/item/item.component').then((m) => m.EventItemComponent),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: '',
|
|
65
|
+
loadComponent: () =>
|
|
66
|
+
import('./pages/list/list.component').then((m) => m.EventsListComponent),
|
|
67
|
+
providers: [
|
|
68
|
+
{
|
|
69
|
+
provide: Masonry,
|
|
70
|
+
useFactory: () => {
|
|
71
|
+
if (typeof window !== 'undefined' && (window as any)['Masonry']) {
|
|
72
|
+
return (window as any)['Masonry'];
|
|
73
|
+
}
|
|
74
|
+
return class {
|
|
75
|
+
layout() {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
reloadItems() {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
destroy() {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> Route order matters: the specific reservation sub-routes (`:id/potwierdzenie`, `:id/anulowanie`,
|
|
93
|
+
> `:id/booking`) come before the generic `:id/:slug` and `:id` detail routes, and the bare list route
|
|
94
|
+
> `''` comes last. Keep this order.
|
|
95
|
+
|
|
96
|
+
## Register in `apps/web/src/app/app.routes.ts`
|
|
97
|
+
|
|
98
|
+
Events registers a single **top-level** route. Add the `wydarzenia` route to the `appRoutes` array
|
|
99
|
+
(idempotently — if a `wydarzenia` route already exists, skip). Events is a booking destination
|
|
100
|
+
browsed from the top level, so it is added at the top level only and is NOT placed under the
|
|
101
|
+
`wyszukaj/:text` search route — events do not appear in search results.
|
|
102
|
+
|
|
103
|
+
Top-level — add to `appRoutes`:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
{
|
|
107
|
+
path: 'wydarzenia',
|
|
108
|
+
loadChildren: () =>
|
|
109
|
+
import('@<PREFIX>/museum-events/shell/angular').then(
|
|
110
|
+
(m) => m.MuseumEventsModule,
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# detect-init.sh <workspace-root>
|
|
4
|
+
#
|
|
5
|
+
# Precondition gate for mobilems-objects (and the other module skills follow the same shape):
|
|
6
|
+
# confirm the MobilEMS foundation from mobilems-project-init is present. Portable to macOS bash 3.2.
|
|
7
|
+
#
|
|
8
|
+
# Exit 0 → foundation present, OK to add the module.
|
|
9
|
+
# Exit 3 → foundation missing (tell the user to run /mobilems:project-init first).
|
|
10
|
+
set -eu
|
|
11
|
+
|
|
12
|
+
ROOT="${1:?usage: detect-init.sh <workspace-root>}"
|
|
13
|
+
cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
|
|
14
|
+
|
|
15
|
+
miss() {
|
|
16
|
+
echo "FOUNDATION MISSING: $1" >&2
|
|
17
|
+
echo "Run /mobilems:project-init first to lay down the MobilEMS foundation." >&2
|
|
18
|
+
exit 3
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
[ -f apps/web/src/app/app.config.ts ] || miss "app.config.ts missing"
|
|
22
|
+
grep -q "SharedModule.forRoot" apps/web/src/app/app.config.ts || miss "SharedModule.forRoot not in app.config.ts (foundation not applied)"
|
|
23
|
+
grep -q "./lib/components" libs/shared/angular/src/index.ts 2>/dev/null || miss "shared-lib components barrel not populated"
|
|
24
|
+
[ -f apps/web/src/app/app.routes.ts ] || miss "app.routes.ts missing"
|
|
25
|
+
|
|
26
|
+
PREFIX="$(grep -oE '@[a-z0-9-]+/angular' tsconfig.base.json | head -1 | sed -E 's#@([a-z0-9-]+)/angular#\1#')"
|
|
27
|
+
echo "FOUNDATION OK. Detected prefix: ${PREFIX:-<unknown>}"
|
|
28
|
+
[ -d libs/museum-objects ] && echo "NOTE: libs/museum-objects already exists — build steps must be idempotent." >&2
|
|
29
|
+
exit 0
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# verify-module.sh <workspace-root> <PREFIX>
|
|
4
|
+
#
|
|
5
|
+
# Post-condition check for mobilems-events: the standalone booking library created
|
|
6
|
+
# (shell/angular only — no domain, no forFeature), MuseumEventsModule wiring RouterModule.forChild,
|
|
7
|
+
# lazy routes, the five pages + reservation service + models + time-format pipe present, the
|
|
8
|
+
# wydarzenia route registered as a single top-level route, and the foundation deps it reuses present.
|
|
9
|
+
# macOS bash 3.2 safe.
|
|
10
|
+
set -eu
|
|
11
|
+
|
|
12
|
+
ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
|
|
13
|
+
PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
|
|
14
|
+
cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
|
|
15
|
+
|
|
16
|
+
fail=0
|
|
17
|
+
pass() { echo " PASS: $1"; }
|
|
18
|
+
bad() { echo " FAIL: $1" >&2; fail=1; }
|
|
19
|
+
|
|
20
|
+
SHELL_LIB=libs/museum-events/shell/angular
|
|
21
|
+
LIB="$SHELL_LIB/src/lib"
|
|
22
|
+
MOD="$LIB/events.module.ts"
|
|
23
|
+
ROUTES="$LIB/events.routes.ts"
|
|
24
|
+
|
|
25
|
+
echo "== Library =="
|
|
26
|
+
[ -d "$SHELL_LIB" ] && pass "shell/angular project present" || bad "libs/museum-events/shell/angular missing"
|
|
27
|
+
[ -f "$MOD" ] && pass "events.module.ts present" || bad "events.module.ts missing"
|
|
28
|
+
grep -q "class MuseumEventsModule" "$MOD" 2>/dev/null && pass "MuseumEventsModule class declared" || bad "MuseumEventsModule class not declared"
|
|
29
|
+
grep -q "RouterModule.forChild" "$MOD" 2>/dev/null && pass "RouterModule.forChild wired" || bad "RouterModule.forChild missing in module"
|
|
30
|
+
[ -f "$ROUTES" ] && pass "events.routes.ts present" || bad "events.routes.ts missing"
|
|
31
|
+
grep -q "@$PREFIX/museum-events/shell/angular" tsconfig.base.json && pass "tsconfig shell path added" || bad "tsconfig @$PREFIX/museum-events/shell/angular path missing"
|
|
32
|
+
|
|
33
|
+
echo "== Pages =="
|
|
34
|
+
for p in list item booking confirm cancel; do
|
|
35
|
+
[ -f "$LIB/pages/$p/$p.component.ts" ] && pass "$p page present" || bad "pages/$p/$p.component.ts missing"
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
echo "== Service, models, pipe =="
|
|
39
|
+
[ -f "$LIB/services/events-reservation.service.ts" ] && pass "events-reservation.service.ts present" || bad "services/events-reservation.service.ts missing"
|
|
40
|
+
grep -q "class EventsReservationService" "$LIB/services/events-reservation.service.ts" 2>/dev/null && pass "EventsReservationService class declared" || bad "EventsReservationService class not declared"
|
|
41
|
+
[ -f "$LIB/models/events.models.ts" ] && pass "events.models.ts present" || bad "models/events.models.ts missing"
|
|
42
|
+
[ -f "$LIB/pipes/time-format.pipe.ts" ] && pass "time-format.pipe.ts present" || bad "pipes/time-format.pipe.ts missing"
|
|
43
|
+
grep -q "class TimeFormatPipe" "$LIB/pipes/time-format.pipe.ts" 2>/dev/null && pass "TimeFormatPipe class declared" || bad "TimeFormatPipe class not declared"
|
|
44
|
+
|
|
45
|
+
echo "== Route registration =="
|
|
46
|
+
APP_ROUTES=apps/web/src/app/app.routes.ts
|
|
47
|
+
grep -q "@$PREFIX/museum-events/shell/angular" "$APP_ROUTES" && pass "events route registered" || bad "events route not in app.routes.ts"
|
|
48
|
+
# single top-level route — expect exactly 1 occurrence
|
|
49
|
+
n=$(grep -c "@$PREFIX/museum-events/shell/angular" "$APP_ROUTES" || true)
|
|
50
|
+
[ "${n:-0}" -eq 1 ] && pass "registered as a single top-level route ($n)" || bad "expected exactly 1 route registration, found $n"
|
|
51
|
+
grep -q "wydarzenia" "$APP_ROUTES" && pass "wydarzenia path present" || bad "wydarzenia path missing"
|
|
52
|
+
|
|
53
|
+
echo "== Foundation deps =="
|
|
54
|
+
grep -q "@smartsoft001-mobilems/angular" package.json && pass "@smartsoft001-mobilems/angular present" || bad "@smartsoft001-mobilems/angular missing"
|
|
55
|
+
grep -q "@smartsoft001-mobilems/models" package.json && pass "@smartsoft001-mobilems/models present" || bad "@smartsoft001-mobilems/models missing"
|
|
56
|
+
|
|
57
|
+
echo
|
|
58
|
+
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
59
|
+
exit "$fail"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mobilems-object-collections
|
|
3
|
+
description: Add the museum COLLECTIONS feature module to a MobilEMS app initialized with mobilems-project-init. Use whenever the user wants to add / scaffold / wire the collections (kolekcje) module, the public collections module, the collection list + detail pages, the collections filters (keywords / cycles), or asks to "dodaj moduł kolekcji". Creates libs/museum-collections (domain + shell/angular), registers the /kolekcje route, builds the collections shared component, and adds the public-collections npm packages. Requires the MobilEMS foundation to exist first.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# MobilEMS collections module
|
|
9
|
+
|
|
10
|
+
Add the **collections** (kolekcje) feature module to a MobilEMS app whose foundation was built by
|
|
11
|
+
`mobilems-project-init`. This skill builds the `libs/museum-collections` library, the collections
|
|
12
|
+
shared component, the collections route, and adds the public-collections npm packages.
|
|
13
|
+
|
|
14
|
+
Collections is a list/detail view backed by `PublicCollectionsSharedModule.forFeature` over the
|
|
15
|
+
`MuseumCollection` entity, with a `filters` component that narrows the list by keywords and cycles.
|
|
16
|
+
|
|
17
|
+
## Precondition
|
|
18
|
+
|
|
19
|
+
Run `scripts/detect-init.sh <workspace-root>`. It confirms the MobilEMS foundation is present
|
|
20
|
+
(`SharedModule.forRoot` in `app.config.ts`, a populated shared-lib barrel). If not, tell the user to
|
|
21
|
+
run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`; reference uses
|
|
22
|
+
`@msr`) and the reference repo path (same one init used).
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
|
|
26
|
+
1. **npm deps** — add `@smartsoft001-mobilems/public-collections-domain`,
|
|
27
|
+
`@smartsoft001-mobilems/public-collections-shell-angular`,
|
|
28
|
+
`@smartsoft001-mobilems/public-collections-shell-dtos` (match the installed mobilems line).
|
|
29
|
+
Resolve versions at run time; do not `npm install` unless asked. No extra third-party viewer deps.
|
|
30
|
+
2. **Library** — create `libs/museum-collections/{domain,shell/angular}` per `references/lib-contents.md`:
|
|
31
|
+
copy the domain entity (`MuseumCollection extends PublicCollection`) and the shell
|
|
32
|
+
(`MuseumCollectionsModule`, routes, `components/filters`, `pages/{list,item}`, `services`), thread
|
|
33
|
+
`<PREFIX>`. Add the two `tsconfig.base.json` paths and the `museum-collections` commit scope.
|
|
34
|
+
3. **Module + routes** — see `references/module-and-routes.md`: `MuseumCollectionsModule` wraps
|
|
35
|
+
`PublicCollectionsSharedModule.forFeature({ routing: false, config: { type: MuseumCollection, apiUrl, entity: 'publicCollections' } })`
|
|
36
|
+
with the `Masonry` SSR factory and the `CrudService → CollectionsService` provider. Add the lazy
|
|
37
|
+
`kolekcje` route to `appRoutes` in `apps/web/src/app/app.routes.ts` and a matching child under the
|
|
38
|
+
`wyszukaj/:text` search route.
|
|
39
|
+
4. **Collections shared component** — see `references/shared-components.md`: build
|
|
40
|
+
`components/collections-grid-item` (it imports `public-collections-domain`) in the shared lib and
|
|
41
|
+
export it from `components/index.ts`. Collections contributes no widget-map entry and no model type
|
|
42
|
+
(those are objects-only).
|
|
43
|
+
5. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib +
|
|
44
|
+
`MuseumCollection` entity exist, the `forFeature` is wired, the `kolekcje` route is registered
|
|
45
|
+
(top-level + search child), the `collections-grid-item` component is built and exported, and the
|
|
46
|
+
three public-collections deps are present. Fix and re-run.
|
|
47
|
+
|
|
48
|
+
## Known QA pitfalls (from live deployments — fix while copying, not after)
|
|
49
|
+
|
|
50
|
+
Each of these was a real Linear bug on a museum project built with this skill (Ostrowiec MOB-961/994,
|
|
51
|
+
Tomaszów MOB-956). The cross-cutting rules live in `mobilems-project-init`
|
|
52
|
+
`references/qa-hardening.md`; these are the collections-specific ones:
|
|
53
|
+
|
|
54
|
+
- **One card component, reused everywhere.** The home page and the collection detail must render the
|
|
55
|
+
SAME shared `collections-grid-item` / object card components as the list views — a second ad-hoc
|
|
56
|
+
card on the home page was a High-priority bug (MOB-994). Never fork a card per page.
|
|
57
|
+
- **No hardcoded UI strings.** The detail heading was shipped as a literal `Zbiory {{ title }}` and
|
|
58
|
+
stayed Polish in EN — every static label goes through a translation key (PL + EN in
|
|
59
|
+
`translations.json`). The list page needs a VISIBLE translated heading (not only `sr-only`), and
|
|
60
|
+
exactly one `h1` per page.
|
|
61
|
+
- **Card image fallback:** when a collection thumbnail fails to load, show the app's `no-image`
|
|
62
|
+
placeholder instead of a broken image.
|
|
63
|
+
- **Hover:** underline the card **title** on hover (not the "Zobacz więcej" link) — same pattern as
|
|
64
|
+
objects.
|
|
65
|
+
- **Search debounce 250 ms** in the filters (`@debounce(250)`), total count + shared
|
|
66
|
+
`NextPageComponent` on the list — consistent with objects/news.
|
|
67
|
+
- **Keyword/cycle chips wrap on 360 px** — chips caused page-level horizontal scroll on mobile.
|
|
68
|
+
- **Heading font must cover Polish diacritics** — collection card titles rendered in a fallback
|
|
69
|
+
font because the configured serif (Bacasime Antique) lacks `ą ć ę ł ń ś ź ż`; verify the Tailwind
|
|
70
|
+
heading `fontFamily` is non-empty and latin-ext complete (see qa-hardening).
|
|
71
|
+
- **Warning-clean templates:** remove needless `?.` on non-nullable signals (NG8107) after copying.
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
|
|
75
|
+
- This skill builds only the collections module + its shared component + its route. It never adds
|
|
76
|
+
other feature modules and never re-runs the foundation.
|
|
77
|
+
- Bundled `references/` + `scripts/` ship automatically. Do not hand-edit `plugin.json`.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# libs/museum-collections — structure to create
|
|
2
|
+
|
|
3
|
+
Copy from the reference repo's `libs/museum-collections` and thread `<PREFIX>`. The lib has two
|
|
4
|
+
projects: a `domain` (the entity) and a `shell/angular` (the Angular module + UI). Both rely on Nx
|
|
5
|
+
inferred targets + the `tsconfig.base.json` path mappings — there are **no** `project.json` /
|
|
6
|
+
`package.json` / `tsconfig.*.json` / `jest.config` / `__mocks__` files under either project (only
|
|
7
|
+
`src/`). This is leaner than `museum-objects`; do not invent config files the reference doesn't have.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
libs/museum-collections/
|
|
11
|
+
├── domain/ # project: museum-collections-domain (inferred)
|
|
12
|
+
│ └── src/
|
|
13
|
+
│ ├── index.ts # export * from './lib/entities';
|
|
14
|
+
│ └── lib/
|
|
15
|
+
│ └── entities/ # MuseumCollection extends PublicCollection (+ index)
|
|
16
|
+
└── shell/angular/ # project: museum-collections-shell-angular (inferred)
|
|
17
|
+
└── src/
|
|
18
|
+
├── index.ts # export * from './lib/pages'; ./lib/components; ./lib/collections.module
|
|
19
|
+
└── lib/
|
|
20
|
+
├── collections.module.ts # MuseumCollectionsModule (references/module-and-routes.md)
|
|
21
|
+
├── collections.routes.ts
|
|
22
|
+
├── components/ # filters (keywords/cycles) + index
|
|
23
|
+
├── pages/ # list (ListComponent), item (ItemComponent) + index
|
|
24
|
+
└── services/ # collections.service.ts + index
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
There are no `enums/`, `specifications/`, or `value-objects/` dirs in the domain — the connection
|
|
28
|
+
shape (keywords/cycles) is inlined on the entity below.
|
|
29
|
+
|
|
30
|
+
## Domain entity (verbatim, thread nothing — pure types)
|
|
31
|
+
|
|
32
|
+
`MuseumCollection extends PublicCollection` (from `@smartsoft001-mobilems/public-collections-domain`)
|
|
33
|
+
adds `paginatorDetails?` and a `connections?` block (keywords + cycles). Reproduce exactly:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Model } from '@smartsoft001/models';
|
|
37
|
+
import { PublicCollection } from '@smartsoft001-mobilems/public-collections-domain';
|
|
38
|
+
|
|
39
|
+
@Model({})
|
|
40
|
+
export class MuseumCollection extends PublicCollection {
|
|
41
|
+
paginatorDetails?: {
|
|
42
|
+
currentPage: number;
|
|
43
|
+
maxPerPage: number;
|
|
44
|
+
totalItemsCount: number;
|
|
45
|
+
totalPagesCount: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
connections?: {
|
|
49
|
+
keywords: {
|
|
50
|
+
id: number;
|
|
51
|
+
name: string;
|
|
52
|
+
count: number;
|
|
53
|
+
}[];
|
|
54
|
+
cycles: {
|
|
55
|
+
id: number;
|
|
56
|
+
name: string;
|
|
57
|
+
count: number;
|
|
58
|
+
}[];
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Wiring
|
|
64
|
+
|
|
65
|
+
- **tsconfig.base.json paths** (add both):
|
|
66
|
+
```json
|
|
67
|
+
"@<PREFIX>/museum-collections/domain": ["libs/museum-collections/domain/src/index.ts"],
|
|
68
|
+
"@<PREFIX>/museum-collections/shell/angular": ["libs/museum-collections/shell/angular/src/index.ts"]
|
|
69
|
+
```
|
|
70
|
+
- **project.json** — none in the reference; both projects use Nx inferred targets. Do not add one.
|
|
71
|
+
- **commit scope** — `commitlint.config.js` reads `libs/` dir names, so `museum-collections` becomes
|
|
72
|
+
a valid scope automatically; no edit needed.
|
|
73
|
+
|
|
74
|
+
The `components/filters` component renders the keyword/cycle facets exposed by the entity's
|
|
75
|
+
`connections` block; the list/detail rendering itself comes from `PublicCollectionsSharedModule`.
|
|
76
|
+
No third-party viewer deps are needed (unlike the objects 3D tab).
|