@smartsoft001-mobilems/claude-plugins 2.116.0 → 2.118.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/references/module-and-routes.md +4 -22
- package/plugins/mobilems/skills/mobilems-articles/scripts/verify-module.sh +22 -1
- package/plugins/mobilems/skills/mobilems-devops-config/references/workflows.md +13 -0
- package/plugins/mobilems/skills/mobilems-devops-config/scripts/verify-devops.sh +10 -0
- package/plugins/mobilems/skills/mobilems-events/references/module-and-routes.md +4 -24
- package/plugins/mobilems/skills/mobilems-events/scripts/verify-module.sh +22 -1
- package/plugins/mobilems/skills/mobilems-object-collections/references/module-and-routes.md +4 -26
- package/plugins/mobilems/skills/mobilems-object-collections/scripts/verify-module.sh +22 -1
- package/plugins/mobilems/skills/mobilems-objects/SKILL.md +1 -1
- package/plugins/mobilems/skills/mobilems-objects/references/module-and-routes.md +4 -28
- package/plugins/mobilems/skills/mobilems-objects/references/shared-components.md +2 -1
- package/plugins/mobilems/skills/mobilems-objects/scripts/verify-module.sh +22 -1
- package/plugins/mobilems/skills/mobilems-partners/references/module-and-routes.md +4 -29
- package/plugins/mobilems/skills/mobilems-partners/scripts/verify-module.sh +22 -1
- package/plugins/mobilems/skills/mobilems-project-init/references/app-shell.md +7 -2
- package/plugins/mobilems/skills/mobilems-project-init/references/config-providers.md +14 -3
- package/plugins/mobilems/skills/mobilems-project-init/references/qa-hardening.md +39 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/shared-lib.md +27 -10
- package/plugins/mobilems/skills/mobilems-project-init/references/tailwind-styles.md +9 -4
- package/plugins/mobilems/skills/mobilems-project-init/scripts/verify-init.sh +12 -0
- package/plugins/mobilems/skills/mobilems-search/scripts/verify-search.sh +12 -0
- package/plugins/mobilems/skills/mobilems-static-pages/scripts/verify-pages.sh +12 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/references/module-and-routes.md +4 -30
- package/plugins/mobilems/skills/mobilems-thematic-publications/scripts/verify-module.sh +22 -1
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
An article module wraps `ArticlesSharedModule.forFeature` over the npm `Article` entity and filters
|
|
4
4
|
its API requests with a `template` value. Thread `<PREFIX>` (the `environment` import) and the chosen
|
|
5
|
-
`<name>` / `<entity>` / `<template>`.
|
|
6
|
-
and
|
|
5
|
+
`<name>` / `<entity>` / `<template>`. `provideBrowserMasonry()` supplies the real library in the browser (`window.Masonry`, set by `bootstrapApplication` from `@smartsoft001-mobilems/angular/bootstrap` in `main.ts`)
|
|
6
|
+
and `null` during SSR (the grid skips layout). Copy the file structure from an existing article module in the reference
|
|
7
7
|
MobilEMS app and substitute the parameters.
|
|
8
8
|
|
|
9
9
|
## `libs/museum-<name>/shell/angular/src/lib/<name>.module.ts`
|
|
@@ -15,32 +15,14 @@ import { RouterModule } from '@angular/router';
|
|
|
15
15
|
import { SharedModule as SmartSharedModule } from '@smartsoft001/angular';
|
|
16
16
|
import { Article } from '@smartsoft001-mobilems/articles-domain';
|
|
17
17
|
import { ArticlesSharedModule } from '@smartsoft001-mobilems/articles-shell-angular';
|
|
18
|
-
import {
|
|
18
|
+
import { provideBrowserMasonry } from '@smartsoft001-mobilems/angular';
|
|
19
19
|
|
|
20
20
|
import { environment } from '@<PREFIX>/angular';
|
|
21
21
|
|
|
22
22
|
import { routes } from './<name>.routes';
|
|
23
23
|
|
|
24
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
|
-
],
|
|
25
|
+
providers: [provideBrowserMasonry()],
|
|
44
26
|
imports: [
|
|
45
27
|
FormsModule,
|
|
46
28
|
SmartSharedModule,
|
|
@@ -70,13 +70,22 @@ fi
|
|
|
70
70
|
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
71
71
|
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
72
72
|
# which the `new Masonry(` grep above cannot see.
|
|
73
|
-
if grep -
|
|
73
|
+
if grep -rqE "provide: Masonry|provideBrowserMasonry" "$LIBDIR" 2>/dev/null; then
|
|
74
74
|
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
75
75
|
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
76
76
|
else
|
|
77
77
|
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
78
78
|
fi
|
|
79
79
|
fi
|
|
80
|
+
# 2b. the provider is provideBrowserMasonry() — an anonymous stand-in class next to
|
|
81
|
+
# `provide: Masonry` reaches for window during SSR and is the bug the framework removed
|
|
82
|
+
STUB="$(grep -rlE "provide: Masonry" "$LIBDIR" 2>/dev/null | xargs grep -lE "return class|: class \{" 2>/dev/null || true)"
|
|
83
|
+
if [ -n "$STUB" ]; then
|
|
84
|
+
bad "hand-written Masonry stub (return class / : class {) — use provideBrowserMasonry() from @smartsoft001-mobilems/angular:"
|
|
85
|
+
printf ' %s\n' $STUB >&2
|
|
86
|
+
else
|
|
87
|
+
pass "no hand-written Masonry stub"
|
|
88
|
+
fi
|
|
80
89
|
# 3. a list page must render through the framework grid
|
|
81
90
|
LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
|
|
82
91
|
if [ -n "$LISTP" ]; then
|
|
@@ -118,6 +127,18 @@ else
|
|
|
118
127
|
bad "translation gaps — fix the FAIL lines above"
|
|
119
128
|
fi
|
|
120
129
|
|
|
130
|
+
echo "== Portal guard =="
|
|
131
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
132
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
133
|
+
if npx --no-install mobilems-check-portal; then
|
|
134
|
+
pass "mobilems-check-portal clean"
|
|
135
|
+
else
|
|
136
|
+
bad "mobilems-check-portal found recurring portal bugs — fix the FAIL lines above"
|
|
137
|
+
fi
|
|
138
|
+
else
|
|
139
|
+
echo " SKIP: mobilems-check-portal unavailable in this project (older framework line)"
|
|
140
|
+
fi
|
|
141
|
+
|
|
121
142
|
echo
|
|
122
143
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
123
144
|
exit "$fail"
|
|
@@ -374,6 +374,10 @@ jobs:
|
|
|
374
374
|
- name: Install Playwright browsers
|
|
375
375
|
run: npx playwright install --with-deps chromium
|
|
376
376
|
|
|
377
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); it reads sources only, so it
|
|
378
|
+
# runs before the build. Needs @smartsoft001-mobilems/angular with the `mobilems-check-portal` bin.
|
|
379
|
+
- run: npx mobilems-check-portal
|
|
380
|
+
|
|
377
381
|
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
|
|
378
382
|
# - run: npx nx-cloud record -- echo Hello World
|
|
379
383
|
- run: npx nx run-many -t lint test build e2e
|
|
@@ -382,6 +386,15 @@ jobs:
|
|
|
382
386
|
if: always()
|
|
383
387
|
```
|
|
384
388
|
|
|
389
|
+
> **`mobilems-check-portal` is part of the CI contract.** Whoever enables the workflow keeps the
|
|
390
|
+
> step before the build. A project that builds in `development.yml` instead adds the same
|
|
391
|
+
> `- run: npx mobilems-check-portal` step ahead of its `nx build` step. `scripts/verify-devops.sh`
|
|
392
|
+
> fails when `ci.yml` / `ci.yml.disabled` lacks it.
|
|
393
|
+
>
|
|
394
|
+
> **Existing portal?** It will report dozens of `innerhtml-external-links` findings until it opts
|
|
395
|
+
> into `contentLinks: true`. Use `- run: npx mobilems-check-portal --skip innerhtml-external-links`
|
|
396
|
+
> (or `check-portal-ignore` comments) first, then remove the `--skip` once `contentLinks: true` is set.
|
|
397
|
+
|
|
385
398
|
> Note: the reference's `ci.yml.disabled` pins Node 20. We thread `{{NODE_VERSION}}` here so the
|
|
386
399
|
> disabled template matches the rest of the project. If a project has a live `ci.yml` on a stale
|
|
387
400
|
> node version, report the drift and ask before changing it.
|
|
@@ -31,6 +31,16 @@ exists .github/workflows/push-to-client.yml
|
|
|
31
31
|
exists .github/workflows/ai-plan.yml
|
|
32
32
|
exists .github/workflows/ai-impl.yml
|
|
33
33
|
exists .github/workflows/ci.yml.disabled
|
|
34
|
+
# CI runs the portal guard before the build (references/workflows.md)
|
|
35
|
+
CI_FILE=""
|
|
36
|
+
for f in .github/workflows/ci.yml .github/workflows/ci.yml.disabled; do
|
|
37
|
+
[ -f "$f" ] && { CI_FILE="$f"; break; }
|
|
38
|
+
done
|
|
39
|
+
if [ -n "$CI_FILE" ] && grep -q "mobilems-check-portal" "$CI_FILE"; then
|
|
40
|
+
pass "$CI_FILE runs mobilems-check-portal"
|
|
41
|
+
else
|
|
42
|
+
bad "ci.yml (or ci.yml.disabled) does not run mobilems-check-portal"
|
|
43
|
+
fi
|
|
34
44
|
# secrets are referenced, not hardcoded
|
|
35
45
|
[ -f .github/workflows/development.yml ] && grep -q "secrets.GITLAB_TOKEN" .github/workflows/development.yml && pass "development.yml uses secrets" || bad "development.yml secrets.GITLAB_TOKEN"
|
|
36
46
|
|
|
@@ -22,13 +22,13 @@ export class MuseumEventsModule {}
|
|
|
22
22
|
|
|
23
23
|
## `events.routes.ts`
|
|
24
24
|
|
|
25
|
-
Every page is loaded lazily with `loadComponent`. The list route additionally provides
|
|
26
|
-
|
|
25
|
+
Every page is loaded lazily with `loadComponent`. The list route additionally provides
|
|
26
|
+
`provideBrowserMasonry()` (the real library in the browser — `window.Masonry`, set by `bootstrapApplication` from `@smartsoft001-mobilems/angular/bootstrap` in `main.ts` — and `null` during SSR, where the grid skips layout). Copy verbatim — there is no
|
|
27
27
|
`<PREFIX>` to thread here either.
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
import { Route } from '@angular/router';
|
|
31
|
-
import {
|
|
31
|
+
import { provideBrowserMasonry } from '@smartsoft001-mobilems/angular';
|
|
32
32
|
|
|
33
33
|
export const eventsRoutes: Route[] = [
|
|
34
34
|
{
|
|
@@ -64,27 +64,7 @@ export const eventsRoutes: Route[] = [
|
|
|
64
64
|
path: '',
|
|
65
65
|
loadComponent: () =>
|
|
66
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
|
-
],
|
|
67
|
+
providers: [provideBrowserMasonry()],
|
|
88
68
|
},
|
|
89
69
|
];
|
|
90
70
|
```
|
|
@@ -67,13 +67,22 @@ fi
|
|
|
67
67
|
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
68
68
|
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
69
69
|
# which the `new Masonry(` grep above cannot see.
|
|
70
|
-
if grep -
|
|
70
|
+
if grep -rqE "provide: Masonry|provideBrowserMasonry" "$LIBDIR" 2>/dev/null; then
|
|
71
71
|
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
72
72
|
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
73
73
|
else
|
|
74
74
|
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
75
75
|
fi
|
|
76
76
|
fi
|
|
77
|
+
# 2b. the provider is provideBrowserMasonry() — an anonymous stand-in class next to
|
|
78
|
+
# `provide: Masonry` reaches for window during SSR and is the bug the framework removed
|
|
79
|
+
STUB="$(grep -rlE "provide: Masonry" "$LIBDIR" 2>/dev/null | xargs grep -lE "return class|: class \{" 2>/dev/null || true)"
|
|
80
|
+
if [ -n "$STUB" ]; then
|
|
81
|
+
bad "hand-written Masonry stub (return class / : class {) — use provideBrowserMasonry() from @smartsoft001-mobilems/angular:"
|
|
82
|
+
printf ' %s\n' $STUB >&2
|
|
83
|
+
else
|
|
84
|
+
pass "no hand-written Masonry stub"
|
|
85
|
+
fi
|
|
77
86
|
# 3. a list page must render through the framework grid
|
|
78
87
|
LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
|
|
79
88
|
if [ -n "$LISTP" ]; then
|
|
@@ -106,6 +115,18 @@ else
|
|
|
106
115
|
pass "no filters component in this module"
|
|
107
116
|
fi
|
|
108
117
|
|
|
118
|
+
echo "== Portal guard =="
|
|
119
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
120
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
121
|
+
if npx --no-install mobilems-check-portal; then
|
|
122
|
+
pass "mobilems-check-portal clean"
|
|
123
|
+
else
|
|
124
|
+
bad "mobilems-check-portal found recurring portal bugs — fix the FAIL lines above"
|
|
125
|
+
fi
|
|
126
|
+
else
|
|
127
|
+
echo " SKIP: mobilems-check-portal unavailable in this project (older framework line)"
|
|
128
|
+
fi
|
|
129
|
+
|
|
109
130
|
echo
|
|
110
131
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
111
132
|
exit "$fail"
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Thread `<PREFIX>` (the `environment` + domain imports). `PublicCollectionsSharedModule.forFeature`
|
|
6
6
|
provides the list/detail behaviour over `MuseumCollection`; `CollectionsService` is provided as the
|
|
7
|
-
`CrudService`;
|
|
8
|
-
SSR. Note: unlike the objects module this one does NOT import `SmartSharedModule`.
|
|
7
|
+
`CrudService`; `provideBrowserMasonry()` supplies the real library in the browser and `null` during
|
|
8
|
+
SSR (the grid then skips layout). Note: unlike the objects module this one does NOT import `SmartSharedModule`.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
11
|
import { NgModule } from '@angular/core';
|
|
@@ -13,7 +13,7 @@ import { FormsModule } from '@angular/forms';
|
|
|
13
13
|
import { RouterModule } from '@angular/router';
|
|
14
14
|
import { CrudService } from '@smartsoft001/crud-shell-angular';
|
|
15
15
|
import { PublicCollectionsSharedModule } from '@smartsoft001-mobilems/public-collections-shell-angular';
|
|
16
|
-
import {
|
|
16
|
+
import { provideBrowserMasonry } from '@smartsoft001-mobilems/angular';
|
|
17
17
|
|
|
18
18
|
import { environment } from '@<PREFIX>/angular';
|
|
19
19
|
import { MuseumCollection } from '@<PREFIX>/museum-collections/domain';
|
|
@@ -28,29 +28,7 @@ import { CollectionsService } from './services';
|
|
|
28
28
|
provide: CrudService,
|
|
29
29
|
useClass: CollectionsService,
|
|
30
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
|
-
},
|
|
31
|
+
provideBrowserMasonry(),
|
|
54
32
|
],
|
|
55
33
|
imports: [
|
|
56
34
|
FormsModule,
|
|
@@ -53,13 +53,22 @@ fi
|
|
|
53
53
|
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
54
54
|
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
55
55
|
# which the `new Masonry(` grep above cannot see.
|
|
56
|
-
if grep -
|
|
56
|
+
if grep -rqE "provide: Masonry|provideBrowserMasonry" "$LIBDIR" 2>/dev/null; then
|
|
57
57
|
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
58
58
|
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
59
59
|
else
|
|
60
60
|
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
61
61
|
fi
|
|
62
62
|
fi
|
|
63
|
+
# 2b. the provider is provideBrowserMasonry() — an anonymous stand-in class next to
|
|
64
|
+
# `provide: Masonry` reaches for window during SSR and is the bug the framework removed
|
|
65
|
+
STUB="$(grep -rlE "provide: Masonry" "$LIBDIR" 2>/dev/null | xargs grep -lE "return class|: class \{" 2>/dev/null || true)"
|
|
66
|
+
if [ -n "$STUB" ]; then
|
|
67
|
+
bad "hand-written Masonry stub (return class / : class {) — use provideBrowserMasonry() from @smartsoft001-mobilems/angular:"
|
|
68
|
+
printf ' %s\n' $STUB >&2
|
|
69
|
+
else
|
|
70
|
+
pass "no hand-written Masonry stub"
|
|
71
|
+
fi
|
|
63
72
|
# 3. the ITEM page renders its objects grid through the framework grid — in the
|
|
64
73
|
# reference the collections LIST is a plain grid; masonry lives on the item page
|
|
65
74
|
ITEMP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'item*' 2>/dev/null || true)"
|
|
@@ -102,6 +111,18 @@ else
|
|
|
102
111
|
bad "translation gaps — fix the FAIL lines above"
|
|
103
112
|
fi
|
|
104
113
|
|
|
114
|
+
echo "== Portal guard =="
|
|
115
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
116
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
117
|
+
if npx --no-install mobilems-check-portal; then
|
|
118
|
+
pass "mobilems-check-portal clean"
|
|
119
|
+
else
|
|
120
|
+
bad "mobilems-check-portal found recurring portal bugs — fix the FAIL lines above"
|
|
121
|
+
fi
|
|
122
|
+
else
|
|
123
|
+
echo " SKIP: mobilems-check-portal unavailable in this project (older framework line)"
|
|
124
|
+
fi
|
|
125
|
+
|
|
105
126
|
echo
|
|
106
127
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
107
128
|
exit "$fail"
|
|
@@ -66,7 +66,7 @@ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on
|
|
|
66
66
|
with the `Masonry` SSR factory. Add the lazy `obiekty` route to `appRoutes` in
|
|
67
67
|
`apps/web/src/app/app.routes.ts` and a matching child under the `wyszukaj/:text` search route.
|
|
68
68
|
4. **Objects shared components** — see `references/shared-components.md`: build `objects-grid-item`,
|
|
69
|
-
`objects-list-item` (each `extends` the framework `Objects…ItemBaseComponent`, with the app's own markup), and the object gallery widget (`widget/gallery-object`) in the shared lib +
|
|
69
|
+
`objects-list-item` (each `extends` the framework `Objects…ItemBaseComponent`, with the app's own markup), and the object gallery widget (`widget/gallery-object`, `extends` the framework `WidgetGalleryObjectBaseComponent`) in the shared lib +
|
|
70
70
|
export them; and register the object gallery widget in the widget content-type map (`GALLERY_OBJECT`).
|
|
71
71
|
`ISpecificObject` and the filter models come from `@smartsoft001-mobilems/objects-shell-angular`.
|
|
72
72
|
5. **Translations** — copy the module's translation groups (`OBJECTS`, `DETAILS_PAGE`, `GALLERY`) from the reference
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
## `libs/museum-objects/shell/angular/src/lib/objects.module.ts`
|
|
4
4
|
|
|
5
5
|
Thread `<PREFIX>` (the `environment` import). `ObjectsSharedModule.forFeature` provides the list/detail
|
|
6
|
-
behaviour over `MsObject`;
|
|
7
|
-
|
|
6
|
+
behaviour over `MsObject`; `provideBrowserMasonry()` supplies the real library in the browser and `null`
|
|
7
|
+
during SSR (the grid then skips layout).
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import { NgModule } from '@angular/core';
|
|
@@ -13,38 +13,14 @@ import { RouterModule } from '@angular/router';
|
|
|
13
13
|
import { SharedModule as SmartSharedModule } from '@smartsoft001/angular';
|
|
14
14
|
import { MsObject } from '@smartsoft001-mobilems/objects-domain';
|
|
15
15
|
import { ObjectsSharedModule } from '@smartsoft001-mobilems/objects-shell-angular';
|
|
16
|
-
import {
|
|
16
|
+
import { provideBrowserMasonry } from '@smartsoft001-mobilems/angular';
|
|
17
17
|
|
|
18
18
|
import { environment } from '@<PREFIX>/angular';
|
|
19
19
|
|
|
20
20
|
import { routes } from './objects.routes';
|
|
21
21
|
|
|
22
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
|
-
],
|
|
23
|
+
providers: [provideBrowserMasonry()],
|
|
48
24
|
imports: [
|
|
49
25
|
FormsModule,
|
|
50
26
|
SmartSharedModule,
|
|
@@ -32,7 +32,8 @@ export * from './objects-list-item';
|
|
|
32
32
|
|
|
33
33
|
## 2. Object gallery widget
|
|
34
34
|
|
|
35
|
-
Build `components/widget/gallery-object`
|
|
35
|
+
Build `components/widget/gallery-object` as `extends WidgetGalleryObjectBaseComponent` (from
|
|
36
|
+
`@smartsoft001-mobilems/angular`) with the app's own markup and styles, and export it from the widget barrel:
|
|
36
37
|
|
|
37
38
|
```ts
|
|
38
39
|
// components/widget/index.ts:
|
|
@@ -59,13 +59,22 @@ fi
|
|
|
59
59
|
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
60
60
|
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
61
61
|
# which the `new Masonry(` grep above cannot see.
|
|
62
|
-
if grep -
|
|
62
|
+
if grep -rqE "provide: Masonry|provideBrowserMasonry" "$LIBDIR" 2>/dev/null; then
|
|
63
63
|
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
64
64
|
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
65
65
|
else
|
|
66
66
|
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
67
67
|
fi
|
|
68
68
|
fi
|
|
69
|
+
# 2b. the provider is provideBrowserMasonry() — an anonymous stand-in class next to
|
|
70
|
+
# `provide: Masonry` reaches for window during SSR and is the bug the framework removed
|
|
71
|
+
STUB="$(grep -rlE "provide: Masonry" "$LIBDIR" 2>/dev/null | xargs grep -lE "return class|: class \{" 2>/dev/null || true)"
|
|
72
|
+
if [ -n "$STUB" ]; then
|
|
73
|
+
bad "hand-written Masonry stub (return class / : class {) — use provideBrowserMasonry() from @smartsoft001-mobilems/angular:"
|
|
74
|
+
printf ' %s\n' $STUB >&2
|
|
75
|
+
else
|
|
76
|
+
pass "no hand-written Masonry stub"
|
|
77
|
+
fi
|
|
69
78
|
# 3. a list page must render through the framework grid
|
|
70
79
|
LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
|
|
71
80
|
if [ -n "$LISTP" ]; then
|
|
@@ -107,6 +116,18 @@ else
|
|
|
107
116
|
bad "translation gaps — fix the FAIL lines above"
|
|
108
117
|
fi
|
|
109
118
|
|
|
119
|
+
echo "== Portal guard =="
|
|
120
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
121
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
122
|
+
if npx --no-install mobilems-check-portal; then
|
|
123
|
+
pass "mobilems-check-portal clean"
|
|
124
|
+
else
|
|
125
|
+
bad "mobilems-check-portal found recurring portal bugs — fix the FAIL lines above"
|
|
126
|
+
fi
|
|
127
|
+
else
|
|
128
|
+
echo " SKIP: mobilems-check-portal unavailable in this project (older framework line)"
|
|
129
|
+
fi
|
|
130
|
+
|
|
110
131
|
echo
|
|
111
132
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
112
133
|
exit "$fail"
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Thread `<PREFIX>` (the `environment` import). This module REUSES `ArticlesSharedModule` (the same
|
|
6
6
|
shared module the news/articles/thematic-paths modules use) but configures it over the Articles
|
|
7
|
-
`Article` type with its OWN `getReducer` and **no `baseQuery`**.
|
|
8
|
-
real library in the browser and
|
|
7
|
+
`Article` type with its OWN `getReducer` and **no `baseQuery`**. `provideBrowserMasonry()` supplies the
|
|
8
|
+
real library in the browser and `null` during SSR (identical to objects). It provides the
|
|
9
9
|
`SERVICES` array (which binds both `CrudService` and `ArticlesService` to `PartnersService`).
|
|
10
10
|
|
|
11
11
|
```ts
|
|
@@ -14,7 +14,7 @@ import { FormsModule } from '@angular/forms';
|
|
|
14
14
|
import { RouterModule } from '@angular/router';
|
|
15
15
|
import { Article } from '@smartsoft001-mobilems/articles-domain';
|
|
16
16
|
import { ArticlesSharedModule } from '@smartsoft001-mobilems/articles-shell-angular';
|
|
17
|
-
import {
|
|
17
|
+
import { provideBrowserMasonry } from '@smartsoft001-mobilems/angular';
|
|
18
18
|
|
|
19
19
|
import { environment } from '@<PREFIX>/angular';
|
|
20
20
|
|
|
@@ -23,32 +23,7 @@ import { routes } from './partners.routes';
|
|
|
23
23
|
import { SERVICES } from './services';
|
|
24
24
|
|
|
25
25
|
@NgModule({
|
|
26
|
-
providers: [
|
|
27
|
-
...SERVICES,
|
|
28
|
-
{
|
|
29
|
-
provide: Masonry,
|
|
30
|
-
useFactory: () => {
|
|
31
|
-
if (typeof window !== 'undefined' && (window as any)['Masonry']) {
|
|
32
|
-
return (window as any)['Masonry'];
|
|
33
|
-
}
|
|
34
|
-
// Return a mock for SSR
|
|
35
|
-
return class {
|
|
36
|
-
constructor() {
|
|
37
|
-
// no-op
|
|
38
|
-
}
|
|
39
|
-
layout() {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
reloadItems() {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
destroy() {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
],
|
|
26
|
+
providers: [...SERVICES, provideBrowserMasonry()],
|
|
52
27
|
imports: [
|
|
53
28
|
FormsModule,
|
|
54
29
|
ArticlesSharedModule.forFeature({
|
|
@@ -68,13 +68,22 @@ fi
|
|
|
68
68
|
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
69
69
|
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
70
70
|
# which the `new Masonry(` grep above cannot see.
|
|
71
|
-
if grep -
|
|
71
|
+
if grep -rqE "provide: Masonry|provideBrowserMasonry" "$LIBDIR" 2>/dev/null; then
|
|
72
72
|
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
73
73
|
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
74
74
|
else
|
|
75
75
|
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
76
76
|
fi
|
|
77
77
|
fi
|
|
78
|
+
# 2b. the provider is provideBrowserMasonry() — an anonymous stand-in class next to
|
|
79
|
+
# `provide: Masonry` reaches for window during SSR and is the bug the framework removed
|
|
80
|
+
STUB="$(grep -rlE "provide: Masonry" "$LIBDIR" 2>/dev/null | xargs grep -lE "return class|: class \{" 2>/dev/null || true)"
|
|
81
|
+
if [ -n "$STUB" ]; then
|
|
82
|
+
bad "hand-written Masonry stub (return class / : class {) — use provideBrowserMasonry() from @smartsoft001-mobilems/angular:"
|
|
83
|
+
printf ' %s\n' $STUB >&2
|
|
84
|
+
else
|
|
85
|
+
pass "no hand-written Masonry stub"
|
|
86
|
+
fi
|
|
78
87
|
# 3. a list page must render through the framework grid
|
|
79
88
|
LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
|
|
80
89
|
if [ -n "$LISTP" ]; then
|
|
@@ -116,6 +125,18 @@ else
|
|
|
116
125
|
bad "translation gaps — fix the FAIL lines above"
|
|
117
126
|
fi
|
|
118
127
|
|
|
128
|
+
echo "== Portal guard =="
|
|
129
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
130
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
131
|
+
if npx --no-install mobilems-check-portal; then
|
|
132
|
+
pass "mobilems-check-portal clean"
|
|
133
|
+
else
|
|
134
|
+
bad "mobilems-check-portal found recurring portal bugs — fix the FAIL lines above"
|
|
135
|
+
fi
|
|
136
|
+
else
|
|
137
|
+
echo " SKIP: mobilems-check-portal unavailable in this project (older framework line)"
|
|
138
|
+
fi
|
|
139
|
+
|
|
119
140
|
echo
|
|
120
141
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
121
142
|
exit "$fail"
|
|
@@ -69,14 +69,19 @@ The framework ships the bases; the app keeps the templates (design) and the data
|
|
|
69
69
|
`PageHeadingOwner`; the template renders `loading()`, `loadFailed()` (→ `app-error-retry (retry)="retry()"`),
|
|
70
70
|
the breadcrumb declaration (`pageRoute ?? '/strona/' + id + '/' + (title | smartSlug)`), a hidden `h1`
|
|
71
71
|
unless `sectionsHaveH1()`, and `<smart-mobilems-page-sections [sections]="pageData.sections ?? null">`
|
|
72
|
-
with `[innerHTML]="contentHtml()"` in the slot.
|
|
72
|
+
with `[innerHTML]="contentHtml()"` in the slot. CMS `[innerHTML]` links (leave-site modal, `rel`,
|
|
73
|
+
new-tab note, `#id` scroll without a route change) are handled by `AppComponent`'s content-link
|
|
74
|
+
directives, but only when `provideMobilemsApp({ contentLinks: true })` is set — it is **opt-in, off by
|
|
75
|
+
default**, so the foundation sets it (`references/config-providers.md`). With `contentLinks: false`
|
|
76
|
+
(or unset), every `[innerHTML]` carries `smartExternalLinks smartInPageAnchors` itself, otherwise
|
|
77
|
+
`mobilems-check-portal` fails `innerhtml-external-links`. Routes use `pageResolver` (`strona/:id`) and
|
|
73
78
|
`createSlugPageResolver('kontakt', { emptyTitleKey: 'CONTACT.pageTitle' })` with
|
|
74
79
|
`runGuardsAndResolvers: 'always'` and `data: { pageRoute: '/kontakt' }` for `/kontakt`.
|
|
75
80
|
- `search.component` extends `SearchBaseComponent<'obiekty' | …>` and declares only `tabDefinitions`
|
|
76
81
|
(`{ id, labelKey: 'SEARCH.tabs.*', count: (d) => d.objects }` per module the app ships); the template
|
|
77
82
|
renders the heading, `app-tabs` with `[(activeTabId)]="activeTab"` and a `<router-outlet>`.
|
|
78
83
|
- `not-found.component` extends `NotFoundComponent` and renders `<smart-mobilems-not-found-content
|
|
79
|
-
|
|
84
|
+
[fullHeight]="true" />` (plus the breadcrumb declaration with `url`).
|
|
80
85
|
|
|
81
86
|
Run a feature-package grep over the pages you copy as a guard. **Do not copy `article-preview`** — the
|
|
82
87
|
reference's `article-preview` page imports `@smartsoft001-mobilems/articles-domain` (it previews an
|
|
@@ -45,12 +45,19 @@ export const appConfig: ApplicationConfig = {
|
|
|
45
45
|
provideMobilemsApp({
|
|
46
46
|
environment, // provides MOBILEMS_ENVIRONMENT; `version` defaults to environment.version
|
|
47
47
|
languages: { available: ['pl', 'en'], default: 'pl' },
|
|
48
|
+
contentLinks: true, // opt-in (off by default): the shell handles external and #id links in CMS content
|
|
48
49
|
skipLinks: {
|
|
49
50
|
home: [
|
|
50
|
-
{
|
|
51
|
+
{
|
|
52
|
+
targetId: 'main-new-objects',
|
|
53
|
+
translationKey: 'HOME.accessibility.skipToNewObjects',
|
|
54
|
+
},
|
|
51
55
|
],
|
|
52
56
|
subpage: [
|
|
53
|
-
{
|
|
57
|
+
{
|
|
58
|
+
targetId: 'main-content',
|
|
59
|
+
translationKey: 'HOME.accessibility.skipToContent',
|
|
60
|
+
},
|
|
54
61
|
],
|
|
55
62
|
},
|
|
56
63
|
// Produkcyjnie translacje są własnością CMS-a klienta (setting/theme),
|
|
@@ -65,7 +72,11 @@ export const appConfig: ApplicationConfig = {
|
|
|
65
72
|
},
|
|
66
73
|
seo: {
|
|
67
74
|
siteNameFallback: { pl: '<NAZWA MUZEUM>', en: '<MUSEUM NAME>' },
|
|
68
|
-
defaultOgImage: {
|
|
75
|
+
defaultOgImage: {
|
|
76
|
+
url: '/gfx/og-default.jpg',
|
|
77
|
+
width: 1200,
|
|
78
|
+
height: 630,
|
|
79
|
+
},
|
|
69
80
|
siteUrl: () => environment.siteUrl,
|
|
70
81
|
},
|
|
71
82
|
}),
|
|
@@ -121,3 +121,42 @@ These conventions were enforced by QA across modules — bake them in from the s
|
|
|
121
121
|
- **Image fallback:** cards show the app's `no-image` placeholder when a thumbnail fails to load.
|
|
122
122
|
- Keyword/tag chips must wrap on 360 px wide screens — no page-level horizontal scroll at
|
|
123
123
|
360/768/1024/1440.
|
|
124
|
+
|
|
125
|
+
## 8. Recurring portal bugs (MOB-1889..1895)
|
|
126
|
+
|
|
127
|
+
Run `npx mobilems-check-portal` (it is part of `scripts/verify-init.sh` and of CI, see
|
|
128
|
+
`mobilems-devops-config`). It reads sources only, so the items marked "manual" still need a check in
|
|
129
|
+
the browser or against the SSR response. Each item says what enforces it.
|
|
130
|
+
|
|
131
|
+
- [ ] **CLS < 0.1 with SSR.** No `[width]`/`[height]` bound to `naturalWidth`/`naturalHeight`, images
|
|
132
|
+
reserve their space. _Enforced in part_: `natural-size-binding`. Measure the rest (Lighthouse).
|
|
133
|
+
- [ ] **`Content-Encoding` on the HTML.** `createServer` compresses by default (`compression` peer
|
|
134
|
+
installed, `ICompressionOptions` to tune); do not add a second `compression()`. _Manual_:
|
|
135
|
+
`curl -sI -H 'Accept-Encoding: gzip' <url>`.
|
|
136
|
+
- [ ] **No `window is not defined` in SSR logs.** Masonry via `provideBrowserMasonry()`, browser-only work
|
|
137
|
+
behind `isPlatformBrowser`. _Enforced in part_: `masonry-stub`. Read the server log on the first load.
|
|
138
|
+
- [ ] **Modal for a link in content, menu and footer.** Set `provideMobilemsApp({ contentLinks: true })`
|
|
139
|
+
(opt-in, off by default) or put `smartExternalLinks` on each `[innerHTML]` host and on the menu and
|
|
140
|
+
footer. _Enforced_: `innerhtml-external-links` (skipped only with the opt-in). Menu/footer: manual.
|
|
141
|
+
- [ ] **Footnote without route change.** A `#id` link scrolls inside the page (`contentLinks` covers it
|
|
142
|
+
in content). _Manual_: click a footnote, the URL path must not change.
|
|
143
|
+
- [ ] **Photos uncropped and ≤ 50svh.** No `object-cover` on museum photos; cap the height at `50svh`.
|
|
144
|
+
_Enforced in part_: `object-cover` (the height cap is manual).
|
|
145
|
+
- [ ] **No placeholders in Contact/footer.** _Enforced_: `contact-placeholder` (`Przykładowa`,
|
|
146
|
+
`00 000 00 00`).
|
|
147
|
+
- [ ] **SVG icons and a safe download filename.** An icon component, not an emoji, arrow or cross glyph;
|
|
148
|
+
the download name comes from `photoFileName`. _Enforced in part_: `template-emoji` (the filename
|
|
149
|
+
is manual).
|
|
150
|
+
- [ ] **Year debounce 500 ms.** The year range filter extends the framework date-input base
|
|
151
|
+
(`FILTER_DATE_INPUT_DEBOUNCE_MS = 500`). _Manual_.
|
|
152
|
+
- [ ] **Filter pill ≥ 24 px.** WCAG 2.2 target size for the active-filter pills. _Manual_ (`ui-a11y-validator`).
|
|
153
|
+
- [ ] **Hover only with a pointer.** `presets: [mobilemsPreset]` (`hoverOnlyWhenSupported`), see
|
|
154
|
+
`tailwind-styles.md`. _Manual_: no sticky hover on a touch emulation.
|
|
155
|
+
- [ ] **List chunk without `three`.** `online-3d-viewer` is loaded with `createLazyLoader(() => import(…))`,
|
|
156
|
+
never a static import. _Enforced_: `static-online-3d-viewer`. Confirm in the build stats.
|
|
157
|
+
- [ ] **No `width="0"` in SSR HTML.** _Enforced in part_: `natural-size-binding`. Grep the SSR
|
|
158
|
+
response: `curl -s <url> | grep -c 'width="0"'`.
|
|
159
|
+
- [ ] **`loading="lazy"` below the first screen.** The LCP image stays eager. _Manual_.
|
|
160
|
+
|
|
161
|
+
To accept a finding on purpose, add `check-portal-ignore <rule>: reason` on the line (see the framework
|
|
162
|
+
README, `mobilems-check-portal`).
|
|
@@ -100,6 +100,23 @@ Build it registering only the **foundation widgets** — `text`, `image`, `video
|
|
|
100
100
|
and type-map entries when they build those widgets. Build the widget barrel
|
|
101
101
|
(`components/widget/index.ts`) exporting the foundation widgets only.
|
|
102
102
|
|
|
103
|
+
The foundation `image`, `audio`, `media` and `gallery-image` widgets are generated as `extends
|
|
104
|
+
WidgetImageBaseComponent` / `WidgetAudioBaseComponent` / `WidgetMediaBaseComponent` /
|
|
105
|
+
`WidgetGalleryImageBaseComponent`, with the app's own markup and styles; `text` extends
|
|
106
|
+
`WidgetBaseComponent` and `video` has no framework base. The framework ships no templates.
|
|
107
|
+
|
|
108
|
+
The bases read template refs, so the generated markup must provide them:
|
|
109
|
+
|
|
110
|
+
- **`gallery-image`**: the grid element carries `#masonryGrid` and contains one
|
|
111
|
+
`<div class="masonry-grid-sizer"></div>` (column width), one `<div class="masonry-gutter-sizer"></div>`
|
|
112
|
+
(gutter) and the `.masonry-item` tiles. Give both sizers their widths in the app CSS, with the same
|
|
113
|
+
768/1024 px breakpoints as `WIDGET_GALLERY_IMAGE_BREAKPOINTS`. A missing sizer only degrades (gutter `0`,
|
|
114
|
+
column width from the first tile, one dev-mode `console.warn`), so do not rely on the fallback.
|
|
115
|
+
Provide `Masonry` with `provideBrowserMasonry()`.
|
|
116
|
+
- **`audio`**: an `<audio #player>` element bound to the events listed in the base's README row.
|
|
117
|
+
- `alt` is an input on `WidgetImageBaseComponent` but a derived computed on
|
|
118
|
+
`WidgetGalleryImageBaseComponent` — bind widget inputs per type in the container, never `alt` on every widget.
|
|
119
|
+
|
|
103
120
|
## Models — framework, not the lib
|
|
104
121
|
|
|
105
122
|
The museum API models (`IObject`, `IImage`, `IDictionary`, `IDictionaryEntry`, `IArticleListItem`,
|
|
@@ -129,16 +146,16 @@ files duplicate exports that now exist; copying those is the single most common
|
|
|
129
146
|
|
|
130
147
|
Framework now — do **not** generate (import from `@smartsoft001-mobilems/angular`):
|
|
131
148
|
|
|
132
|
-
| Kind | Framework exports
|
|
133
|
-
| ------------ |
|
|
134
|
-
| environment | `createMobilemsEnvironment`, `MOBILEMS_ENVIRONMENT`, `EnvironmentService`
|
|
135
|
-
| pipes | `altText`, `fileUrl`, `backgroundUrl`, `plainText`, `stripAnchors`, `capitalize`
|
|
136
|
-
| tools | `classifyLink`, `isInternalLink`, `normalizeUrl`, `stripHtml`, `formatNamedEntries`, `normalizeSearchText`, `newTabText`, `pageToOffset`, `decodeHtmlEntities`, `ElementIdSequence`, `createSafeTimeouts`, `photoFileName`/`urlExtension`, `widgetImageUrl`, `overflowingTags`, `themeTagKey`, `themeTagsLayout`, `THEME_TAGS_VISIBLE_LIMIT`, `resolveMenuItemTarget`, `isNavigableMenuItem` (+ `MenuItemTarget`), `objectCardMeta`/`objectCardPlace`/`objectCardExhibition`/`exhibitionText` (+ `IObjectCardSource`), `SERIES_TITLES_FILTER`, `catalogEntryFilterParams` (the named-entries filter query params), `recommendedPerPage`
|
|
137
|
-
| directives | `a[smartExternalLink]`, `[smartExternalLinks]`, `[smartFocusTrap]`, `[smartTruncatedTitle]`, `[smartInPageAnchors]` (`InPageAnchorsDirective`; references use `app…` — rename the attributes)
|
|
138
|
-
| services | `ConfirmationModalService`, `ExternalLinkConfirmationService`, `FilterPersistenceService`, `OrientationService`, `WcagChangesService`, `LineHeightService`, `BreadcrumbsService`, `ContactService`, `DataService`, `MuseumSeoService`
|
|
139
|
-
| interceptors | `LanguageInterceptor`, `PreviewAuthInterceptor`, `SsrTimeoutInterceptor`, `DedupeRequestInterceptor`
|
|
140
|
-
| bases | `CalendarBaseComponent`, `PaginationDotsBaseComponent`, `PaginationBaseComponent`, `CarouselBaseComponent`, `PartnersCarouselBaseComponent`, `NewObjectsCarouselBaseComponent`, `ArticlesCarouselBaseComponent`, `SliderBaseComponent` (+ `ISliderOptions`; `ScrollTopComponent` is server-guarded), `GalleryBaseComponent`, `GalleryFullscreenDialogBaseComponent` (`GalleryFullscreenBaseComponent` is `@deprecated`), `ConfirmationModalBaseComponent`, `NewsletterModalBaseComponent`, `MapBaseComponent`, `FilterInputBaseComponent`, `FilterDateInputBaseComponent`, `FilterCustomDictionaryInputBaseComponent`, `FilterDictionaryInputBaseComponent` (inline tree, `source`, paging), `ThemeTagsFilterBaseComponent`, `CatalogFilterBarBaseComponent`, `CatalogToolbarBaseComponent`, `SearchbarBaseComponent`, `SelectCheckboxBaseComponent` (+ `ISelectionHooks`), `DropdownBaseComponent`, `TabsBaseComponent`, `WcagBaseComponent` (+ `WCAG_OPTION_GROUPS`), `BreadcrumbsBaseComponent`, `BreadcrumbsBarBaseComponent`, `MenuItemBaseComponent`, `ObjectCardBaseComponent`, `ObjectsGridItemBaseComponent`, `ObjectsListItemBaseComponent`, `CollectionCardBaseComponent`, `CollectionsGridItemBaseComponent`, `FavoriteButtonBaseComponent` (+ `IFavoriteHooks`, `IFavoriteRef`), `FavoritesNoticeBaseComponent`, `SelectionActionsBaseComponent`, `DownloadCardsBaseComponent`, `RecommendedBaseComponent` |
|
|
141
|
-
| SSR tools | `injectServerResponseInit`, `appendPreloadLinks` (image preload `Link` headers)
|
|
149
|
+
| Kind | Framework exports |
|
|
150
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| environment | `createMobilemsEnvironment`, `MOBILEMS_ENVIRONMENT`, `EnvironmentService` |
|
|
152
|
+
| pipes | `altText`, `fileUrl`, `backgroundUrl`, `plainText`, `stripAnchors`, `capitalize` |
|
|
153
|
+
| tools | `classifyLink`, `isInternalLink`, `normalizeUrl`, `stripHtml`, `formatNamedEntries`, `normalizeSearchText`, `newTabText`, `pageToOffset`, `decodeHtmlEntities`, `ElementIdSequence`, `createSafeTimeouts`, `createLazyLoader` (single-flight dynamic `import()`, e.g. `online-3d-viewer`), `photoFileName`/`urlExtension`, `widgetImageUrl`, `overflowingTags`, `themeTagKey`, `themeTagsLayout`, `THEME_TAGS_VISIBLE_LIMIT`, `resolveMenuItemTarget`, `isNavigableMenuItem` (+ `MenuItemTarget`), `objectCardMeta`/`objectCardPlace`/`objectCardExhibition`/`exhibitionText` (+ `IObjectCardSource`), `SERIES_TITLES_FILTER`, `catalogEntryFilterParams` (the named-entries filter query params), `recommendedPerPage`, `widgetDomId`, `cleanWidgetText`, `formatFileSize`, `safeDecode` |
|
|
154
|
+
| directives | `a[smartExternalLink]`, `[smartExternalLinks]`, `[smartFocusTrap]`, `[smartTruncatedTitle]`, `[smartInPageAnchors]` (`InPageAnchorsDirective`; references use `app…` — rename the attributes) |
|
|
155
|
+
| services | `ConfirmationModalService`, `ExternalLinkConfirmationService`, `FilterPersistenceService`, `OrientationService`, `WcagChangesService`, `LineHeightService`, `BreadcrumbsService`, `ContactService`, `DataService`, `MuseumSeoService` |
|
|
156
|
+
| interceptors | `LanguageInterceptor`, `PreviewAuthInterceptor`, `SsrTimeoutInterceptor`, `DedupeRequestInterceptor` |
|
|
157
|
+
| bases | `CalendarBaseComponent`, `PaginationDotsBaseComponent`, `PaginationBaseComponent`, `CarouselBaseComponent`, `PartnersCarouselBaseComponent`, `NewObjectsCarouselBaseComponent`, `ArticlesCarouselBaseComponent`, `SliderBaseComponent` (+ `ISliderOptions`; `ScrollTopComponent` is server-guarded), `GalleryBaseComponent`, `GalleryFullscreenDialogBaseComponent` (`GalleryFullscreenBaseComponent` is `@deprecated`), `ConfirmationModalBaseComponent`, `NewsletterModalBaseComponent`, `MapBaseComponent`, `FilterInputBaseComponent`, `FilterDateInputBaseComponent`, `FilterCustomDictionaryInputBaseComponent`, `FilterDictionaryInputBaseComponent` (inline tree, `source`, paging), `ThemeTagsFilterBaseComponent`, `CatalogFilterBarBaseComponent`, `CatalogToolbarBaseComponent`, `SearchbarBaseComponent`, `SelectCheckboxBaseComponent` (+ `ISelectionHooks`), `DropdownBaseComponent`, `TabsBaseComponent`, `WcagBaseComponent` (+ `WCAG_OPTION_GROUPS`), `BreadcrumbsBaseComponent`, `BreadcrumbsBarBaseComponent`, `MenuItemBaseComponent`, `ObjectCardBaseComponent`, `ObjectsGridItemBaseComponent`, `ObjectsListItemBaseComponent`, `CollectionCardBaseComponent`, `CollectionsGridItemBaseComponent`, `FavoriteButtonBaseComponent` (+ `IFavoriteHooks`, `IFavoriteRef`), `FavoritesNoticeBaseComponent`, `SelectionActionsBaseComponent`, `DownloadCardsBaseComponent`, `RecommendedBaseComponent`, `WidgetImageBaseComponent`, `WidgetMediaBaseComponent`, `WidgetGalleryImageBaseComponent`, `WidgetAudioBaseComponent`, `WidgetGalleryObjectBaseComponent` (all extend `WidgetBaseComponent`) |
|
|
158
|
+
| SSR tools | `injectServerResponseInit`, `appendPreloadLinks` (image preload `Link` headers) |
|
|
142
159
|
|
|
143
160
|
What stays in the app lib: the **template and styles** of every base above — the museum keeps its
|
|
144
161
|
own HTML and Tailwind classes (the filter inputs, searchbar, catalog toolbar and filter bar, theme tags and select
|
|
@@ -11,7 +11,10 @@ WCAG-clean skin. The token **keys** are the structural contract the templates us
|
|
|
11
11
|
as written. Step 9 swaps the values for the client's brand spec if there is one.
|
|
12
12
|
|
|
13
13
|
```js
|
|
14
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
mobilemsContent,
|
|
16
|
+
mobilemsPreset,
|
|
17
|
+
} = require('@smartsoft001-mobilems/angular/tailwind');
|
|
15
18
|
|
|
16
19
|
/** @type {import('tailwindcss').Config} */
|
|
17
20
|
module.exports = {
|
|
@@ -20,8 +23,7 @@ module.exports = {
|
|
|
20
23
|
'./libs/**/*.{html,ts}',
|
|
21
24
|
...mobilemsContent,
|
|
22
25
|
],
|
|
23
|
-
|
|
24
|
-
darkMode: ['class', '[class~="contrast-high"]'],
|
|
26
|
+
presets: [mobilemsPreset],
|
|
25
27
|
theme: {
|
|
26
28
|
extend: {
|
|
27
29
|
colors: {
|
|
@@ -76,7 +78,10 @@ module.exports = {
|
|
|
76
78
|
> needed when modules arrive. `mobilemsContent` adds the framework component templates (app shell,
|
|
77
79
|
> 404, page sections, module pages): their `smart-*` classes live in `node_modules`, so without it
|
|
78
80
|
> they are missing from the app CSS — the framework skip links carry their own inline styles, the
|
|
79
|
-
> rest does not. `
|
|
81
|
+
> rest does not. `mobilemsPreset` (opt-in, no `content`, no `theme`) carries what every portal shares:
|
|
82
|
+
> the `smart-` `prefix`, `darkMode` keyed off `contrast-high` for the WCAG high-contrast toggle, and
|
|
83
|
+
> `future.hoverOnlyWhenSupported`, which wraps `hover:` in `@media (hover: hover)` so touch devices no
|
|
84
|
+
> longer get sticky hover states. Brand tokens stay in this file's `theme.extend`.
|
|
80
85
|
|
|
81
86
|
> **Font tokens are named by role** (`heading`, `body`, `ui`), never after a typeface or a project.
|
|
82
87
|
> In reference mode the copied templates may use the source project's own token names — remap those
|
|
@@ -143,6 +143,18 @@ else
|
|
|
143
143
|
pass "workspace aliases use @$PREFIX/"
|
|
144
144
|
fi
|
|
145
145
|
|
|
146
|
+
echo "== Portal guard =="
|
|
147
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
148
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
149
|
+
if npx --no-install mobilems-check-portal; then
|
|
150
|
+
pass "mobilems-check-portal clean"
|
|
151
|
+
else
|
|
152
|
+
bad "mobilems-check-portal found recurring portal bugs — fix the FAIL lines above"
|
|
153
|
+
fi
|
|
154
|
+
else
|
|
155
|
+
echo " SKIP: mobilems-check-portal unavailable in this project (older framework line)"
|
|
156
|
+
fi
|
|
157
|
+
|
|
146
158
|
echo
|
|
147
159
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
148
160
|
exit "$fail"
|
|
@@ -56,6 +56,18 @@ else
|
|
|
56
56
|
echo " SKIP: mobilems-check-translations niedostępny w tym projekcie (starsza linia frameworka)"
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
|
+
echo "== Strażnik portalu =="
|
|
60
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
61
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
62
|
+
if npx --no-install mobilems-check-portal; then
|
|
63
|
+
pass "brak naruszeń strażnika portalu"
|
|
64
|
+
else
|
|
65
|
+
bad "strażnik portalu zgłosił błędy — popraw wypisane wyżej"
|
|
66
|
+
fi
|
|
67
|
+
else
|
|
68
|
+
echo " SKIP: mobilems-check-portal niedostępny w tym projekcie (starsza linia frameworka)"
|
|
69
|
+
fi
|
|
70
|
+
|
|
59
71
|
echo
|
|
60
72
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
61
73
|
exit "$fail"
|
|
@@ -53,6 +53,18 @@ else
|
|
|
53
53
|
echo " SKIP: mobilems-check-translations niedostępny w tym projekcie (starsza linia frameworka)"
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
|
+
echo "== Strażnik portalu =="
|
|
57
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
58
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
59
|
+
if npx --no-install mobilems-check-portal; then
|
|
60
|
+
pass "brak naruszeń strażnika portalu"
|
|
61
|
+
else
|
|
62
|
+
bad "strażnik portalu zgłosił błędy — popraw wypisane wyżej"
|
|
63
|
+
fi
|
|
64
|
+
else
|
|
65
|
+
echo " SKIP: mobilems-check-portal niedostępny w tym projekcie (starsza linia frameworka)"
|
|
66
|
+
fi
|
|
67
|
+
|
|
56
68
|
echo
|
|
57
69
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
58
70
|
exit "$fail"
|
package/plugins/mobilems/skills/mobilems-thematic-publications/references/module-and-routes.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Thread `<PREFIX>` (the `environment` + domain imports). This module REUSES `ArticlesSharedModule`
|
|
6
6
|
(the same shared module the news/articles modules use) but configures it over its OWN
|
|
7
|
-
`MuseumThematicPath` entity with its OWN `getReducer` and **no `baseQuery`**.
|
|
8
|
-
|
|
7
|
+
`MuseumThematicPath` entity with its OWN `getReducer` and **no `baseQuery`**. `provideBrowserMasonry()`
|
|
8
|
+
supplies the real library in the browser (`window.Masonry`, set by `bootstrapApplication` from `@smartsoft001-mobilems/angular/bootstrap` in `main.ts`) and `null` during SSR (identical to objects).
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
11
|
import { NgModule } from '@angular/core';
|
|
@@ -13,7 +13,7 @@ import { FormsModule } from '@angular/forms';
|
|
|
13
13
|
import { RouterModule } from '@angular/router';
|
|
14
14
|
import { SharedModule as SmartSharedModule } from '@smartsoft001/angular';
|
|
15
15
|
import { ArticlesSharedModule } from '@smartsoft001-mobilems/articles-shell-angular';
|
|
16
|
-
import {
|
|
16
|
+
import { provideBrowserMasonry } from '@smartsoft001-mobilems/angular';
|
|
17
17
|
|
|
18
18
|
import { environment } from '@<PREFIX>/angular';
|
|
19
19
|
import { MuseumThematicPath } from '@<PREFIX>/museum-thematic-paths/domain';
|
|
@@ -24,33 +24,7 @@ import { SERVICES } from './services';
|
|
|
24
24
|
import { routes } from './thematic-paths.routes';
|
|
25
25
|
|
|
26
26
|
@NgModule({
|
|
27
|
-
providers: [
|
|
28
|
-
ThematicPathsFacade,
|
|
29
|
-
...SERVICES,
|
|
30
|
-
{
|
|
31
|
-
provide: Masonry,
|
|
32
|
-
useFactory: () => {
|
|
33
|
-
if (typeof window !== 'undefined' && (window as any)['Masonry']) {
|
|
34
|
-
return (window as any)['Masonry'];
|
|
35
|
-
}
|
|
36
|
-
// Return a mock for SSR
|
|
37
|
-
return class {
|
|
38
|
-
constructor() {
|
|
39
|
-
// no-op
|
|
40
|
-
}
|
|
41
|
-
layout() {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
reloadItems() {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
destroy() {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
],
|
|
27
|
+
providers: [ThematicPathsFacade, ...SERVICES, provideBrowserMasonry()],
|
|
54
28
|
imports: [
|
|
55
29
|
FormsModule,
|
|
56
30
|
SmartSharedModule,
|
|
@@ -64,13 +64,22 @@ fi
|
|
|
64
64
|
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
65
65
|
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
66
66
|
# which the `new Masonry(` grep above cannot see.
|
|
67
|
-
if grep -
|
|
67
|
+
if grep -rqE "provide: Masonry|provideBrowserMasonry" "$LIBDIR" 2>/dev/null; then
|
|
68
68
|
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
69
69
|
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
70
70
|
else
|
|
71
71
|
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
72
72
|
fi
|
|
73
73
|
fi
|
|
74
|
+
# 2b. the provider is provideBrowserMasonry() — an anonymous stand-in class next to
|
|
75
|
+
# `provide: Masonry` reaches for window during SSR and is the bug the framework removed
|
|
76
|
+
STUB="$(grep -rlE "provide: Masonry" "$LIBDIR" 2>/dev/null | xargs grep -lE "return class|: class \{" 2>/dev/null || true)"
|
|
77
|
+
if [ -n "$STUB" ]; then
|
|
78
|
+
bad "hand-written Masonry stub (return class / : class {) — use provideBrowserMasonry() from @smartsoft001-mobilems/angular:"
|
|
79
|
+
printf ' %s\n' $STUB >&2
|
|
80
|
+
else
|
|
81
|
+
pass "no hand-written Masonry stub"
|
|
82
|
+
fi
|
|
74
83
|
# 3. a list page must render through the framework grid
|
|
75
84
|
LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
|
|
76
85
|
if [ -n "$LISTP" ]; then
|
|
@@ -103,6 +112,18 @@ else
|
|
|
103
112
|
pass "no filters component in this module"
|
|
104
113
|
fi
|
|
105
114
|
|
|
115
|
+
echo "== Portal guard =="
|
|
116
|
+
# Static guard for the recurring portal bugs (MOB-1889..1895); needs @smartsoft001-mobilems/angular with the bin.
|
|
117
|
+
if [ -x node_modules/.bin/mobilems-check-portal ]; then
|
|
118
|
+
if npx --no-install mobilems-check-portal; then
|
|
119
|
+
pass "mobilems-check-portal clean"
|
|
120
|
+
else
|
|
121
|
+
bad "mobilems-check-portal found recurring portal bugs — fix the FAIL lines above"
|
|
122
|
+
fi
|
|
123
|
+
else
|
|
124
|
+
echo " SKIP: mobilems-check-portal unavailable in this project (older framework line)"
|
|
125
|
+
fi
|
|
126
|
+
|
|
106
127
|
echo
|
|
107
128
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
108
129
|
exit "$fail"
|