@smartsoft001-mobilems/claude-plugins 2.88.0 → 2.90.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/README.md +27 -2
- package/package.json +1 -1
- package/plugins/mobilems/.claude-plugin/README.md +21 -2
- package/plugins/mobilems/.claude-plugin/plugin.json +1 -1
- package/plugins/mobilems/skills/mobilems-articles/SKILL.md +52 -20
- package/plugins/mobilems/skills/mobilems-articles/references/module-and-routes.md +24 -0
- package/plugins/mobilems/skills/mobilems-articles/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-articles/scripts/verify-module.sh +53 -0
- package/plugins/mobilems/skills/mobilems-devops-config/SKILL.md +141 -0
- package/plugins/mobilems/skills/mobilems-devops-config/references/docker.md +58 -0
- package/plugins/mobilems/skills/mobilems-devops-config/references/env-and-lint.md +59 -0
- package/plugins/mobilems/skills/mobilems-devops-config/references/github-hygiene.md +54 -0
- package/plugins/mobilems/skills/mobilems-devops-config/references/tooling.md +105 -0
- package/plugins/mobilems/skills/mobilems-devops-config/references/workflows.md +410 -0
- package/plugins/mobilems/skills/mobilems-devops-config/scripts/detect-foundation.sh +47 -0
- package/plugins/mobilems/skills/mobilems-devops-config/scripts/verify-devops.sh +96 -0
- package/plugins/mobilems/skills/mobilems-events/SKILL.md +27 -1
- package/plugins/mobilems/skills/mobilems-events/references/module-and-routes.md +24 -0
- package/plugins/mobilems/skills/mobilems-events/scripts/verify-module.sh +43 -0
- package/plugins/mobilems/skills/mobilems-object-collections/SKILL.md +46 -14
- package/plugins/mobilems/skills/mobilems-object-collections/references/lib-contents.md +11 -0
- package/plugins/mobilems/skills/mobilems-object-collections/references/module-and-routes.md +26 -0
- package/plugins/mobilems/skills/mobilems-object-collections/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-object-collections/scripts/verify-module.sh +54 -0
- package/plugins/mobilems/skills/mobilems-objects/SKILL.md +42 -10
- package/plugins/mobilems/skills/mobilems-objects/references/module-and-routes.md +24 -0
- package/plugins/mobilems/skills/mobilems-objects/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-objects/scripts/verify-module.sh +53 -0
- package/plugins/mobilems/skills/mobilems-partners/SKILL.md +30 -2
- package/plugins/mobilems/skills/mobilems-partners/references/module-and-routes.md +24 -0
- package/plugins/mobilems/skills/mobilems-partners/scripts/check-translations.js +90 -0
- package/plugins/mobilems/skills/mobilems-partners/scripts/verify-module.sh +53 -0
- package/plugins/mobilems/skills/mobilems-project-init/SKILL.md +68 -8
- package/plugins/mobilems/skills/mobilems-project-init/references/branding-reset.md +112 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/claude-config.md +7 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/config-providers.md +1 -1
- package/plugins/mobilems/skills/mobilems-project-init/references/qa-hardening.md +30 -30
- package/plugins/mobilems/skills/mobilems-project-init/references/shared-lib.md +42 -5
- package/plugins/mobilems/skills/mobilems-project-init/references/tailwind-styles.md +33 -23
- package/plugins/mobilems/skills/mobilems-project-init/scripts/verify-init.sh +62 -9
- package/plugins/mobilems/skills/mobilems-thematic-publications/SKILL.md +25 -1
- package/plugins/mobilems/skills/mobilems-thematic-publications/references/lib-contents.md +11 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/references/module-and-routes.md +26 -0
- package/plugins/mobilems/skills/mobilems-thematic-publications/scripts/verify-module.sh +43 -0
|
@@ -62,6 +62,30 @@ import { routes } from './objects.routes';
|
|
|
62
62
|
export class MuseumObjectsModule {}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
> **The `Masonry` provider exists only to feed the framework's `MasonryGridComponent`.** It is DI
|
|
66
|
+
> plumbing, not a grid — never call `new Masonry(...)`, keep a `MasonryInstance`, or re-implement the
|
|
67
|
+
> layout/`reloadItems` dance in a page component. A hand-rolled CSS-grid or column layout is the same
|
|
68
|
+
> defect. `scripts/verify-module.sh` fails the run on both.
|
|
69
|
+
|
|
70
|
+
The list page renders the grid like this — copy this shape, it is the required one:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<smart-mobilems-masonry-grid [list]="list()" [options]="{ gutter: 24 }">
|
|
74
|
+
<ng-template #masonryContentTpl>
|
|
75
|
+
<div class="masonry-grid-sizer"></div>
|
|
76
|
+
@for (item of list(); track item.id) {
|
|
77
|
+
<div class="masonry-item"><smart-objects-grid-item [item]="item" /></div>
|
|
78
|
+
}
|
|
79
|
+
</ng-template>
|
|
80
|
+
</smart-mobilems-masonry-grid>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`.masonry-grid-sizer` and `.masonry-item` are required — they are the component's default
|
|
84
|
+
`columnWidth` / `itemSelector`. The card (shared-components.md) is the only module-specific part. Inputs,
|
|
85
|
+
public methods and the SSR provider are documented in the framework catalog,
|
|
86
|
+
[`packages/shared/angular/README.md`](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/shared/angular/README.md#masonrygridcomponent)
|
|
87
|
+
(installed as `node_modules/@smartsoft001-mobilems/angular/README.md`).
|
|
88
|
+
|
|
65
89
|
## `objects.routes.ts`
|
|
66
90
|
|
|
67
91
|
```ts
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-translations.js "<GROUP GROUP …>" [reference-root]
|
|
4
|
+
*
|
|
5
|
+
* Run from the workspace root (verify-module.sh cd's there). Verifies the
|
|
6
|
+
* module's translation groups in apps/web/public/translations.json:
|
|
7
|
+
* 1. each group exists and is non-empty in BOTH pl and en,
|
|
8
|
+
* 2. every literal GROUP.* key referenced in libs/ or apps/web/src exists
|
|
9
|
+
* in both languages (dead references = defect),
|
|
10
|
+
* 3. with a reference root: every reference key of the group exists in the
|
|
11
|
+
* generated file — missing keys are listed (the module is incomplete).
|
|
12
|
+
* Prints PASS/FAIL lines in the verify-module.sh idiom; exit 1 on any FAIL.
|
|
13
|
+
*/
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
|
|
16
|
+
const [groupsArg, refRoot] = process.argv.slice(2);
|
|
17
|
+
const groups = (groupsArg || "").split(/\s+/).filter(Boolean);
|
|
18
|
+
if (!groups.length) { console.log(" FAIL: no translation groups given"); process.exit(1); }
|
|
19
|
+
|
|
20
|
+
const TRANS = "apps/web/public/translations.json";
|
|
21
|
+
let fail = 0;
|
|
22
|
+
const pass = (m) => console.log(` PASS: ${m}`);
|
|
23
|
+
const bad = (m) => { console.log(` FAIL: ${m}`); fail = 1; };
|
|
24
|
+
|
|
25
|
+
const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
|
|
26
|
+
v && typeof v === "object" ? flat(v, `${prefix}.${k}`) : [`${prefix}.${k}`]);
|
|
27
|
+
|
|
28
|
+
const readTrans = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).data.translations; } catch { return null; } };
|
|
29
|
+
|
|
30
|
+
const gen = readTrans(TRANS);
|
|
31
|
+
if (!gen) { bad(`cannot read ${TRANS}`); process.exit(1); }
|
|
32
|
+
|
|
33
|
+
// 1. groups present + non-empty in both languages
|
|
34
|
+
const genKeys = { pl: new Set(), en: new Set() };
|
|
35
|
+
for (const lang of ["pl", "en"]) {
|
|
36
|
+
for (const g of groups) {
|
|
37
|
+
const keys = flat(gen[lang]?.[g], g);
|
|
38
|
+
keys.forEach((k) => genKeys[lang].add(k));
|
|
39
|
+
if (keys.length) pass(`${g} present in ${lang} (${keys.length} keys)`);
|
|
40
|
+
else bad(`${g} missing or empty in ${lang}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. dead references — literal GROUP.* keys used in sources must exist.
|
|
45
|
+
// Dynamic tails (`GROUP.${x}`) don't match the literal regex, and a matched
|
|
46
|
+
// PREFIX that is a parent of existing keys counts as present.
|
|
47
|
+
const walk = (dir) => {
|
|
48
|
+
let out = [];
|
|
49
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
if (e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
51
|
+
const p = `${dir}/${e.name}`;
|
|
52
|
+
if (e.isDirectory()) out = out.concat(walk(p));
|
|
53
|
+
else if (/\.(ts|html)$/.test(e.name)) out.push(p);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
};
|
|
57
|
+
let src = "";
|
|
58
|
+
for (const d of ["libs", "apps/web/src"]) {
|
|
59
|
+
if (fs.existsSync(d)) src += walk(d).map((f) => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } }).join("\n");
|
|
60
|
+
}
|
|
61
|
+
const re = new RegExp(`\\b(?:${groups.join("|")})(?:\\.[A-Za-z0-9_]+)+`, "g");
|
|
62
|
+
const used = new Set(src.match(re) || []);
|
|
63
|
+
const dead = { pl: [], en: [] };
|
|
64
|
+
for (const key of used) {
|
|
65
|
+
for (const lang of ["pl", "en"]) {
|
|
66
|
+
const ks = genKeys[lang];
|
|
67
|
+
if (!ks.has(key) && ![...ks].some((k) => k.startsWith(`${key}.`))) dead[lang].push(key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const lang of ["pl", "en"]) {
|
|
71
|
+
if (dead[lang].length) bad(`${lang}: keys referenced in code but missing in ${TRANS}: ${dead[lang].slice(0, 8).join(", ")}${dead[lang].length > 8 ? ` (+${dead[lang].length - 8})` : ""}`);
|
|
72
|
+
else pass(`no dead ${lang} references (${used.size} literal keys checked)`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. completeness vs reference
|
|
76
|
+
if (refRoot) {
|
|
77
|
+
const ref = readTrans(`${refRoot}/apps/web/public/translations.json`);
|
|
78
|
+
if (!ref) bad(`cannot read reference translations under ${refRoot}`);
|
|
79
|
+
else {
|
|
80
|
+
for (const lang of ["pl", "en"]) {
|
|
81
|
+
const missing = groups.flatMap((g) => flat(ref[lang]?.[g], g)).filter((k) => !genKeys[lang].has(k));
|
|
82
|
+
if (missing.length) bad(`${lang}: ${missing.length} reference keys missing: ${missing.slice(0, 8).join(", ")}${missing.length > 8 ? " …" : ""}`);
|
|
83
|
+
else pass(`${lang}: all reference keys covered`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
console.log(" SKIP: no reference root given — completeness vs reference not checked");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
process.exit(fail);
|
|
@@ -8,6 +8,7 @@ set -eu
|
|
|
8
8
|
|
|
9
9
|
ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
|
|
10
10
|
PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
12
|
cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
|
|
12
13
|
|
|
13
14
|
fail=0
|
|
@@ -46,6 +47,58 @@ echo "== Deps =="
|
|
|
46
47
|
grep -q "@smartsoft001-mobilems/objects-domain" package.json && pass "objects-domain dep" || bad "objects-domain dep missing"
|
|
47
48
|
grep -q "online-3d-viewer" package.json && pass "online-3d-viewer dep" || bad "online-3d-viewer dep missing"
|
|
48
49
|
|
|
50
|
+
echo "== Framework first (packages/shared/angular/README.md) =="
|
|
51
|
+
LIBDIR="libs/museum-objects/shell/angular/src/lib"
|
|
52
|
+
# 1. masonry belongs to the framework's MasonryGridComponent — no module-level instances
|
|
53
|
+
MAS="$(grep -rlnE "new Masonry\(|MasonryInstance" libs apps/web/src 2>/dev/null || true)"
|
|
54
|
+
if [ -n "$MAS" ]; then
|
|
55
|
+
bad "hand-rolled masonry — render grids with <smart-mobilems-masonry-grid> (MasonryGridComponent):"
|
|
56
|
+
printf ' %s\n' $MAS >&2
|
|
57
|
+
else
|
|
58
|
+
pass "no hand-rolled masonry instances"
|
|
59
|
+
fi
|
|
60
|
+
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
61
|
+
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
62
|
+
# which the `new Masonry(` grep above cannot see.
|
|
63
|
+
if grep -rq "provide: Masonry" "$LIBDIR" 2>/dev/null; then
|
|
64
|
+
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
65
|
+
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
66
|
+
else
|
|
67
|
+
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
# 3. a list page must render through the framework grid
|
|
71
|
+
LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
|
|
72
|
+
if [ -n "$LISTP" ]; then
|
|
73
|
+
if grep -rq "smart-mobilems-masonry-grid" $LISTP 2>/dev/null; then
|
|
74
|
+
pass "list page renders via <smart-mobilems-masonry-grid>"
|
|
75
|
+
else
|
|
76
|
+
bad "list page does not render <smart-mobilems-masonry-grid> (README.md#masonrygridcomponent)"
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
# 4. filters must EXTEND the framework base class — `context.` alone proves nothing, and a
|
|
80
|
+
# differently-named dir must not silently skip the check
|
|
81
|
+
FILT="$(find "$LIBDIR/components" -maxdepth 1 -type d \( -name 'filters*' -o -name 'search*' -o -name 'facet*' \) 2>/dev/null || true)"
|
|
82
|
+
if [ -n "$FILT" ]; then
|
|
83
|
+
if grep -rqE "FiltersBaseComponent" $FILT 2>/dev/null; then
|
|
84
|
+
pass "filters use the framework filter API"
|
|
85
|
+
else
|
|
86
|
+
bad "filters do not extend FiltersBaseComponent — no ad-hoc query building:"
|
|
87
|
+
printf ' %s\n' $FILT >&2
|
|
88
|
+
fi
|
|
89
|
+
else
|
|
90
|
+
pass "no filters component in this module"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
echo "== Translations (REF_ROOT=${REF_ROOT:-unset}) =="
|
|
94
|
+
# Groups: present in pl+en, no dead references, complete vs the reference repo
|
|
95
|
+
# (export REF_ROOT=<reference-repo-root> to enable the completeness diff).
|
|
96
|
+
if node "$SCRIPT_DIR/check-translations.js" "OBJECTS DETAILS_PAGE GALLERY" "${REF_ROOT:-}"; then
|
|
97
|
+
pass "translation groups complete"
|
|
98
|
+
else
|
|
99
|
+
bad "translation gaps — fix the FAIL lines above"
|
|
100
|
+
fi
|
|
101
|
+
|
|
49
102
|
echo
|
|
50
103
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
51
104
|
exit "$fail"
|
|
@@ -23,7 +23,31 @@ REUSES the Articles shared module (like the thematic-paths module) but with its
|
|
|
23
23
|
Run `scripts/detect-init.sh <workspace-root>`. It confirms the MobilEMS foundation is present
|
|
24
24
|
(`SharedModule.forRoot` in `app.config.ts`, a populated shared-lib barrel). If not, tell the user to
|
|
25
25
|
run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`; reference uses
|
|
26
|
-
|
|
26
|
+
own) and the reference repo path (same one init used).
|
|
27
|
+
|
|
28
|
+
## Framework first — MANDATORY
|
|
29
|
+
|
|
30
|
+
Read these before writing ANY component, service, pipe, directive or helper, and reuse what they
|
|
31
|
+
list — re-implementing framework code is a defect, not a shortcut:
|
|
32
|
+
|
|
33
|
+
0. [**The Framework API index**](https://github.com/smartsoft001/mobilems-framework/blob/main/README.md#-framework-api) — the map of every package to
|
|
34
|
+
its catalog. Start here when you do not already know which package owns what.
|
|
35
|
+
1. The framework API catalog — `packages/shared/angular/README.md` in this repo, installed as
|
|
36
|
+
`node_modules/@smartsoft001-mobilems/angular/README.md` in a museum app ([browse](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/shared/angular/README.md)).
|
|
37
|
+
This is the path `scripts/verify-module.sh` names when it fails.
|
|
38
|
+
2. [`packages/articles/README.md`](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/articles/README.md) +
|
|
39
|
+
[`packages/objects/README.md`](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/objects/README.md) — the packages this module reuses
|
|
40
|
+
3. `libs/shared/angular/README.md` — the app's own shared lib (built by `mobilems-project-init`)
|
|
41
|
+
|
|
42
|
+
| Do not hand-roll | Use |
|
|
43
|
+
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| a masonry grid (`new Masonry(...)`, per-page masonry JS/CSS) | `<smart-mobilems-masonry-grid>` (`MasonryGridComponent`) with a `#masonryContentTpl`; the `Masonry` provider only feeds it |
|
|
45
|
+
| filter state / hand-built `query` arrays | `FiltersBaseComponent<MuseumPartner>` + its `FiltersContext` accessors |
|
|
46
|
+
| list/detail plumbing (facade wiring, id-from-route) | the `ArticlesListComponent` / `ArticlesItemComponent` base classes |
|
|
47
|
+
| media URLs, meta/OG tags, filter dictionaries, modals, WCAG | `FileUrlService`, `SeoService`/`MetaService`, `DictionaryService`, `ModalService`, `WcagService` |
|
|
48
|
+
|
|
49
|
+
Anything the framework genuinely lacks goes into `libs/shared/angular` **once** (and into its
|
|
50
|
+
README) — never a per-module copy. `scripts/verify-module.sh` fails the run on hand-rolled masonry.
|
|
27
51
|
|
|
28
52
|
## Steps
|
|
29
53
|
|
|
@@ -48,7 +72,11 @@ run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`;
|
|
|
48
72
|
`partnerzy` route to the `appRoutes` array in `apps/web/src/app/app.routes.ts` as a top-level
|
|
49
73
|
route, and a matching child under the `wyszukaj/:text` search route (partners appears in site
|
|
50
74
|
search).
|
|
51
|
-
4. **
|
|
75
|
+
4. **Translations** — copy the module's translation groups (`PARTNERS`) from the reference
|
|
76
|
+
repo's `apps/web/public/translations.json` into the app's file, for BOTH `pl` and `en`.
|
|
77
|
+
Complete group = every reference key present; every key referenced in the module's code
|
|
78
|
+
must exist in both languages — a raw `GROUP.key` rendered in the UI is a defect.
|
|
79
|
+
5. **Verify** — run `REF_ROOT=<reference-repo> scripts/verify-module.sh <workspace-root> <PREFIX>`: asserts the lib + entity
|
|
52
80
|
exist, both tsconfig paths are added, `ArticlesSharedModule.forFeature` is wired with
|
|
53
81
|
`entity: 'partners'` + `reducerFactory: getReducer`, the `+state/partners.reducer.ts` exports
|
|
54
82
|
`getReducer`, the `components/{grid-item,list-item}` cards are present in the partners lib, the
|
|
@@ -66,6 +66,30 @@ import { SERVICES } from './services';
|
|
|
66
66
|
export class MuseumPartnersModule {}
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
> **The `Masonry` provider exists only to feed the framework's `MasonryGridComponent`.** It is DI
|
|
70
|
+
> plumbing, not a grid — never call `new Masonry(...)`, keep a `MasonryInstance`, or re-implement the
|
|
71
|
+
> layout/`reloadItems` dance in a page component. A hand-rolled CSS-grid or column layout is the same
|
|
72
|
+
> defect. `scripts/verify-module.sh` fails the run on both.
|
|
73
|
+
|
|
74
|
+
The list page renders the grid like this — copy this shape, it is the required one:
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<smart-mobilems-masonry-grid [list]="list()" [options]="{ gutter: 24 }">
|
|
78
|
+
<ng-template #masonryContentTpl>
|
|
79
|
+
<div class="masonry-grid-sizer"></div>
|
|
80
|
+
@for (item of list(); track item.id) {
|
|
81
|
+
<div class="masonry-item"><app-partners-grid-item [item]="item" /></div>
|
|
82
|
+
}
|
|
83
|
+
</ng-template>
|
|
84
|
+
</smart-mobilems-masonry-grid>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`.masonry-grid-sizer` and `.masonry-item` are required — they are the component's default
|
|
88
|
+
`columnWidth` / `itemSelector`. The card (lib-contents.md) is the only module-specific part. Inputs,
|
|
89
|
+
public methods and the SSR provider are documented in the framework catalog,
|
|
90
|
+
[`packages/shared/angular/README.md`](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/shared/angular/README.md#masonrygridcomponent)
|
|
91
|
+
(installed as `node_modules/@smartsoft001-mobilems/angular/README.md`).
|
|
92
|
+
|
|
69
93
|
> Contrast with `mobilems-objects` (`ObjectsSharedModule.forFeature` over `MsObject`, no
|
|
70
94
|
> `reducerFactory`): here the module reuses `ArticlesSharedModule`, supplies `type: Article` with
|
|
71
95
|
> `reducerFactory: getReducer`, and there is deliberately **no `baseQuery`** in the config.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-translations.js "<GROUP GROUP …>" [reference-root]
|
|
4
|
+
*
|
|
5
|
+
* Run from the workspace root (verify-module.sh cd's there). Verifies the
|
|
6
|
+
* module's translation groups in apps/web/public/translations.json:
|
|
7
|
+
* 1. each group exists and is non-empty in BOTH pl and en,
|
|
8
|
+
* 2. every literal GROUP.* key referenced in libs/ or apps/web/src exists
|
|
9
|
+
* in both languages (dead references = defect),
|
|
10
|
+
* 3. with a reference root: every reference key of the group exists in the
|
|
11
|
+
* generated file — missing keys are listed (the module is incomplete).
|
|
12
|
+
* Prints PASS/FAIL lines in the verify-module.sh idiom; exit 1 on any FAIL.
|
|
13
|
+
*/
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
|
|
16
|
+
const [groupsArg, refRoot] = process.argv.slice(2);
|
|
17
|
+
const groups = (groupsArg || "").split(/\s+/).filter(Boolean);
|
|
18
|
+
if (!groups.length) { console.log(" FAIL: no translation groups given"); process.exit(1); }
|
|
19
|
+
|
|
20
|
+
const TRANS = "apps/web/public/translations.json";
|
|
21
|
+
let fail = 0;
|
|
22
|
+
const pass = (m) => console.log(` PASS: ${m}`);
|
|
23
|
+
const bad = (m) => { console.log(` FAIL: ${m}`); fail = 1; };
|
|
24
|
+
|
|
25
|
+
const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
|
|
26
|
+
v && typeof v === "object" ? flat(v, `${prefix}.${k}`) : [`${prefix}.${k}`]);
|
|
27
|
+
|
|
28
|
+
const readTrans = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).data.translations; } catch { return null; } };
|
|
29
|
+
|
|
30
|
+
const gen = readTrans(TRANS);
|
|
31
|
+
if (!gen) { bad(`cannot read ${TRANS}`); process.exit(1); }
|
|
32
|
+
|
|
33
|
+
// 1. groups present + non-empty in both languages
|
|
34
|
+
const genKeys = { pl: new Set(), en: new Set() };
|
|
35
|
+
for (const lang of ["pl", "en"]) {
|
|
36
|
+
for (const g of groups) {
|
|
37
|
+
const keys = flat(gen[lang]?.[g], g);
|
|
38
|
+
keys.forEach((k) => genKeys[lang].add(k));
|
|
39
|
+
if (keys.length) pass(`${g} present in ${lang} (${keys.length} keys)`);
|
|
40
|
+
else bad(`${g} missing or empty in ${lang}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. dead references — literal GROUP.* keys used in sources must exist.
|
|
45
|
+
// Dynamic tails (`GROUP.${x}`) don't match the literal regex, and a matched
|
|
46
|
+
// PREFIX that is a parent of existing keys counts as present.
|
|
47
|
+
const walk = (dir) => {
|
|
48
|
+
let out = [];
|
|
49
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
if (e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
51
|
+
const p = `${dir}/${e.name}`;
|
|
52
|
+
if (e.isDirectory()) out = out.concat(walk(p));
|
|
53
|
+
else if (/\.(ts|html)$/.test(e.name)) out.push(p);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
};
|
|
57
|
+
let src = "";
|
|
58
|
+
for (const d of ["libs", "apps/web/src"]) {
|
|
59
|
+
if (fs.existsSync(d)) src += walk(d).map((f) => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } }).join("\n");
|
|
60
|
+
}
|
|
61
|
+
const re = new RegExp(`\\b(?:${groups.join("|")})(?:\\.[A-Za-z0-9_]+)+`, "g");
|
|
62
|
+
const used = new Set(src.match(re) || []);
|
|
63
|
+
const dead = { pl: [], en: [] };
|
|
64
|
+
for (const key of used) {
|
|
65
|
+
for (const lang of ["pl", "en"]) {
|
|
66
|
+
const ks = genKeys[lang];
|
|
67
|
+
if (!ks.has(key) && ![...ks].some((k) => k.startsWith(`${key}.`))) dead[lang].push(key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const lang of ["pl", "en"]) {
|
|
71
|
+
if (dead[lang].length) bad(`${lang}: keys referenced in code but missing in ${TRANS}: ${dead[lang].slice(0, 8).join(", ")}${dead[lang].length > 8 ? ` (+${dead[lang].length - 8})` : ""}`);
|
|
72
|
+
else pass(`no dead ${lang} references (${used.size} literal keys checked)`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. completeness vs reference
|
|
76
|
+
if (refRoot) {
|
|
77
|
+
const ref = readTrans(`${refRoot}/apps/web/public/translations.json`);
|
|
78
|
+
if (!ref) bad(`cannot read reference translations under ${refRoot}`);
|
|
79
|
+
else {
|
|
80
|
+
for (const lang of ["pl", "en"]) {
|
|
81
|
+
const missing = groups.flatMap((g) => flat(ref[lang]?.[g], g)).filter((k) => !genKeys[lang].has(k));
|
|
82
|
+
if (missing.length) bad(`${lang}: ${missing.length} reference keys missing: ${missing.slice(0, 8).join(", ")}${missing.length > 8 ? " …" : ""}`);
|
|
83
|
+
else pass(`${lang}: all reference keys covered`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
console.log(" SKIP: no reference root given — completeness vs reference not checked");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
process.exit(fail);
|
|
@@ -10,6 +10,7 @@ set -eu
|
|
|
10
10
|
|
|
11
11
|
ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX>}"
|
|
12
12
|
PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX>}"
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
14
|
cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
|
|
14
15
|
|
|
15
16
|
fail=0
|
|
@@ -54,6 +55,58 @@ grep -q "@smartsoft001-mobilems/articles-shell-angular" package.json && pass "ar
|
|
|
54
55
|
grep -q "@smartsoft001-mobilems/articles-shell-dtos" package.json && pass "articles-shell-dtos dep" || bad "articles-shell-dtos dep missing"
|
|
55
56
|
grep -q "@smartsoft001-mobilems/objects-domain" package.json && pass "objects-domain dep" || bad "objects-domain dep missing"
|
|
56
57
|
|
|
58
|
+
echo "== Framework first (packages/shared/angular/README.md) =="
|
|
59
|
+
LIBDIR="libs/museum-partners/shell/angular/src/lib"
|
|
60
|
+
# 1. masonry belongs to the framework's MasonryGridComponent — no module-level instances
|
|
61
|
+
MAS="$(grep -rlnE "new Masonry\(|MasonryInstance" libs apps/web/src 2>/dev/null || true)"
|
|
62
|
+
if [ -n "$MAS" ]; then
|
|
63
|
+
bad "hand-rolled masonry — render grids with <smart-mobilems-masonry-grid> (MasonryGridComponent):"
|
|
64
|
+
printf ' %s\n' $MAS >&2
|
|
65
|
+
else
|
|
66
|
+
pass "no hand-rolled masonry instances"
|
|
67
|
+
fi
|
|
68
|
+
# 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
|
|
69
|
+
# rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
|
|
70
|
+
# which the `new Masonry(` grep above cannot see.
|
|
71
|
+
if grep -rq "provide: Masonry" "$LIBDIR" 2>/dev/null; then
|
|
72
|
+
if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
|
|
73
|
+
pass "grids render via <smart-mobilems-masonry-grid>"
|
|
74
|
+
else
|
|
75
|
+
bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
# 3. a list page must render through the framework grid
|
|
79
|
+
LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
|
|
80
|
+
if [ -n "$LISTP" ]; then
|
|
81
|
+
if grep -rq "smart-mobilems-masonry-grid" $LISTP 2>/dev/null; then
|
|
82
|
+
pass "list page renders via <smart-mobilems-masonry-grid>"
|
|
83
|
+
else
|
|
84
|
+
bad "list page does not render <smart-mobilems-masonry-grid> (README.md#masonrygridcomponent)"
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
# 4. filters must EXTEND the framework base class — `context.` alone proves nothing, and a
|
|
88
|
+
# differently-named dir must not silently skip the check
|
|
89
|
+
FILT="$(find "$LIBDIR/components" -maxdepth 1 -type d \( -name 'filters*' -o -name 'search*' -o -name 'facet*' \) 2>/dev/null || true)"
|
|
90
|
+
if [ -n "$FILT" ]; then
|
|
91
|
+
if grep -rqE "FiltersBaseComponent" $FILT 2>/dev/null; then
|
|
92
|
+
pass "filters use the framework filter API"
|
|
93
|
+
else
|
|
94
|
+
bad "filters do not extend FiltersBaseComponent — no ad-hoc query building:"
|
|
95
|
+
printf ' %s\n' $FILT >&2
|
|
96
|
+
fi
|
|
97
|
+
else
|
|
98
|
+
pass "no filters component in this module"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
echo "== Translations (REF_ROOT=${REF_ROOT:-unset}) =="
|
|
102
|
+
# Groups: present in pl+en, no dead references, complete vs the reference repo
|
|
103
|
+
# (export REF_ROOT=<reference-repo-root> to enable the completeness diff).
|
|
104
|
+
if node "$SCRIPT_DIR/check-translations.js" "PARTNERS" "${REF_ROOT:-}"; then
|
|
105
|
+
pass "translation groups complete"
|
|
106
|
+
else
|
|
107
|
+
bad "translation gaps — fix the FAIL lines above"
|
|
108
|
+
fi
|
|
109
|
+
|
|
57
110
|
echo
|
|
58
111
|
if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
|
|
59
112
|
exit "$fail"
|
|
@@ -29,6 +29,42 @@ and `mobilems-thematic-publications`, each of which adds its own library, shared
|
|
|
29
29
|
package, and routes. Keeping feature code out of the foundation is the layering rule that lets the
|
|
30
30
|
foundation build standalone (its shared library imports no feature package).
|
|
31
31
|
|
|
32
|
+
## Framework first — MANDATORY
|
|
33
|
+
|
|
34
|
+
The museum app builds **on** the framework, not beside it. Before writing or copying any component,
|
|
35
|
+
service, pipe, directive or helper, read:
|
|
36
|
+
|
|
37
|
+
0. [**The Framework API index**](https://github.com/smartsoft001/mobilems-framework/blob/main/README.md#-framework-api) —
|
|
38
|
+
the map of every package to its catalog. Start here when you do not already know which package
|
|
39
|
+
owns what.
|
|
40
|
+
1. The framework API catalog — `packages/shared/angular/README.md` in this repo, installed as
|
|
41
|
+
`node_modules/@smartsoft001-mobilems/angular/README.md` in a museum app
|
|
42
|
+
([browse](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/shared/angular/README.md)).
|
|
43
|
+
This is the path `scripts/verify-init.sh` names when it fails.
|
|
44
|
+
2. The [models catalog](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/shared/models/README.md).
|
|
45
|
+
|
|
46
|
+
Reuse what they list. Re-implementing framework code is a defect, not a shortcut.
|
|
47
|
+
|
|
48
|
+
This applies with force in **reference mode**: the reference repo predates parts of the framework, so
|
|
49
|
+
some of its shared components duplicate framework exports. When a reference file re-implements
|
|
50
|
+
something the catalog already provides, **use the framework export instead of copying the file**:
|
|
51
|
+
|
|
52
|
+
| Reference pattern to drop | Framework export to use |
|
|
53
|
+
| ---------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
54
|
+
| a hand-rolled masonry grid (`new Masonry(...)`, per-page `.masonry-item` JS) | `MasonryGridComponent` (`<smart-mobilems-masonry-grid>`) |
|
|
55
|
+
| bespoke filter state / hand-built `query` arrays | `FiltersBaseComponent<T>` + `FiltersContext<T>` |
|
|
56
|
+
| an ad-hoc media/thumbnail URL builder | `FileUrlService.get(...)` (`'cache' \| 'big' \| 'noCache'`) |
|
|
57
|
+
| local meta/OG tag writers | `SeoService` / `MetaService` |
|
|
58
|
+
| a local dictionary fetcher for filters | `DictionaryService` (lowercase keys) |
|
|
59
|
+
| a local WCAG/contrast state store | `WcagService` / `ContrastService` / `StyleService` |
|
|
60
|
+
| a bespoke modal host | `ModalService.open()` + `ModalRef` |
|
|
61
|
+
| a static-page loader | `PageStaticComponent` base class |
|
|
62
|
+
|
|
63
|
+
Everything the framework does **not** provide goes into the app's shared lib
|
|
64
|
+
(`libs/shared/angular`) exactly once — pipes, tools, directives, interceptors (see
|
|
65
|
+
`references/shared-lib.md`) — and every module reuses it from there. If you find a shared-lib item
|
|
66
|
+
that clearly belongs in the framework, name it in the final report so it can be upstreamed.
|
|
67
|
+
|
|
32
68
|
## Steps
|
|
33
69
|
|
|
34
70
|
### 0. Precondition — confirm a scaffold-nx-workspace base
|
|
@@ -42,7 +78,8 @@ run `/smart:scaffold-nx-workspace` first — do not try to create the Nx workspa
|
|
|
42
78
|
|
|
43
79
|
The base already derived/confirmed/threaded the import-alias prefix. Read it from
|
|
44
80
|
`tsconfig.base.json` `paths` — the key `@<PREFIX>/angular`. Hold `<PREFIX>`; thread it into every
|
|
45
|
-
copied file (the reference repo
|
|
81
|
+
copied file (replacing the reference repo's own alias with `@<PREFIX>` is the main rewrite; read it
|
|
82
|
+
from that repo's `tsconfig.base.json`).
|
|
46
83
|
|
|
47
84
|
### 2. Choose mode
|
|
48
85
|
|
|
@@ -100,11 +137,33 @@ the static pages (`home`, `page`, `contact`, `search`, `not-found`) + their reso
|
|
|
100
137
|
catch-all). Each module skill adds its own feature route to the `appRoutes` array when it runs
|
|
101
138
|
(`article-preview` is articles-coupled — `mobilems-articles` adds it, not the foundation).
|
|
102
139
|
|
|
103
|
-
### 9.
|
|
140
|
+
### 9. Branding + SEO reset — strip the reference museum's identity
|
|
141
|
+
|
|
142
|
+
**Mandatory in BOTH modes.** Step 4 already wrote the neutral token set, so a foundation-only app is
|
|
143
|
+
brand-clean by construction and this step only applies the client's spec if there is one. In
|
|
144
|
+
reference mode it does the real work: everything copied so far carries the _source project's_ brand
|
|
145
|
+
— its fonts, palette, logos and social image, its SEO/meta strings and its domain. Apply
|
|
146
|
+
`references/branding-reset.md` before QA hardening — it enumerates every carrier (styles, assets,
|
|
147
|
+
SEO, translations, configs) and how to reset it.
|
|
148
|
+
|
|
149
|
+
Ask the user (`AskUserQuestion`) once, up front:
|
|
150
|
+
|
|
151
|
+
- **Brand spec available?** → apply it (fonts, palette, logos, social image, site name/domain).
|
|
152
|
+
- **No spec (default)** → apply the **neutral token set**: full-latin-ext fonts (Playfair Display for
|
|
153
|
+
headings, Inter for body), a neutral grey/accent palette with every text pair ≥4.5:1, placeholder
|
|
154
|
+
logo + `no-image` + social image, generic site name/description keys.
|
|
155
|
+
|
|
156
|
+
Either way this is a **token-only** change, and there are exactly two files that carry the tokens:
|
|
157
|
+
`tailwind.config.js` (`theme.extend.colors` + `theme.extend.fontFamily`) and
|
|
158
|
+
`apps/web/src/styles.scss` (`@font-face` + the metric-override fallbacks). There is no CSS-custom-property
|
|
159
|
+
theme layer in this stack — do not go looking for one. Never touch layout classes, DOM structure,
|
|
160
|
+
component inputs, API field mapping, dictionary keys or route paths. The app must look re-skinned,
|
|
161
|
+
not re-built.
|
|
162
|
+
|
|
163
|
+
### 10. QA hardening — SEO, i18n, WCAG, branding
|
|
104
164
|
|
|
105
|
-
Apply `references/qa-hardening.md
|
|
106
|
-
|
|
107
|
-
leftovers and closes the recurring gaps QA always finds:
|
|
165
|
+
Apply `references/qa-hardening.md`. The copied foundation carries the _source project's_ identity,
|
|
166
|
+
so this step removes leftovers and closes the recurring SEO/i18n/WCAG gaps:
|
|
108
167
|
|
|
109
168
|
- dynamic `/sitemap.xml` + `/robots.txt` SSR endpoints driven by `SITE_URL` (never static copies —
|
|
110
169
|
they shipped another museum's domain twice),
|
|
@@ -116,12 +175,12 @@ leftovers and closes the recurring gaps QA always finds:
|
|
|
116
175
|
- warning-clean build & console (NG8107/NG8112/NG8113/NG0100/NG0913, Swiper loop, duplicate API
|
|
117
176
|
calls).
|
|
118
177
|
|
|
119
|
-
###
|
|
178
|
+
### 11. .claude extension
|
|
120
179
|
|
|
121
180
|
Apply `references/claude-config.md`: add `flow@mobilems` to `enabledPlugins`, grow the
|
|
122
181
|
MobilEMS-specific permissions / MCP allow-lists, and seed `tools/specs` + `tools/prompts` pointers.
|
|
123
182
|
|
|
124
|
-
###
|
|
183
|
+
### 12. Verify
|
|
125
184
|
|
|
126
185
|
Run `scripts/verify-init.sh <workspace-root> <PREFIX>`. It asserts the delta landed (Tailwind,
|
|
127
186
|
multimediaUrl, MobilEMS providers, populated shared-lib barrel), the QA-hardening basics (sitemap +
|
|
@@ -135,7 +194,8 @@ robots endpoints, favicon, non-empty heading font), AND the foundation is self-c
|
|
|
135
194
|
- **Reference mode (default):** copies the shared lib + pages from a real MobilEMS repo and threads
|
|
136
195
|
the prefix. This is the only mode that regenerates the full foundation, because the shared lib is
|
|
137
196
|
not in npm.
|
|
138
|
-
- **Foundation-only (fallback):** do steps 1, 3, 4, 5, 9 (deps, Tailwind, env,
|
|
197
|
+
- **Foundation-only (fallback):** do steps 1, 3, 4, 5, 9 and 11 (prefix, deps, Tailwind, env,
|
|
198
|
+
branding reset, .claude) and scaffold a
|
|
139
199
|
reduced `app.config.ts` that does NOT import local shared-lib code (omit the interceptors + the
|
|
140
200
|
`App` shell that need it). Leave a `MOBILEMS-FOUNDATION-TODO.md` listing what reference mode would
|
|
141
201
|
have copied (shared lib, pages, full app.config, app shell) so the user can supply a reference
|