@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.
Files changed (47) hide show
  1. package/package.json +1 -1
  2. package/plugins/mobilems/.claude-plugin/plugin.json +1 -1
  3. package/plugins/mobilems/skills/mobilems-articles/SKILL.md +108 -0
  4. package/plugins/mobilems/skills/mobilems-articles/references/lib-contents.md +164 -0
  5. package/plugins/mobilems/skills/mobilems-articles/references/module-and-routes.md +146 -0
  6. package/plugins/mobilems/skills/mobilems-articles/references/shared-components.md +56 -0
  7. package/plugins/mobilems/skills/mobilems-articles/scripts/detect-init.sh +29 -0
  8. package/plugins/mobilems/skills/mobilems-articles/scripts/verify-module.sh +61 -0
  9. package/plugins/mobilems/skills/mobilems-events/SKILL.md +65 -0
  10. package/plugins/mobilems/skills/mobilems-events/references/lib-contents.md +212 -0
  11. package/plugins/mobilems/skills/mobilems-events/references/module-and-routes.md +113 -0
  12. package/plugins/mobilems/skills/mobilems-events/scripts/detect-init.sh +29 -0
  13. package/plugins/mobilems/skills/mobilems-events/scripts/verify-module.sh +59 -0
  14. package/plugins/mobilems/skills/mobilems-object-collections/SKILL.md +77 -0
  15. package/plugins/mobilems/skills/mobilems-object-collections/references/lib-contents.md +76 -0
  16. package/plugins/mobilems/skills/mobilems-object-collections/references/module-and-routes.md +116 -0
  17. package/plugins/mobilems/skills/mobilems-object-collections/references/shared-components.md +61 -0
  18. package/plugins/mobilems/skills/mobilems-object-collections/scripts/detect-init.sh +29 -0
  19. package/plugins/mobilems/skills/mobilems-object-collections/scripts/verify-module.sh +44 -0
  20. package/plugins/mobilems/skills/mobilems-objects/SKILL.md +80 -0
  21. package/plugins/mobilems/skills/mobilems-objects/references/lib-contents.md +68 -0
  22. package/plugins/mobilems/skills/mobilems-objects/references/module-and-routes.md +108 -0
  23. package/plugins/mobilems/skills/mobilems-objects/references/shared-components.md +69 -0
  24. package/plugins/mobilems/skills/mobilems-objects/scripts/detect-init.sh +29 -0
  25. package/plugins/mobilems/skills/mobilems-objects/scripts/verify-module.sh +51 -0
  26. package/plugins/mobilems/skills/mobilems-partners/SKILL.md +65 -0
  27. package/plugins/mobilems/skills/mobilems-partners/references/lib-contents.md +160 -0
  28. package/plugins/mobilems/skills/mobilems-partners/references/module-and-routes.md +125 -0
  29. package/plugins/mobilems/skills/mobilems-partners/scripts/detect-init.sh +29 -0
  30. package/plugins/mobilems/skills/mobilems-partners/scripts/verify-module.sh +59 -0
  31. package/plugins/mobilems/skills/mobilems-project-init/SKILL.md +151 -0
  32. package/plugins/mobilems/skills/mobilems-project-init/references/app-shell.md +115 -0
  33. package/plugins/mobilems/skills/mobilems-project-init/references/claude-config.md +44 -0
  34. package/plugins/mobilems/skills/mobilems-project-init/references/config-providers.md +203 -0
  35. package/plugins/mobilems/skills/mobilems-project-init/references/npm-deps.md +66 -0
  36. package/plugins/mobilems/skills/mobilems-project-init/references/qa-hardening.md +124 -0
  37. package/plugins/mobilems/skills/mobilems-project-init/references/server-env.md +97 -0
  38. package/plugins/mobilems/skills/mobilems-project-init/references/shared-lib.md +125 -0
  39. package/plugins/mobilems/skills/mobilems-project-init/references/tailwind-styles.md +112 -0
  40. package/plugins/mobilems/skills/mobilems-project-init/scripts/classify-shared-components.py +114 -0
  41. package/plugins/mobilems/skills/mobilems-project-init/scripts/detect-base.sh +49 -0
  42. package/plugins/mobilems/skills/mobilems-project-init/scripts/verify-init.sh +81 -0
  43. package/plugins/mobilems/skills/mobilems-thematic-publications/SKILL.md +60 -0
  44. package/plugins/mobilems/skills/mobilems-thematic-publications/references/lib-contents.md +134 -0
  45. package/plugins/mobilems/skills/mobilems-thematic-publications/references/module-and-routes.md +107 -0
  46. package/plugins/mobilems/skills/mobilems-thematic-publications/scripts/detect-init.sh +29 -0
  47. package/plugins/mobilems/skills/mobilems-thematic-publications/scripts/verify-module.sh +56 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartsoft001-mobilems/claude-plugins",
