@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
@@ -0,0 +1,116 @@
1
+ # MuseumCollectionsModule + route registration
2
+
3
+ ## `libs/museum-collections/shell/angular/src/lib/collections.module.ts`
4
+
5
+ Thread `<PREFIX>` (the `environment` + domain imports). `PublicCollectionsSharedModule.forFeature`
6
+ provides the list/detail behaviour over `MuseumCollection`; `CollectionsService` is provided as the
7
+ `CrudService`; the `Masonry` factory returns the real library in the browser and a no-op mock during
8
+ SSR. Note: unlike the objects module this one does NOT import `SmartSharedModule`.
9
+
10
+ ```ts
11
+ import { NgModule } from '@angular/core';
12
+ import { FormsModule } from '@angular/forms';
13
+ import { RouterModule } from '@angular/router';
14
+ import { CrudService } from '@smartsoft001/crud-shell-angular';
15
+ import { PublicCollectionsSharedModule } from '@smartsoft001-mobilems/public-collections-shell-angular';
16
+ import { Masonry } from '@thisissoon/angular-masonry';
17
+
18
+ import { environment } from '@<PREFIX>/angular';
19
+ import { MuseumCollection } from '@<PREFIX>/museum-collections/domain';
20
+
21
+ import { routes } from './collections.routes';
22
+ import { CollectionsService } from './services';
23
+
24
+ @NgModule({
25
+ providers: [
26
+ CollectionsService,
27
+ {
28
+ provide: CrudService,
29
+ useClass: CollectionsService,
30
+ },
31
+ {
32
+ provide: Masonry,
33
+ useFactory: () => {
34
+ if (typeof window !== 'undefined' && (window as any)['Masonry']) {
35
+ return (window as any)['Masonry'];
36
+ }
37
+ // Return a mock for SSR
38
+ return class {
39
+ constructor() {
40
+ // no-op
41
+ }
42
+ layout() {
43
+ return;
44
+ }
45
+ reloadItems() {
46
+ return;
47
+ }
48
+ destroy() {
49
+ return;
50
+ }
51
+ };
52
+ },
53
+ },
54
+ ],
55
+ imports: [
56
+ FormsModule,
57
+ PublicCollectionsSharedModule.forFeature({
58
+ routing: false,
59
+ config: {
60
+ type: MuseumCollection,
61
+ apiUrl: environment.apiUrl,
62
+ entity: 'publicCollections',
63
+ },
64
+ }),
65
+ RouterModule.forChild(routes),
66
+ ],
67
+ })
68
+ export class MuseumCollectionsModule {}
69
+ ```
70
+
71
+ ## `collections.routes.ts`
72
+
73
+ Identical shape to the objects routes (`:id/:slug` → Item, `:id` → Item, `''` → List).
74
+
75
+ ```ts
76
+ import { Route } from '@angular/router';
77
+
78
+ import { ItemComponent, ListComponent } from './pages';
79
+
80
+ export const routes: Route[] = [
81
+ { path: ':id/:slug', component: ItemComponent },
82
+ { path: ':id', component: ItemComponent },
83
+ { path: '', component: ListComponent },
84
+ ];
85
+ ```
86
+
87
+ ## Register in `apps/web/src/app/app.routes.ts`
88
+
89
+ Add the collections route to the `appRoutes` array (idempotently — if a `kolekcje` route already
90
+ exists, skip). Add it as a top-level route, and as a child of the `wyszukaj/:text` search route so
91
+ `/wyszukaj/:text/kolekcje` works.
92
+
93
+ Top-level — add to `appRoutes`:
94
+
95
+ ```ts
96
+ {
97
+ path: 'kolekcje',
98
+ loadChildren: () =>
99
+ import('@<PREFIX>/museum-collections/shell/angular').then(
100
+ (m) => m.MuseumCollectionsModule,
101
+ ),
102
+ },
103
+ ```
104
+
105
+ Search child — add to the `children` of the `wyszukaj/:text` route, before the
106
+ `{ path: '', redirectTo: 'obiekty' }` default:
107
+
108
+ ```ts
109
+ {
110
+ path: 'kolekcje',
111
+ loadChildren: () =>
112
+ import('@<PREFIX>/museum-collections/shell/angular').then(
113
+ (m) => m.MuseumCollectionsModule,
114
+ ),
115
+ },
116
+ ```
@@ -0,0 +1,61 @@
1
+ # Collections shared components
2
+
3
+ The collections module contributes one shared component to `libs/shared/angular` — the part of the
4
+ shared UI that renders a museum collection and therefore depends on
5
+ `@smartsoft001-mobilems/public-collections-domain`. Build it by copying from the reference repo's
6
+ `libs/shared/angular/src/lib` and threading `<PREFIX>`. Every step is idempotent — if the target
7
+ already exists, leave it.
8
+
9
+ All paths below are under `libs/shared/angular/src/lib`.
10
+
11
+ ## 1. Collection card component
12
+
13
+ Build this component directory and export it from `components/index.ts`:
14
+
15
+ | Component | Renders |
16
+ | ---------------------------------- | ---------------------------------- |
17
+ | `components/collections-grid-item` | a collection card in the grid view |
18
+
19
+ ```ts
20
+ // components/index.ts — this export belongs to the collections module:
21
+ export * from './collections-grid-item';
22
+ ```
23
+
24
+ `collections-grid-item` carries its own `index.ts`
25
+ (`export * from './collections-grid-item.component';`) plus the `.component.ts` + `.component.html`.
26
+ Build the whole dir from the reference and thread `<PREFIX>`.
27
+
28
+ The build-safety grep over the reference shared lib confirms which components depend on the
29
+ public-collections feature package — it should list only `collections-grid-item`:
30
+
31
+ ```bash
32
+ grep -rlnE "from '@[^']*/(public-collections)-(domain|shell-angular|shell-dtos)'" \
33
+ <reference-repo>/libs/shared/angular/src
34
+ ```
35
+
36
+ ## 2. No widget, no model type
37
+
38
+ Unlike `mobilems-objects`, collections contributes **nothing** to the widget container
39
+ (`components/widget/widget.component.ts`) and **no** model type to `models/`. There is no
40
+ `GALLERY_COLLECTION` widget type. Do not touch the widget container or the models dir.
41
+
42
+ ## 3. Cross-module dependency — the collection detail page renders related objects + articles
43
+
44
+ The collection **item/detail page** (`libs/museum-collections/shell/angular/.../pages/item`) shows
45
+ related objects and articles, so it imports the object and article card shared components
46
+ (`ObjectsGridItemComponent`/`ObjectsListItemComponent` and `ArticlesGridItemComponent`/
47
+ `ArticlesListItemComponent`) from `@<PREFIX>/angular`, and uses `MsObject` (objects-domain) /
48
+ `Article` (articles-domain). Those cards are built by `mobilems-objects` / `mobilems-articles`, so the
49
+ collections module is **not** fully standalone — its detail page assumes those modules' card
50
+ components exist.
51
+
52
+ When you add collections, check which of those cards the copied item page imports and confirm they're
53
+ exported from `components/index.ts`. If a needed card isn't there yet (its module hasn't been added),
54
+ either add that module first, build the missing card component here (it's just an Article/MsObject
55
+ card — copy it from the reference like `collections-grid-item`), or remove the related-content section
56
+ from the item page. Don't leave a dangling import — it will not compile.
57
+
58
+ ## Verify
59
+
60
+ `scripts/verify-module.sh` checks that the `collections-grid-item` component exists and is exported
61
+ from `components/index.ts`.
@@ -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,44 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # verify-module.sh <workspace-root> <PREFIX>
4
+ #
5
+ # Post-condition check for mobilems-object-collections: library created, route registered (top-level
6
+ # + search child), the collections shared component built + exported, public-collections deps present.
7
+ # macOS bash 3.2 safe.
8
+ set -eu
9
+
10
+ ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
11
+ PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
12
+ cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
13
+
14
+ fail=0
15
+ pass() { echo " PASS: $1"; }
16
+ bad() { echo " FAIL: $1" >&2; fail=1; }
17
+
18
+ echo "== Library =="
19
+ [ -f libs/museum-collections/shell/angular/src/lib/collections.module.ts ] && pass "MuseumCollectionsModule present" || bad "collections.module.ts missing"
20
+ [ -f libs/museum-collections/domain/src/lib/entities/museum-collection.entity.ts ] && pass "MuseumCollection entity present" || bad "domain entity missing"
21
+ grep -q "MuseumCollection" libs/museum-collections/domain/src/lib/entities/museum-collection.entity.ts 2>/dev/null && pass "MuseumCollection class declared" || bad "MuseumCollection class missing"
22
+ grep -q "museum-collections/shell/angular" tsconfig.base.json && pass "tsconfig path added" || bad "tsconfig.base.json path missing"
23
+ grep -q "PublicCollectionsSharedModule.forFeature" libs/museum-collections/shell/angular/src/lib/collections.module.ts 2>/dev/null && pass "forFeature wired" || bad "PublicCollectionsSharedModule.forFeature missing"
24
+
25
+ echo "== Route registration =="
26
+ ROUTES=apps/web/src/app/app.routes.ts
27
+ grep -q "museum-collections/shell/angular" "$ROUTES" && pass "collections route registered" || bad "collections route not in app.routes.ts"
28
+ # top-level + search child — expect at least 2 occurrences
29
+ n=$(grep -c "museum-collections/shell/angular" "$ROUTES" || true)
30
+ [ "${n:-0}" -ge 2 ] && pass "registered top-level + search child ($n)" || bad "expected route top-level + search child, found $n"
31
+
32
+ echo "== Collections shared components =="
33
+ COMP=libs/shared/angular/src/lib/components
34
+ [ -d "$COMP/collections-grid-item" ] && pass "built collections-grid-item" || bad "missing $COMP/collections-grid-item"
35
+ grep -q "collections-grid-item" "$COMP/index.ts" && pass "collections-grid-item exported" || bad "collections-grid-item export missing"
36
+
37
+ echo "== Deps =="
38
+ grep -q "@smartsoft001-mobilems/public-collections-domain" package.json && pass "public-collections-domain dep" || bad "public-collections-domain dep missing"
39
+ grep -q "@smartsoft001-mobilems/public-collections-shell-angular" package.json && pass "public-collections-shell-angular dep" || bad "public-collections-shell-angular dep missing"
40
+ grep -q "@smartsoft001-mobilems/public-collections-shell-dtos" package.json && pass "public-collections-shell-dtos dep" || bad "public-collections-shell-dtos dep missing"
41
+
42
+ echo
43
+ if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
44
+ exit "$fail"
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: mobilems-objects
3
+ description: Add the museum OBJECTS feature module to a MobilEMS app initialized with mobilems-project-init. Use whenever the user wants to add / scaffold / wire the objects (obiekty) module, museum object list + detail pages, object tabs (images, map, audio, 3D, animation, virtual walk), or asks to "dodaj moduł obiektów / eksponatów". Creates libs/museum-objects (domain + shell/angular), registers the /obiekty route, builds the objects shared components, and adds the objects 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 objects module
9
+
10
+ Add the **objects** (obiekty) feature module to a MobilEMS app whose foundation was built by
11
+ `mobilems-project-init`. This skill builds the `libs/museum-objects` library, the objects shared
12
+ components, the objects route, and adds the objects npm packages.
13
+
14
+ Objects is the richest module — a list/detail view with a tabbed detail (images deep-zoom, Leaflet
15
+ map, audio, 3D model, animation, virtual walk) backed by `ObjectsSharedModule.forFeature` over the
16
+ `MsObject` entity.
17
+
18
+ ## Precondition
19
+
20
+ Run `scripts/detect-init.sh <workspace-root>`. It confirms the MobilEMS foundation is present
21
+ (`SharedModule.forRoot` in `app.config.ts`, a populated shared-lib barrel). If not, tell the user to
22
+ run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`; reference uses
23
+ `@msr`) and the reference repo path (same one init used).
24
+
25
+ ## Steps
26
+
27
+ 1. **npm deps** — add `@smartsoft001-mobilems/objects-domain`, `@smartsoft001-mobilems/objects-shell-angular`,
28
+ `@smartsoft001-mobilems/objects-shell-dtos` (match the installed mobilems line) and
29
+ `online-3d-viewer` (the 3D tab; `leaflet`/`openseadragon`/`swiper` are already foundation deps).
30
+ Resolve versions at run time; do not `npm install` unless asked.
31
+ 2. **Library** — create `libs/museum-objects/{domain,shell/angular}` per `references/lib-contents.md`:
32
+ copy the domain entity/enums and the shell (`MuseumObjectsModule`, routes, `components/*`,
33
+ `pages/{list,item}`, `services`), thread `<PREFIX>`. Add the two `tsconfig.base.json` paths and the
34
+ `museum-objects` commit scope.
35
+ 3. **Module + routes** — see `references/module-and-routes.md`: `MuseumObjectsModule` wraps
36
+ `ObjectsSharedModule.forFeature({ routing: false, config: { type: MsObject, apiUrl, entity: 'objects' } })`
37
+ with the `Masonry` SSR factory. Add the lazy `obiekty` route to `appRoutes` in
38
+ `apps/web/src/app/app.routes.ts` and a matching child under the `wyszukaj/:text` search route.
39
+ 4. **Objects shared components** — see `references/shared-components.md`: build `objects-grid-item`,
40
+ `objects-list-item`, and the object gallery widget (`widget/gallery-object`) in the shared lib +
41
+ export them; register the object gallery widget in the widget content-type map (`GALLERY_OBJECT`);
42
+ and add `ISpecificObject` (extends `MsObject`) to `models/objects.model.ts`.
43
+ 5. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib exists, the
44
+ `obiekty` route is registered (top-level + search child), the objects shared components are built
45
+ and exported, the widget map registers the object gallery widget, and `ISpecificObject` is present.
46
+ 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-960/991,
51
+ Tomaszów MOB-949/993). The cross-cutting rules live in `mobilems-project-init`
52
+ `references/qa-hardening.md`; these are the objects-specific ones:
53
+
54
+ - **Search debounce.** The panel full-text search (`app-objects-searchbar variant="panel"`) must
55
+ re-emit through a handler with `@debounce(250)` (`lodash-decorators`) — without it every keystroke
56
+ hits the API. Collections and news filters already do this; keep the modules consistent.
57
+ - **List page:** show the total result count (`facade.totalCount()`) and add the shared
58
+ `NextPageComponent` at the bottom (same `selectedPage`/`lastPageIndex` pattern as the news list).
59
+ - **Map tab coordinates.** Read coordinates through ONE helper (`getObjectCoordinates()`) used by
60
+ both the tab-visibility predicate and the map render: `archeoMap.latitude/longitude` first, then
61
+ fall back to the first valid `createPlaces[]` entry; accept **both `lon` and `long`** (upstream
62
+ `MsObjectPlace` says `long`, the real API sends `lon`); validate with `Number.isFinite` (lat `0`
63
+ is valid; `Number('') === 0` — reject empty strings explicitly). Duplicating the predicate in two
64
+ components caused a "map never renders" bug.
65
+ - **Field labels match the client's field spec everywhere** — list card, detail and filters must use
66
+ the same names (QA rejected "Materiał" vs "Tworzywo", "Miejsca:" vs "Miejsce powstania").
67
+ - **Keywords section** maps the API `tags[]` field (not `keywords[]`) and renders clickable links
68
+ that filter the list.
69
+ - **Warning-clean templates:** after copying, remove needless `?.` on non-nullable signals (NG8107)
70
+ and drop unused entries from `imports: []` (NG8113 — the tab container often imports
71
+ Model3d/Map/VirtualWalk tabs its template no longer references). Masonry grid items must not set
72
+ `width` after change detection (NG0100).
73
+ - **A11y:** exactly one `h1` (detail = object title); cards exposed as `role="link"` must activate
74
+ on Enter/Space; focused/hovered cards change label colours together with the rest of the content.
75
+
76
+ ## Notes
77
+
78
+ - This skill builds only the objects module + its shared components + its route. It never adds other
79
+ feature modules and never re-runs the foundation.
80
+ - Bundled `references/` + `scripts/` ship automatically. Do not hand-edit `plugin.json`.
@@ -0,0 +1,68 @@
1
+ # libs/museum-objects — structure to create
2
+
3
+ Copy from the reference repo's `libs/museum-objects` and thread `<PREFIX>`. The lib has two
4
+ projects: a `domain` (entities/enums) and a `shell/angular` (the Angular module + UI).
5
+
6
+ ```
7
+ libs/museum-objects/
8
+ ├── domain/ # project: museum-objects-domain
9
+ │ └── src/
10
+ │ ├── index.ts # export * from './lib/entities'; export * from './lib/enums';
11
+ │ └── lib/
12
+ │ ├── entities/ # MuseumObject extends MsObject (+ index)
13
+ │ ├── enums/ # connection-type.enum (+ index)
14
+ │ ├── specifications/
15
+ │ └── value-objects/
16
+ └── shell/angular/ # project: museum-objects-shell-angular
17
+ └── src/
18
+ ├── index.ts # export * from './pages'; export * from './objects.module';
19
+ ├── __mocks__/ # jest mocks for the @smartsoft001-mobilems/* feature pkgs
20
+ └── lib/
21
+ ├── objects.module.ts # MuseumObjectsModule (references/module-and-routes.md)
22
+ ├── objects.routes.ts
23
+ ├── components/ # description, filter-input, high-def-request-modal,
24
+ │ # menu, objects-searchbar, tabs/*
25
+ ├── pages/ # list (ListComponent), item (ItemComponent) + index
26
+ └── services/ # objects.service.ts + index
27
+ ```
28
+
29
+ ## Domain entity (verbatim, thread nothing — pure types)
30
+
31
+ ```ts
32
+ import { Field, Model } from '@smartsoft001/models';
33
+ import { MsObject } from '@smartsoft001-mobilems/objects-domain';
34
+
35
+ @Model({})
36
+ export class MuseumObject extends MsObject {
37
+ historicalProvenance?: any;
38
+ seriesTitles?: any;
39
+ imageDesktop?: any;
40
+ virtualExhibitionSlides?: any;
41
+ shareMuseumObjectMultimediaEnabled!: boolean;
42
+ }
43
+ ```
44
+
45
+ ## Wiring
46
+
47
+ - **tsconfig.base.json paths** (add both):
48
+ ```json
49
+ "@<PREFIX>/museum-objects/domain": ["libs/museum-objects/domain/src/index.ts"],
50
+ "@<PREFIX>/museum-objects/shell/angular": ["libs/museum-objects/shell/angular/src/index.ts"]
51
+ ```
52
+ - **project.json / build config** — copy each project's build config and normalize every path field
53
+ that the reference baked to its origin location — `sourceRoot`, `dest`, `outputPath`,
54
+ `coverageDirectory`, `jestConfig`, `lintFilePatterns`, and the jest `displayName` — to `libs/museum-objects/...`. Some
55
+ reference modules carry stale values here (e.g. a shell `sourceRoot` missing the `shell/angular`
56
+ segment, or a `coverageDirectory` under `packages/...`) while others are already correct — verify
57
+ each field against a known-good sibling lib and fix only the ones that are actually wrong. The
58
+ `domain` project may ship no `project.json` at all (Nx infers its targets from `package.json` +
59
+ `jest.config`) — that's fine, leave it inferred; just set its `package.json` `name` to
60
+ `museum-objects-domain`.
61
+ - **commit scope** — `commitlint.config.js` reads `libs/` dir names, so `museum-objects` becomes a
62
+ valid scope automatically; no edit needed.
63
+ - **jest `__mocks__`** — keep the `src/__mocks__/@smartsoft001-mobilems/*` stubs from the reference;
64
+ they let the shell unit-test without the real feature packages.
65
+
66
+ The tabs (`tabs/*`) include the 3D model tab, which uses `online-3d-viewer` — that's why this skill
67
+ adds that dep. The map tab uses `leaflet`, images use `openseadragon`/`swiper` — already foundation
68
+ deps.
@@ -0,0 +1,108 @@
1
+ # MuseumObjectsModule + route registration
2
+
3
+ ## `libs/museum-objects/shell/angular/src/lib/objects.module.ts`
4
+
5
+ Thread `<PREFIX>` (the `environment` import). `ObjectsSharedModule.forFeature` provides the list/detail
6
+ behaviour over `MsObject`; the `Masonry` factory returns the real library in the browser and a no-op
7
+ mock during SSR.
8
+
9
+ ```ts
10
+ import { NgModule } from '@angular/core';
11
+ import { FormsModule } from '@angular/forms';
12
+ import { RouterModule } from '@angular/router';
13
+ import { SharedModule as SmartSharedModule } from '@smartsoft001/angular';
14
+ import { MsObject } from '@smartsoft001-mobilems/objects-domain';
15
+ import { ObjectsSharedModule } from '@smartsoft001-mobilems/objects-shell-angular';
16
+ import { Masonry } from '@thisissoon/angular-masonry';
17
+
18
+ import { environment } from '@<PREFIX>/angular';
19
+
20
+ import { routes } from './objects.routes';
21
+
22
+ @NgModule({
23
+ providers: [
24
+ {
25
+ provide: Masonry,
26
+ useFactory: () => {
27
+ if (typeof window !== 'undefined' && (window as any)['Masonry']) {
28
+ return (window as any)['Masonry'];
29
+ }
30
+ // Return a mock for SSR
31
+ return class {
32
+ constructor() {
33
+ // no-op
34
+ }
35
+ layout() {
36
+ return;
37
+ }
38
+ reloadItems() {
39
+ return;
40
+ }
41
+ destroy() {
42
+ return;
43
+ }
44
+ };
45
+ },
46
+ },
47
+ ],
48
+ imports: [
49
+ FormsModule,
50
+ SmartSharedModule,
51
+ ObjectsSharedModule.forFeature({
52
+ routing: false,
53
+ config: {
54
+ type: MsObject,
55
+ apiUrl: environment.apiUrl,
56
+ entity: 'objects',
57
+ },
58
+ }),
59
+ RouterModule.forChild(routes),
60
+ ],
61
+ })
62
+ export class MuseumObjectsModule {}
63
+ ```
64
+
65
+ ## `objects.routes.ts`
66
+
67
+ ```ts
68
+ import { Route } from '@angular/router';
69
+
70
+ import { ItemComponent, ListComponent } from './pages';
71
+
72
+ export const routes: Route[] = [
73
+ { path: ':id/:slug', component: ItemComponent },
74
+ { path: ':id', component: ItemComponent },
75
+ { path: '', component: ListComponent },
76
+ ];
77
+ ```
78
+
79
+ ## Register in `apps/web/src/app/app.routes.ts`
80
+
81
+ Add the objects route to the `appRoutes` array (idempotently — if an `obiekty` route already exists,
82
+ skip). Add it as a top-level route, and as a child of the `wyszukaj/:text` search route so
83
+ `/wyszukaj/:text/obiekty` works and the search view's `redirectTo: 'obiekty'` default resolves.
84
+
85
+ Top-level — add to `appRoutes`:
86
+
87
+ ```ts
88
+ {
89
+ path: 'obiekty',
90
+ loadChildren: () =>
91
+ import('@<PREFIX>/museum-objects/shell/angular').then(
92
+ (m) => m.MuseumObjectsModule,
93
+ ),
94
+ },
95
+ ```
96
+
97
+ Search child — add to the `children` of the `wyszukaj/:text` route, before the
98
+ `{ path: '', redirectTo: 'obiekty' }` default:
99
+
100
+ ```ts
101
+ {
102
+ path: 'obiekty',
103
+ loadChildren: () =>
104
+ import('@<PREFIX>/museum-objects/shell/angular').then(
105
+ (m) => m.MuseumObjectsModule,
106
+ ),
107
+ },
108
+ ```
@@ -0,0 +1,69 @@
1
+ # Objects shared components
2
+
3
+ The objects module contributes a few shared components to `libs/shared/angular` — the parts of the
4
+ shared UI that render a museum object and therefore depend on `@smartsoft001-mobilems/objects-domain`.
5
+ Build them by copying from the reference repo's `libs/shared/angular/src/lib` and threading
6
+ `<PREFIX>`. Every step is idempotent — if the target already exists, leave it.
7
+
8
+ All paths below are under `libs/shared/angular/src/lib`.
9
+
10
+ ## 1. Object card components
11
+
12
+ Build these component directories and export them from `components/index.ts`:
13
+
14
+ | Component | Renders |
15
+ | ------------------------------ | ------------------------------- |
16
+ | `components/objects-grid-item` | an object card in the grid view |
17
+ | `components/objects-list-item` | an object row in the list view |
18
+
19
+ ```ts
20
+ // components/index.ts — these exports belong to the objects module:
21
+ export * from './objects-grid-item';
22
+ export * from './objects-list-item';
23
+ ```
24
+
25
+ ## 2. Object gallery widget
26
+
27
+ Build `components/widget/gallery-object` and export it from the widget barrel:
28
+
29
+ ```ts
30
+ // components/widget/index.ts:
31
+ export * from './gallery-object/widget-gallery-object.component';
32
+ ```
33
+
34
+ Register it in the widget content-type map in `components/widget/widget.component.ts` so the page
35
+ renderer can render `GALLERY_OBJECT` content blocks:
36
+
37
+ ```ts
38
+ import { WidgetGalleryObjectComponent } from './gallery-object/widget-gallery-object.component';
39
+ // …in the ArticleWidgetType → component map:
40
+ [ArticleWidgetType.GALLERY_OBJECT]: WidgetGalleryObjectComponent,
41
+ ```
42
+
43
+ The widget container renders sub-widgets via `NgComponentOutlet`, so the gallery widget goes in the
44
+ type-map only — it does **not** go in the container's `imports` array.
45
+
46
+ ## 3. Object model type
47
+
48
+ `models/objects.model.ts` holds the foundation model types plus the object-specific `ISpecificObject`,
49
+ which extends `MsObject`. Add the import and the **full** interface — copy `ISpecificObject` verbatim
50
+ from the reference's `models/objects.model.ts` (it has a real field set; do not leave it as an empty
51
+ stub, or the objects `description-container` component won't compile):
52
+
53
+ ```ts
54
+ import { MsObject } from '@smartsoft001-mobilems/objects-domain';
55
+ // …
56
+ export interface ISpecificObject extends MsObject {
57
+ // …copy the full field set from the reference objects.model.ts…
58
+ }
59
+ ```
60
+
61
+ `ISpecificObject` is consumed by the objects `description-container` component, so it must be present
62
+ (with its real fields) before that compiles. Note `verify-module.sh` only checks the interface
63
+ exists — it can't tell a stub from the real thing, so copy the fields.
64
+
65
+ ## Verify
66
+
67
+ `scripts/verify-module.sh` checks the two card components + the gallery widget exist and are exported,
68
+ the widget map registers `WidgetGalleryObjectComponent`, and `ISpecificObject` is present in
69
+ `objects.model.ts`.
@@ -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,51 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # verify-module.sh <workspace-root> <PREFIX>
4
+ #
5
+ # Post-condition check for mobilems-objects: library created, route registered (top-level + search
6
+ # child), objects shared components built + wired into the widget map + objects.model. bash 3.2 safe.
7
+ set -eu
8
+
9
+ ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
10
+ PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
11
+ cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
12
+
13
+ fail=0
14
+ pass() { echo " PASS: $1"; }
15
+ bad() { echo " FAIL: $1" >&2; fail=1; }
16
+
17
+ echo "== Library =="
18
+ [ -f libs/museum-objects/shell/angular/src/lib/objects.module.ts ] && pass "MuseumObjectsModule present" || bad "objects.module.ts missing"
19
+ [ -f libs/museum-objects/domain/src/lib/entities/museum-object.entity.ts ] && pass "MuseumObject entity present" || bad "domain entity missing"
20
+ grep -q "museum-objects/shell/angular" tsconfig.base.json && pass "tsconfig path added" || bad "tsconfig.base.json path missing"
21
+ grep -q "ObjectsSharedModule.forFeature" libs/museum-objects/shell/angular/src/lib/objects.module.ts 2>/dev/null && pass "forFeature wired" || bad "ObjectsSharedModule.forFeature missing"
22
+
23
+ echo "== Route registration =="
24
+ ROUTES=apps/web/src/app/app.routes.ts
25
+ grep -q "museum-objects/shell/angular" "$ROUTES" && pass "objects route registered" || bad "objects route not in app.routes.ts"
26
+ # top-level + search child — expect at least 2 occurrences
27
+ n=$(grep -c "museum-objects/shell/angular" "$ROUTES" || true)
28
+ [ "${n:-0}" -ge 2 ] && pass "registered top-level + search child ($n)" || bad "expected route top-level + search child, found $n"
29
+
30
+ echo "== Objects shared components =="
31
+ COMP=libs/shared/angular/src/lib/components
32
+ for d in objects-grid-item objects-list-item widget/gallery-object; do
33
+ [ -d "$COMP/$d" ] && pass "built $d" || bad "missing $COMP/$d"
34
+ done
35
+ grep -q "objects-grid-item" "$COMP/index.ts" && pass "objects-grid-item exported" || bad "objects-grid-item export missing"
36
+ grep -q "gallery-object" "$COMP/widget/index.ts" && pass "gallery-object exported" || bad "gallery-object export missing"
37
+
38
+ echo "== Widget map + object model =="
39
+ WID="$COMP/widget/widget.component.ts"
40
+ grep -q "WidgetGalleryObjectComponent" "$WID" && pass "object gallery widget registered in widget map" || bad "object gallery widget not in widget map"
41
+ OBJM=libs/shared/angular/src/lib/models/objects.model.ts
42
+ grep -q "objects-domain" "$OBJM" && pass "objects.model imports MsObject" || bad "objects.model MsObject import missing"
43
+ grep -qE "interface ISpecificObject" "$OBJM" && pass "ISpecificObject present" || bad "ISpecificObject missing"
44
+
45
+ echo "== Deps =="
46
+ grep -q "@smartsoft001-mobilems/objects-domain" package.json && pass "objects-domain dep" || bad "objects-domain dep missing"
47
+ grep -q "online-3d-viewer" package.json && pass "online-3d-viewer dep" || bad "online-3d-viewer dep missing"
48
+
49
+ echo
50
+ if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
51
+ exit "$fail"