3
- "version": "2.86.0",
3
+ "version": "2.88.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "mobilems",
3
3
  "description": "MobilEMS-specific Claude Code skills (MuzaCMS / MobileMS indexer API)",
4
- "version": "2.86.0"
4
+ "version": "2.88.0"
5
5
  }
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: mobilems-articles
3
+ description: Add an article (content) feature module to a MobilEMS app initialized with mobilems-project-init. The module is parametrized — you give it a NAME and a TEMPLATE value, and it builds an Article-backed list+detail module whose API requests are filtered by that template. Use whenever the user wants to add / scaffold / wire an articles, news (aktualności), education, or any template-filtered content module, or asks to "dodaj moduł artykułów / aktualności". One run builds one named module; run it again for each additional content type. Creates libs/museum-<name> (domain + shell/angular), registers its route, builds its article card shared components, and adds the articles 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 article module
9
+
10
+ Build a **parametrized article module** on top of a MobilEMS foundation produced by
11
+ `mobilems-project-init`. An article module is an Article-backed list + detail feature whose API
12
+ requests carry a fixed `template` filter, so the same module shape powers any number of content
13
+ types — you choose the **name** and the **template value**. A project may have several article
14
+ modules under different names (e.g. one per content category), and the same skill builds each.
15
+
16
+ The module wraps `ArticlesSharedModule.forFeature` over the npm `Article` entity. The only thing that
17
+ distinguishes one article module from another is its name, its route, its `entity` key, the
18
+ `template` value it filters on, and whether it keeps feature-local ngrx state.
19
+
20
+ ## Parameters
21
+
22
+ Collect these in Step 1 with `AskUserQuestion` (or accept them from the invocation if the user already
23
+ gave them). The first two are the essentials:
24
+
25
+ | Parameter | What it is | Example |
26
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------- |
27
+ | **name** | the module name → folder `museum-<name>`, class `Museum<Name>Module`, lib alias `@<PREFIX>/museum-<name>` | `news` |
28
+ | **template** | the value sent in the API request filter `baseQuery: [{ key: 'template', value: '<template>', type: '=', hidden: true }]` | `AKTUALNOŚCI` |
29
+ | route | the URL path segment for the module's pages | `aktualnosci` |
30
+ | entity | the `forFeature` `entity` key (API entity + ngrx state key); often equals the name | `news` |
31
+ | feature state? | whether the module keeps its own ngrx state (adds a `reducerFactory`) | yes / no |
32
+
33
+ > The filter **key is always `template`** — only the **value** changes per module. Copy the file
34
+ > structure from any existing article module in the reference MobilEMS app the project was built from,
35
+ > adapting its name/route/entity/template; don't treat that app's particular modules as a fixed set to
36
+ > reproduce.
37
+
38
+ ## Precondition
39
+
40
+ Run `scripts/detect-init.sh <workspace-root>`. It confirms the MobilEMS foundation is present
41
+ (`SharedModule.forRoot` in `app.config.ts`, a populated shared-lib barrel). If not, tell the user to
42
+ run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`; reference uses `@msr`)
43
+ and the reference repo path (same one init used).
44
+
45
+ ## Steps
46
+
47
+ 1. **Gather the parameters** above. Ask for the **name** and **template** at minimum; default `route`
48
+ and `entity` to the name unless the user specifies otherwise; ask whether the module needs feature
49
+ state.
50
+ 2. **npm deps** — add `@smartsoft001-mobilems/articles-domain`, `@smartsoft001-mobilems/articles-shell-angular`,
51
+ `@smartsoft001-mobilems/articles-shell-dtos` (match the installed mobilems line). Every article
52
+ module shares these three — if a previous article module already added them, skip. Resolve versions
53
+ at run time; do not `npm install` unless asked. (`@thisissoon/angular-masonry` is already a foundation dep.)
54
+ Also add any **extra third-party packages the copied pages import** that aren't already in the
55
+ workspace — e.g. the reference list page uses `lodash-decorators` (`@debounce`); grep the pages you
56
+ copy for non-`@angular`/`@smartsoft001` imports and add the missing ones.
57
+ 3. **Library** — create `libs/museum-<name>/{domain,shell/angular}` per `references/lib-contents.md`:
58
+ the thin domain entity (`Museum<Name> extends Article`), the shell (`Museum<Name>Module`, routes,
59
+ `pages/{list,item}`, services, and — if the module keeps feature state — `+state/<name>.reducer.ts`),
60
+ threading `<PREFIX>` and the chosen name/entity. Add the two `tsconfig.base.json` paths
61
+ (`@<PREFIX>/museum-<name>/domain` + `/shell/angular`).
62
+ 4. **Module + route** — see `references/module-and-routes.md`: `Museum<Name>Module` wraps
63
+ `ArticlesSharedModule.forFeature` with `type: Article`, the chosen `entity`, and
64
+ `baseQuery: [{ key: 'template', value: '<template>', type: '=', hidden: true }]`, plus the `Masonry`
65
+ SSR factory. Add the lazy `<route>` route to `appRoutes` in `apps/web/src/app/app.routes.ts`, and a
66
+ matching child under the `wyszukaj/:text` search route if the module's results belong in site search.
67
+ Also build the app's **`/article-preview` page** if it isn't there yet (it previews an `Article`, so
68
+ it belongs to the articles feature, not the foundation) — idempotent across runs; see
69
+ `references/module-and-routes.md`.
70
+ 5. **Article card shared components** — see `references/shared-components.md`: build the module's grid
71
+ and list cards (`<name>-grid-item`, `<name>-list-item`) in `libs/shared/angular` and export them; if
72
+ the module renders article-gallery content blocks, also build the article gallery widget and register
73
+ it in the widget content-type map (build once, idempotent across article modules).
74
+ 6. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX> <name> [template]`: asserts the
75
+ library exists, the `forFeature` carries the chosen `entity` (and template, if given), the route is
76
+ registered, and the article card shared components are built and exported. Fix and re-run.
77
+
78
+ ## Known QA pitfalls (from live deployments — fix while copying, not after)
79
+
80
+ Each of these was a real Linear bug on a museum project built with this skill (Ostrowiec MOB-962,
81
+ Tomaszów MOB-957/993). The cross-cutting rules live in `mobilems-project-init`
82
+ `references/qa-hardening.md`; these are the article-module-specific ones:
83
+
84
+ - **Author filter dictionary key is lowercase `authors`.** `dictionaryKey="authors"` routes to
85
+ `api/article/authors/...`; `"Author"` silently falls through to `api/dictionary/Author/...` →
86
+ HTTP 409 and an empty options list (one-character bug that shipped to QA).
87
+ - **alt from CMS titles must strip HTML.** The detail header image shipped with
88
+ `alt="<p>Tytuł</p>"` — pipe the title through `smartRemoveHtml` before binding it to `alt`.
89
+ - **Detail page structure:** an `h1` (article title — copied reference had only `h2`), breadcrumbs
90
+ (`/ Artykuły / {tytuł}`), and gallery image captions rendered as text, NOT `h3` (an `h1 → h3`
91
+ jump is a recurring axe-core finding).
92
+ - **List item field order per mockup:** date ABOVE the title (implementations kept shipping it at
93
+ the bottom), keywords as inline links styled per the design, and mockup-exact sort labels
94
+ ("Sortowanie: Od najnowszych", not "Sortuj: Data publikacji - od najnowszych").
95
+ - **List spacing:** in list view the items need a real gap (`gap-4` fixed the "too close together"
96
+ QA bug).
97
+ - **Search debounce 250 ms** (`@debounce(250)`), total count + shared `NextPageComponent` on the
98
+ list — consistent with objects/collections.
99
+ - **Warning-clean templates:** remove needless `?.` on non-nullable signals (NG8107) and unused
100
+ `@let` declarations (NG8112) after copying.
101
+
102
+ ## Notes
103
+
104
+ - One run builds one named article module. Run the skill again — with a different name + template — for
105
+ each additional content type the project needs.
106
+ - Article modules reuse the foundation's generic `IArticle` model and the foundation's news carousel —
107
+ this skill does not rebuild them.
108
+ - Bundled `references/` + `scripts/` ship automatically. Do not hand-edit `plugin.json`.
@@ -0,0 +1,164 @@
1
+ # libs/museum-<name> — structure to create
2
+
3
+ Copy from an existing article module in the reference MobilEMS app and substitute the module's
4
+ `<name>` / `<entity>`, threading `<PREFIX>`. The library has two projects: a
5
+ thin `domain` (one entity extending the npm `Article`) and a `shell/angular` (the Angular module +
6
+ list/item pages + service). Article modules use the npm `Article` entity directly through
7
+ `ArticlesSharedModule.forFeature`; the local domain entity is a near-empty subclass.
8
+
9
+ ```
10
+ libs/museum-<name>/
11
+ ├── domain/ # project: museum-<name>-domain
12
+ │ └── src/
13
+ │ ├── index.ts # export * from './lib/entities';
14
+ │ └── lib/
15
+ │ ├── entities/ # Museum<Name> extends Article (+ index)
16
+ │ ├── enums/ # .gitkeep
17
+ │ ├── specifications/ # .gitkeep
18
+ │ └── value-objects/ # .gitkeep
19
+ └── shell/angular/ # project: museum-<name>-shell-angular
20
+ └── src/
21
+ ├── index.ts # public barrel (pages + module [+ service])
22
+ └── lib/
23
+ ├── <name>.module.ts # Museum<Name>Module (references/module-and-routes.md)
24
+ ├── <name>.routes.ts
25
+ ├── index.ts # SERVICES const + barrel (see below)
26
+ ├── +state/ # ONLY if the module keeps feature state: <name>.reducer.ts
27
+ ├── pages/ # list (ListComponent), item (ItemComponent) + index (PAGES)
28
+ └── services/ # optional module service (e.g. a visit logger)
29
+ ```
30
+
31
+ ## Domain entity (thin subclass of the npm `Article`)
32
+
33
+ `domain/src/lib/entities/museum-<name>.entity.ts`:
34
+
35
+ ```ts
36
+ import { Model } from '@smartsoft001/models';
37
+ import { Article } from '@smartsoft001-mobilems/articles-domain';
38
+
39
+ @Model({})
40
+ export class Museum<Name> extends Article {}
41
+ ```
42
+
43
+ Then `entities/index.ts` re-exports it and `domain/src/index.ts` does `export * from './lib/entities';`.
44
+ (If the module needs extra fields, add them to this subclass.)
45
+
46
+ ## shell `lib/index.ts` — `SERVICES` + barrel
47
+
48
+ Define a `SERVICES` const here binding `ArticlesService` as the `CrudService`; a module that keeps
49
+ feature state spreads it into its providers:
50
+
51
+ ```ts
52
+ import { CrudService } from '@smartsoft001/crud-shell-angular';
53
+ import { ArticlesService } from '@smartsoft001-mobilems/articles-shell-angular';
54
+
55
+ export * from './pages/index';
56
+ export * from './<name>.module';
57
+
58
+ export const SERVICES = [
59
+ ArticlesService,
60
+ { provide: CrudService, useClass: ArticlesService },
61
+ ];
62
+ ```
63
+
64
+ ## shell `src/index.ts` (public barrel)
65
+
66
+ `export * from './lib/pages';` + `export * from './lib/<name>.module';` (plus the module's own service
67
+ if it ships one — see below).
68
+
69
+ ## `pages/index.ts`
70
+
71
+ ```ts
72
+ import { ItemComponent } from './item/item.component';
73
+ import { ListComponent } from './list/list.component';
74
+
75
+ export * from './item/item.component';
76
+ export * from './list/list.component';
77
+
78
+ export const PAGES = [ListComponent, ItemComponent];
79
+ ```
80
+
81
+ Copy the `list/` and `item/` `.component.ts` + `.html` from a reference article module, threading
82
+ `<PREFIX>`.
83
+
84
+ ## Optional: `+state/<name>.reducer.ts` (feature-local ngrx state)
85
+
86
+ Build this only if the module keeps its own ngrx state (Step 1 "feature state? = yes"). It wraps the
87
+ smart CRUD reducer keyed by the module's `<entity>`:
88
+
89
+ ```ts
90
+ import { Action } from '@ngrx/store';
91
+ import {
92
+ State as SmartState,
93
+ initialState as smartInitialState,
94
+ getReducer as smartGetReducer,
95
+ } from '@smartsoft001/crud-shell-angular';
96
+ import { Article } from '@smartsoft001-mobilems/articles-domain';
97
+
98
+ export type State = SmartState<Article>;
99
+
100
+ export const initialState = { ...smartInitialState };
101
+
102
+ const reducer = (state = initialState, action: any) => {
103
+ switch (action.type) {
104
+ default:
105
+ return smartGetReducer('<entity>')(state, action);
106
+ }
107
+ };
108
+
109
+ export function getReducer() {
110
+ return (state: any, action: Action) => reducer(state, action);
111
+ }
112
+ ```
113
+
114
+ The module then passes `reducerFactory: getReducer` to `forFeature` (see
115
+ `references/module-and-routes.md`). A module without feature state omits `+state/` entirely.
116
+
117
+ ## Optional: a module service
118
+
119
+ A module may ship a small injectable (for example, a visit logger). Copy it from the reference module
120
+ if it has one, threading `<PREFIX>`, and export it from `src/index.ts`:
121
+
122
+ ```ts
123
+ import { HttpClient } from '@angular/common/http';
124
+ import { inject, Injectable } from '@angular/core';
125
+ import { firstValueFrom } from 'rxjs';
126
+
127
+ import { environment } from '@<PREFIX>/angular';
128
+
129
+ @Injectable({ providedIn: 'root' })
130
+ export class Museum<Name>Service {
131
+ private http = inject(HttpClient);
132
+
133
+ logVisit(id: string | number): Promise<void> {
134
+ const url = environment.apiUrl + 'api/article/' + id + '/visit';
135
+ return firstValueFrom(this.http.post<void>(url, {}));
136
+ }
137
+ }
138
+ ```
139
+
140
+ > **Watch for cross-module service imports.** In the reference, some article modules' pages inject
141
+ > _another_ article module's service (e.g. a `news` page importing `MuseumArticlesService` from
142
+ > `@<ref-prefix>/museum-articles/shell/angular`). A generic article module must be self-contained — when
143
+ > a copied page injects a sibling module's service, give this module its **own** `Museum<Name>Service`
144
+ > (the template above) and rewire the page's import to it, rather than depending on a module that may
145
+ > not exist in the target.
146
+
147
+ ## Wiring
148
+
149
+ - **tsconfig.base.json paths** (add both):
150
+ ```json
151
+ "@<PREFIX>/museum-<name>/domain": ["libs/museum-<name>/domain/src/index.ts"],
152
+ "@<PREFIX>/museum-<name>/shell/angular": ["libs/museum-<name>/shell/angular/src/index.ts"]
153
+ ```
154
+ - **project.json / build config** — copy each project's build config and normalize every path field
155
+ the reference baked to its origin location — `sourceRoot`, `dest`, `outputPath`, `coverageDirectory`,
156
+ `jestConfig`, `lintFilePatterns`, and the jest `displayName` — to `libs/museum-<name>/...`. Some reference modules carry
157
+ stale values (e.g. a `sourceRoot` missing the `shell/angular` segment, or a `coverageDirectory`
158
+ under `packages/...`) while others are already correct — verify each against a known-good sibling and
159
+ fix only the stale ones. Set each `package.json` `name` to match (`museum-<name>-domain` /
160
+ `museum-<name>-shell-angular`) — a copied `package.json` may carry a stale name. A `domain` with no
161
+ `project.json` (Nx-inferred) is fine.
162
+ - **commit scope** — `commitlint.config.js` reads `libs/` dir names, so `museum-<name>` becomes a valid
163
+ scope automatically; no edit needed.
164
+ - **jest** — keep the reference `jest.config.ts` / `tsconfig.spec.json` so the shell unit-tests run.
@@ -0,0 +1,146 @@
1
+ # `Museum<Name>Module` + route registration
2
+
3
+ An article module wraps `ArticlesSharedModule.forFeature` over the npm `Article` entity and filters
4
+ its API requests with a `template` value. Thread `<PREFIX>` (the `environment` import) and the chosen
5
+ `<name>` / `<entity>` / `<template>`. The `Masonry` factory returns the real library in the browser
6
+ and a no-op mock during SSR. Copy the file structure from an existing article module in the reference
7
+ MobilEMS app and substitute the parameters.
8
+
9
+ ## `libs/museum-<name>/shell/angular/src/lib/<name>.module.ts`
10
+
11
+ ```ts
12
+ import { NgModule } from '@angular/core';
13
+ import { FormsModule } from '@angular/forms';
14
+ import { RouterModule } from '@angular/router';
15
+ import { SharedModule as SmartSharedModule } from '@smartsoft001/angular';
16
+ import { Article } from '@smartsoft001-mobilems/articles-domain';
17
+ import { ArticlesSharedModule } from '@smartsoft001-mobilems/articles-shell-angular';
18
+ import { Masonry } from '@thisissoon/angular-masonry';
19
+
20
+ import { environment } from '@<PREFIX>/angular';
21
+
22
+ import { routes } from './<name>.routes';
23
+
24
+ @NgModule({
25
+ providers: [
26
+ {
27
+ provide: Masonry,
28
+ useFactory: () => {
29
+ if (typeof window !== 'undefined' && (window as any)['Masonry']) {
30
+ return (window as any)['Masonry'];
31
+ }
32
+ // Return a mock for SSR
33
+ return class {
34
+ constructor() {
35
+ // no-op
36
+ }
37
+ layout() { return; }
38
+ reloadItems() { return; }
39
+ destroy() { return; }
40
+ };
41
+ },
42
+ },
43
+ ],
44
+ imports: [
45
+ FormsModule,
46
+ SmartSharedModule,
47
+ ArticlesSharedModule.forFeature({
48
+ routing: false,
49
+ config: {
50
+ type: Article,
51
+ apiUrl: environment.apiUrl,
52
+ entity: '<entity>',
53
+ baseQuery: [
54
+ { value: '<template>', key: 'template', type: '=', hidden: true },
55
+ ],
56
+ },
57
+ }),
58
+ RouterModule.forChild(routes),
59
+ ],
60
+ })
61
+ export class Museum<Name>Module {}
62
+ ```
63
+
64
+ The **`baseQuery`** is what scopes the module to its content: `key` is always `'template'`, `type` is
65
+ `'='`, `hidden` is `true`, and `value` is the module's `<template>` (the API then returns only records
66
+ matching that template). Change only the `value` between modules.
67
+
68
+ ### Optional: feature-local ngrx state
69
+
70
+ If the module keeps its own ngrx state, build `+state/<name>.reducer.ts` (see
71
+ `references/lib-contents.md`) exporting a `getReducer`, then in the module:
72
+
73
+ - pass `reducerFactory: getReducer` inside the `forFeature` `config`,
74
+ - define a `SERVICES` const in `lib/index.ts` that binds `ArticlesService` as the `CrudService`, and
75
+ spread it into the `providers` (alongside the `Masonry` provider).
76
+
77
+ (In the reference, the stateful article module also omits `SmartSharedModule` from its `imports` and
78
+ imports `ConfigsModule` from `@<PREFIX>/angular` — mirror whichever the reference module you copy
79
+ uses.) A module with no feature state omits `+state/` and the `reducerFactory` entirely.
80
+
81
+ ## `<name>.routes.ts`
82
+
83
+ ```ts
84
+ import { Route } from '@angular/router';
85
+
86
+ import { ItemComponent, ListComponent } from './pages';
87
+
88
+ export const routes: Route[] = [
89
+ { path: ':id/:slug', component: ItemComponent },
90
+ { path: ':id', component: ItemComponent },
91
+ { path: '', component: ListComponent },
92
+ ];
93
+ ```
94
+
95
+ ## Register in `apps/web/src/app/app.routes.ts`
96
+
97
+ Add the module's `<route>` to the `appRoutes` array (idempotently — if a `<route>` route already
98
+ exists, skip). Add it as a top-level route, and — if the module's results belong in site search — as a
99
+ child of the `wyszukaj/:text` route so `/wyszukaj/:text/<route>` works.
100
+
101
+ Top-level — add to `appRoutes`:
102
+
103
+ ```ts
104
+ {
105
+ path: '<route>',
106
+ loadChildren: () =>
107
+ import('@<PREFIX>/museum-<name>/shell/angular').then(
108
+ (m) => m.Museum<Name>Module,
109
+ ),
110
+ },
111
+ ```
112
+
113
+ Search child — add to the `children` of the `wyszukaj/:text` route, before the
114
+ `{ path: '', redirectTo: ... }` default:
115
+
116
+ ```ts
117
+ {
118
+ path: '<route>',
119
+ loadChildren: () =>
120
+ import('@<PREFIX>/museum-<name>/shell/angular').then(
121
+ (m) => m.Museum<Name>Module,
122
+ ),
123
+ },
124
+ ```
125
+
126
+ ## The `/article-preview` page (build once)
127
+
128
+ The app's content-preview page (`apps/web/src/app/article-preview/`) renders an `Article` for CMS
129
+ preview mode, so it imports `@smartsoft001-mobilems/articles-domain` and belongs to the articles
130
+ feature — the foundation deliberately leaves it out. Build it the first time an article module is
131
+ added, and skip it if it already exists (idempotent across article-module runs):
132
+
133
+ 1. Copy `apps/web/src/app/article-preview/` (`.component.ts` + `.html`) from the reference app, thread
134
+ `<PREFIX>`.
135
+ 2. Add its route to `appRoutes` in `apps/web/src/app/app.routes.ts` (a plain top-level route, before
136
+ the `**` catch-all):
137
+
138
+ ```ts
139
+ {
140
+ path: 'article-preview',
141
+ loadComponent: () =>
142
+ import('./article-preview/article-preview.component').then(
143
+ (m) => m.ArticlePreviewComponent,
144
+ ),
145
+ },
146
+ ```
@@ -0,0 +1,56 @@
1
+ # Article module shared components
2
+
3
+ An article module renders its records as cards in the grid view and rows in the list view, so it
4
+ contributes a pair of shared components to `libs/shared/angular` named after the module. Build them by
5
+ copying the card components of an existing article module in the reference MobilEMS app and adapting
6
+ them to the module's `<name>` and `<PREFIX>`. Every step is idempotent — if the target already exists,
7
+ leave it.
8
+
9
+ All paths below are under `libs/shared/angular/src/lib`.
10
+
11
+ ## Article card components — build + export
12
+
13
+ Build these two component directories and export them from `components/index.ts`:
14
+
15
+ | Component | Renders |
16
+ | ----------------------------- | -------------------------------- |
17
+ | `components/<name>-grid-item` | an article card in the grid view |
18
+ | `components/<name>-list-item` | an article row in the list view |
19
+
20
+ ```ts
21
+ // components/index.ts — these exports belong to the <name> module:
22
+ export * from './<name>-grid-item';
23
+ export * from './<name>-list-item';
24
+ ```
25
+
26
+ These render an `Article` (via the foundation's generic `IArticle` model), so they depend on
27
+ `@smartsoft001-mobilems/articles-domain` — which this skill installs. There is no module-specific
28
+ model type to add (contrast `mobilems-objects`, which adds `ISpecificObject` to `objects.model.ts`).
29
+
30
+ ## Optional: article gallery widget
31
+
32
+ If the project renders **article-gallery content blocks** (a page widget that embeds a gallery of
33
+ articles), build the article gallery widget once and register it in the widget content-type map. This
34
+ is shared by all article modules — build it the first time an article module needs it, and skip if a
35
+ previous article module already added it.
36
+
37
+ ```ts
38
+ // components/widget/index.ts:
39
+ export * from './gallery-article/widget-gallery-article.component';
40
+ ```
41
+
42
+ Register it in the widget content-type map in `components/widget/widget.component.ts`:
43
+
44
+ ```ts
45
+ import { WidgetGalleryArticleComponent } from './gallery-article/widget-gallery-article.component';
46
+ // …in the ArticleWidgetType → component map:
47
+ [ArticleWidgetType.GALLERY_ARTICLE]: WidgetGalleryArticleComponent,
48
+ ```
49
+
50
+ The widget container renders sub-widgets via `NgComponentOutlet`, so the gallery widget goes in the
51
+ type-map only — it does **not** go in the container's `imports` array.
52
+
53
+ ## Verify
54
+
55
+ `scripts/verify-module.sh <workspace> <PREFIX> <name>` checks the module's `<name>-grid-item` and
56
+ `<name>-list-item` exist and are exported from the components barrel.
@@ -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.
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,61 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # verify-module.sh <workspace-root> <PREFIX> <name> [template]
4
+ #
5
+ # Post-condition check for one article module named <name>: library created, ArticlesSharedModule
6
+ # .forFeature wired (with the template value if given), route registered, and the module's article
7
+ # card shared components built + exported.
8
+ set -eu
9
+
10
+ ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
11
+ PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
12
+ NAME="${3:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
13
+ TEMPLATE="${4:-}"
14
+ cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
15
+
16
+ LIBF="museum-$NAME"
17
+ fail=0
18
+ pass() { echo " PASS: $1"; }
19
+ bad() { echo " FAIL: $1" >&2; fail=1; }
20
+ info() { echo " INFO: $1"; }
21
+
22
+ echo "== Verifying article module: name=$NAME lib=$LIBF =="
23
+
24
+ echo "== Library =="
25
+ MOD="$(ls libs/$LIBF/shell/angular/src/lib/*.module.ts 2>/dev/null | head -1)"
26
+ [ -n "$MOD" ] && pass "module file present ($MOD)" || bad "no *.module.ts in libs/$LIBF/shell/angular/src/lib"
27
+ [ -f "libs/$LIBF/domain/src/lib/entities/index.ts" ] && pass "domain entity present" || bad "domain entity missing"
28
+ grep -q "$LIBF/shell/angular" tsconfig.base.json && pass "tsconfig shell path added" || bad "tsconfig.base.json shell path missing"
29
+ grep -q "$LIBF/domain" tsconfig.base.json && pass "tsconfig domain path added" || bad "tsconfig.base.json domain path missing"
30
+
31
+ echo "== forFeature wiring =="
32
+ if [ -n "$MOD" ]; then
33
+ grep -q "ArticlesSharedModule.forFeature" "$MOD" && pass "ArticlesSharedModule.forFeature wired" || bad "ArticlesSharedModule.forFeature missing"
34
+ grep -q "key: 'template'" "$MOD" && pass "baseQuery filters on key 'template'" || bad "baseQuery key 'template' missing"
35
+ if [ -n "$TEMPLATE" ]; then
36
+ grep -q "$TEMPLATE" "$MOD" && pass "template value '$TEMPLATE' present" || bad "template value '$TEMPLATE' missing"
37
+ fi
38
+ grep -q "reducerFactory" "$MOD" && info "module keeps feature state (reducerFactory present)" || info "module has no feature state"
39
+ fi
40
+
41
+ echo "== Route registration =="
42
+ ROUTES=apps/web/src/app/app.routes.ts
43
+ n=$(grep -c "$LIBF/shell/angular" "$ROUTES" 2>/dev/null || true)
44
+ [ "${n:-0}" -ge 1 ] && pass "route registered ($n occurrence(s) — top-level$([ "${n:-0}" -ge 2 ] && echo ' + search child'))" || bad "route not in app.routes.ts"
45
+
46
+ echo "== Article card shared components =="
47
+ COMP=libs/shared/angular/src/lib/components
48
+ for d in "$NAME-grid-item" "$NAME-list-item"; do
49
+ [ -d "$COMP/$d" ] && pass "built $d" || bad "missing $COMP/$d"
50
+ done
51
+ grep -q "$NAME-grid-item" "$COMP/index.ts" && pass "$NAME-grid-item exported" || bad "$NAME-grid-item export missing"
52
+ grep -q "$NAME-list-item" "$COMP/index.ts" && pass "$NAME-list-item exported" || bad "$NAME-list-item export missing"
53
+
54
+ echo "== Deps =="
55
+ for p in articles-domain articles-shell-angular articles-shell-dtos; do
56
+ grep -q "@smartsoft001-mobilems/$p" package.json && pass "$p dep" || bad "$p dep missing"
57
+ done
58
+
59
+ echo
60
+ if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
61
+ exit "$fail"
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: mobilems-events
3
+ description: Add the museum EVENTS feature module (wydarzenia) to a MobilEMS app initialized with mobilems-project-init. Use whenever the user wants to add / scaffold / wire the events module, the "wydarzenia" pages (event list + event detail + booking/reservation flow with confirm + cancel), event booking, event reservation, an event calendar / available-dates picker, or asks to "dodaj moduł wydarzeń / rezerwacji wydarzeń". Creates libs/museum-events (shell/angular only — a standalone booking module), registers the /wydarzenia route, and confirms the foundation packages it reuses are present. 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 events module
9
+
10
+ Add the **events** (wydarzenia) feature module to a MobilEMS app whose foundation was built by
11
+ `mobilems-project-init`. Events is a self-contained booking / reservation system: a list of events,
12
+ an event detail page, and a reservation flow (booking form -> confirmation -> cancellation) driven by
13
+ its own `EventsReservationService` over the event / event-periods / event-reservations API. This
14
+ skill builds the `libs/museum-events` library and registers a single top-level `/wydarzenia` route.
15
+
16
+ Events has a leaner shape than the other feature modules: a pure `RouterModule.forChild` module with
17
+ **lazy `loadComponent` routes** and **no shared-module `forFeature`**, and it ships **only a
18
+ `shell/angular` project** (there is no `domain` library). All of its UI and data access live inside
19
+ its own `shell/angular`.
20
+
21
+ ## Precondition
22
+
23
+ Run `scripts/detect-init.sh <workspace-root>`. It confirms the MobilEMS foundation is present
24
+ (`SharedModule.forRoot` in `app.config.ts`, a populated shared-lib barrel). If not, tell the user to
25
+ run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`; the reference
26
+ MobilEMS app uses `@msr`) and the reference repo path (same one init used).
27
+
28
+ ## Steps
29
+
30
+ 1. **npm deps (precondition check)** — events imports only foundation packages:
31
+ `@smartsoft001-mobilems/angular`, `@smartsoft001-mobilems/models`, `@smartsoft001/angular`,
32
+ `@thisissoon/angular-masonry`, and `@ngx-translate/core` — all already installed by
33
+ `mobilems-project-init`. So this step is a CHECK: confirm those lines are in `package.json`; there
34
+ is nothing new to add. As a safeguard, grep the copied lib for any third-party import not in that
35
+ foundation set and add only what is genuinely missing. Do not `npm install` unless asked.
36
+ 2. **Library** — create `libs/museum-events/shell/angular` per `references/lib-contents.md`: copy the
37
+ shell (`MuseumEventsModule`, `events.routes.ts`, `pages/{list,item,booking,confirm,cancel}`,
38
+ `services/events-reservation.service.ts`, `models/events.models.ts`, `pipes/time-format.pipe.ts`,
39
+ and the `src/__mocks__/@smartsoft001-mobilems/{angular,models}.ts` jest mocks), thread `<PREFIX>`.
40
+ Add the single `tsconfig.base.json` path `@<PREFIX>/museum-events/shell/angular` and the
41
+ `museum-events` commit scope.
42
+ 3. **Module + routes** — see `references/module-and-routes.md`: `MuseumEventsModule` is just
43
+ `@NgModule({ imports: [RouterModule.forChild(eventsRoutes)] })`; `eventsRoutes` uses lazy
44
+ `loadComponent` for every page and provides the `Masonry` SSR factory on the list route. Add the
45
+ lazy `wydarzenia` route to the `appRoutes` array in `apps/web/src/app/app.routes.ts` as a single
46
+ top-level route.
47
+ 4. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the
48
+ `libs/museum-events/shell/angular` lib exists with `MuseumEventsModule` (`RouterModule.forChild`)
49
+ and `events.routes.ts`, the `@<PREFIX>/museum-events/shell/angular` tsconfig path is added, the
50
+ five pages + `EventsReservationService` + `events.models.ts` + `TimeFormatPipe` are present, the
51
+ `wydarzenia` route is registered as a single top-level route, and the foundation deps it reuses are
52
+ in `package.json`. Fix and re-run.
53
+
54
+ ## Notes
55
+
56
+ - Events is a standalone booking module: it owns its data access (`EventsReservationService` calling
57
+ the events API) and contributes **nothing** to `libs/shared/angular`. The shared lib stays
58
+ untouched.
59
+ - Events ships **only** a `shell/angular` project — there is no `domain` library and no shared-module
60
+ `forFeature`; its event/reservation types live in `shell/angular/src/lib/models/events.models.ts`.
61
+ - `/wydarzenia` is a top-level route only. Events is a booking destination, not a search result, so it
62
+ is NOT added under the `wyszukaj/:text` search children.
63
+ - This skill ONLY touches the events module + its route. It never adds other feature modules, never
64
+ re-runs the foundation, and never edits `libs/shared/angular`.
65
+ - Bundled `references/` + `scripts/` ship automatically. Do not hand-edit `plugin.json`.