@smartsoft001-mobilems/claude-plugins 2.101.0 → 2.103.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartsoft001-mobilems/claude-plugins",
3
- "version": "2.101.0",
3
+ "version": "2.103.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "mobilems",
3
3
  "description": "MobilEMS-specific Claude Code skills (MuzaCMS / MobileMS indexer API)",
4
- "version": "2.101.0"
4
+ "version": "2.103.0"
5
5
  }
@@ -4,41 +4,79 @@ Build the root `App` component, the static pages, and the app routing by copying
4
4
  `apps/web/src/app` and threading `<PREFIX>`. The foundation owns the shell, the static pages, and the
5
5
  routing table's static routes; each feature module skill adds its own routes.
6
6
 
7
- ## App root — `app.ts` / `app.html` / `app.scss`
7
+ ## App root — `app.ts` / `app.html`
8
8
 
9
- Copy verbatim and thread `<PREFIX>`. The root component imports the `COMPONENTS` array +
10
- `SkipLinksComponent` from `@<PREFIX>/angular`, injects `ConfigsFacade` + `SeoService` (from
11
- `@smartsoft001-mobilems/angular`), `LocalStorageService` (shared lib) and `Router`; it inits the
12
- language, loads the global `Config` in `ngOnInit`, and focuses `#skip-links` on each `NavigationEnd`.
9
+ The shell lives in the framework (`AppComponent`, selector `smart-mobilems-app`). The root
10
+ component is a declaration only — thread `<PREFIX>`:
13
11
 
14
- > **QA gotcha (skip links):** the copied skip-link labels/targets describe the _reference_ museum's
15
- > sections — a project shipped skip links that still said "Światowid". Rewrite them for this app's
16
- > actual sections and localize them (PL/EN keys). See `references/qa-hardening.md` §1.
17
- > `app.html`:
12
+ ```ts
13
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
14
+ import { AppComponent } from '@smartsoft001-mobilems/angular';
15
+
16
+ import {
17
+ ConfirmationModalComponent,
18
+ FooterComponent,
19
+ HeaderComponent,
20
+ } from '@<PREFIX>/angular';
21
+
22
+ @Component({
23
+ imports: [
24
+ AppComponent,
25
+ HeaderComponent,
26
+ FooterComponent,
27
+ ConfirmationModalComponent,
28
+ ],
29
+ selector: 'app-root',
30
+ templateUrl: './app.html',
31
+ changeDetection: ChangeDetectionStrategy.Eager,
32
+ })
33
+ export class App {}
34
+ ```
35
+
36
+ `app.html`:
18
37
 
19
38
  ```html
20
- <app-skip-links></app-skip-links>
21
- <app-app [options]="options">
22
- <header tabindex="-1"><app-header></app-header></header>
23
- <footer><app-footer></app-footer></footer>
24
- </app-app>
39
+ <smart-mobilems-app>
40
+ <header tabindex="-1"><app-header /></header>
41
+ <footer><app-footer /></footer>
42
+ <app-confirmation-modal />
43
+ </smart-mobilems-app>
25
44
  ```
26
45
 
46
+ Language init, the global `Config` load, the `app-version` meta tag, skip links and
47
+ focus-after-navigation all come from `provideMobilemsApp()` in `app.config.ts`
48
+ (`references/config-providers.md`). Do **not** copy `skip-links`, `local-storage`,
49
+ `configs-cache`, `components/app` or `route-focus` from the reference app — they are framework
50
+ code now.
51
+
52
+ > **QA gotcha (skip links):** the skip-link targets are the `skipLinks.home` / `skipLinks.subpage`
53
+ > entries of `provideMobilemsApp()`. They must describe _this_ museum's sections and be localized
54
+ > (PL/EN keys) — a project shipped skip links that still said "Światowid". The framework adds the
55
+ > nav and footer links itself, so the header's main navigation MUST carry `id="main-nav" tabindex="-1"`
56
+ > and the footer `id="main-footer" tabindex="-1"`; every other `targetId` must exist on the page it is
57
+ > listed for — a missing id is a silent no-op. See `references/qa-hardening.md` §1.
58
+
27
59
  ## Static pages
28
60
 
29
- Copy `pages/{home,page,contact,search,not-found}` + the `home` and `page` resolvers, and thread
30
- `<PREFIX>`. These use only foundation shared components:
31
-
32
- - `home.component` uses the foundation carousels (`NewObjectsCarouselComponent`,
33
- `NewsCarouselComponent`, `PartnersCarouselComponent`) and the `IObject`/`IArticle`/`IPartner` types
34
- from the shared `models/` dir, plus the `DataService` home loaders — all foundation code (see
35
- `references/shared-lib.md`). The home page renders fully; its carousels populate from the API at
36
- runtime. **QA gotchas:** home sections must reuse the SAME shared card components as the module
37
- list pages (an ad-hoc home card was a High bug); Swiper `loop` only when there are enough slides
38
- for `slidesPerView` (else a console warning on every load); don't fire the same home API call
39
- multiple times on load (`object-of-the-week` went out 3× — share/replay it). See
40
- `references/qa-hardening.md` §6–7.
41
- - `search.component` renders results through a `<router-outlet>`; it has no feature import.
61
+ The framework ships the bases; the app keeps the templates (design) and the data:
62
+
63
+ - `home.component` extends `HomeBaseComponent`: `page` (CMS slug from `provideMobilemsApp({ homePage })`)
64
+ and `applySeo()` come from the base; the template is `<h1 class="smart-sr-only">` +
65
+ `<smart-mobilems-page-sections [page]="page" (pageLoaded)="applySeo($event)" />` and the component
66
+ provides `{ provide: PAGE_SECTION_COMPONENTS_TOKEN, useValue: PAGE_SECTION_COMPONENTS }` (the museum's
67
+ section variants, `references/shared-lib.md`). No hard-coded home layout.
68
+ - `page.component` extends `PageStaticComponent`: provides `PAGE_SECTION_COMPONENTS_TOKEN` and
69
+ `PageHeadingOwner`; the template renders `loading()`, `loadFailed()` (→ `app-error-retry (retry)="retry()"`),
70
+ the breadcrumb declaration (`pageRoute ?? '/strona/' + id + '/' + (title | smartSlug)`), a hidden `h1`
71
+ unless `sectionsHaveH1()`, and `<smart-mobilems-page-sections [sections]="pageData.sections ?? null">`
72
+ with `[innerHTML]="contentHtml()"` in the slot. Routes use `pageResolver` (`strona/:id`) and
73
+ `createSlugPageResolver('kontakt', { emptyTitleKey: 'CONTACT.pageTitle' })` with
74
+ `runGuardsAndResolvers: 'always'` and `data: { pageRoute: '/kontakt' }` for `/kontakt`.
75
+ - `search.component` extends `SearchBaseComponent<'obiekty' | …>` and declares only `tabDefinitions`
76
+ (`{ id, labelKey: 'SEARCH.tabs.*', count: (d) => d.objects }` per module the app ships); the template
77
+ renders the heading, `app-tabs` with `[(activeTabId)]="activeTab"` and a `<router-outlet>`.
78
+ - `not-found.component` extends `NotFoundComponent` and renders `<smart-mobilems-not-found-content
79
+ [fullHeight]="true" />` (plus the breadcrumb declaration with `url`).
42
80
 
43
81
  Run a feature-package grep over the pages you copy as a guard. **Do not copy `article-preview`** — the
44
82
  reference's `article-preview` page imports `@smartsoft001-mobilems/articles-domain` (it previews an
@@ -5,135 +5,72 @@ version). Thread `<PREFIX>`. `app.config.server.ts` and
5
5
  `app.routes.server.ts` stay as the base emitted them; `main.ts`, `main.server.ts` and `server.ts`
6
6
  are overwritten in step 5 (`references/server-env.md` §3).
7
7
 
8
- The imports from `@<PREFIX>/angular` (`LanguageInterceptor`, `PreviewAuthInterceptor`,
9
- `SsrTimeoutInterceptor`, `LocalStorageService`, `EnvironmentService`) come from the shared lib
10
- populated in step 7 — so in **foundation-only** mode, omit the three interceptor providers and the
11
- `@<PREFIX>/angular` interceptor imports (they don't exist yet).
8
+ The framework provides the whole provider set (`provideMobilemsCore()`: hydration, zoneless, router,
9
+ HttpClient + the MobilEMS interceptors, NgRx, Masonry, ngx-translate, `SharedModule.forRoot`) and the app
10
+ plumbing (`provideMobilemsApp()`: language, translations fetch, config load, SEO constants, skip links,
11
+ focus). Only `environment` / `EnvironmentService` come from the shared lib (step 7). Museum-specific
12
+ interceptors (e.g. a JWT one) go through `provideMobilemsCore({ interceptors: [...] })`.
12
13
 
13
14
  ```ts
14
- import {
15
- HTTP_INTERCEPTORS,
16
- HttpClient,
17
- provideHttpClient,
18
- withFetch,
19
- withInterceptorsFromDi,
20
- } from '@angular/common/http';
21
15
  import {
22
16
  ApplicationConfig,
23
- Injectable,
24
- provideBrowserGlobalErrorListeners,
25
- provideZonelessChangeDetection,
26
- importProvidersFrom,
27
17
  inject,
28
- provideEnvironmentInitializer,
29
18
  isDevMode,
30
- provideAppInitializer,
19
+ provideEnvironmentInitializer,
31
20
  } from '@angular/core';
32
- import {
33
- provideClientHydration,
34
- withEventReplay,
35
- withI18nSupport,
36
- } from '@angular/platform-browser';
37
- import { provideRouter, withInMemoryScrolling } from '@angular/router';
38
- import { EffectsModule, provideEffects } from '@ngrx/effects';
39
- import { provideStore, StoreModule } from '@ngrx/store';
40
21
  import { provideStoreDevtools } from '@ngrx/store-devtools';
41
- import {
42
- InterpolatableTranslationObject,
43
- TranslateModule,
44
- TranslateService,
45
- } from '@ngx-translate/core';
46
- import {
47
- AppService,
48
- ErrorService,
49
- NgrxSharedModule,
50
- StyleService,
51
- } from '@smartsoft001/angular';
52
22
  import {
53
23
  ISearchData,
24
+ provideMobilemsApp,
25
+ provideMobilemsCore,
54
26
  SEARCH_CONFIG_TOKEN,
55
- SharedModule,
56
27
  } from '@smartsoft001-mobilems/angular';
57
- import { MasonryModule } from '@thisissoon/angular-masonry';
58
- import { catchError, firstValueFrom, map, of, timeout } from 'rxjs';
59
28
 
60
- import {
61
- environment,
62
- EnvironmentService,
63
- LanguageInterceptor,
64
- LocalStorageService,
65
- PreviewAuthInterceptor,
66
- SsrTimeoutInterceptor,
67
- } from '@<PREFIX>/angular';
29
+ import { environment, EnvironmentService } from '@<PREFIX>/angular';
68
30
 
69
31
  import { appRoutes } from './app.routes';
70
32
 
71
- @Injectable()
72
- class NoOpAppService extends AppService {
73
- override initTitle(): void {
74
- // No-op: disable AppService.initTitle() which overrides
75
- // page titles set by components via SeoService.updateTitle()
76
- }
77
- }
78
-
79
33
  export const appConfig: ApplicationConfig = {
80
34
  providers: [
81
- provideClientHydration(withEventReplay(), withI18nSupport()),
82
- provideBrowserGlobalErrorListeners(),
83
- provideZonelessChangeDetection(),
84
- provideRouter(
85
- appRoutes,
86
- withInMemoryScrolling({ scrollPositionRestoration: 'top' }),
87
- ),
88
- provideHttpClient(withFetch(), withInterceptorsFromDi()),
89
- { provide: HTTP_INTERCEPTORS, useClass: LanguageInterceptor, multi: true },
90
- {
91
- provide: HTTP_INTERCEPTORS,
92
- useClass: PreviewAuthInterceptor,
93
- multi: true,
94
- },
95
- {
96
- provide: HTTP_INTERCEPTORS,
97
- useClass: SsrTimeoutInterceptor,
98
- multi: true,
99
- },
100
- importProvidersFrom(
101
- StoreModule.forRoot(
102
- {},
103
- {
104
- runtimeChecks: {
105
- strictStateImmutability: false,
106
- strictActionImmutability: false,
107
- },
108
- },
109
- ),
110
- MasonryModule.forRoot(),
111
- TranslateModule.forRoot(),
112
- EffectsModule.forRoot([]),
113
- NgrxSharedModule,
114
- SharedModule.forRoot({
35
+ provideMobilemsCore({
36
+ routes: appRoutes,
37
+ shared: {
115
38
  apiUrl: environment.apiUrl,
116
39
  multimediaUrl: environment.multimediaUrl,
117
40
  mainLogoPath: 'logo.png',
118
41
  styleVariables: {},
119
- }),
120
- ),
121
- { provide: StyleService, useClass: StyleService },
122
- { provide: AppService, useClass: NoOpAppService },
123
- {
124
- provide: ErrorService,
125
- useValue: {
126
- log: (err: any) => {
127
- console.error('Global error handler:', err);
128
- },
129
42
  },
130
- },
43
+ }),
44
+ provideMobilemsApp({
45
+ version: environment.version,
46
+ languages: { available: ['pl', 'en'], default: 'pl' },
47
+ skipLinks: {
48
+ home: [
49
+ { targetId: 'main-new-objects', translationKey: 'HOME.accessibility.skipToNewObjects' },
50
+ ],
51
+ subpage: [
52
+ { targetId: 'main-content', translationKey: 'HOME.accessibility.skipToContent' },
53
+ ],
54
+ },
55
+ // Produkcyjnie translacje są własnością CMS-a klienta (setting/theme),
56
+ // a lokalny plik służy developmentowi. USE_LOCAL_TRANSLATIONS wymusza
57
+ // plik także w buildzie produkcyjnym (zrzuty benchmarku, review makiety,
58
+ // demo bez backendu).
59
+ translations: {
60
+ url:
61
+ environment.production && !environment.useLocalTranslations
62
+ ? environment.apiUrl + 'setting/theme'
63
+ : '/api/translations',
64
+ },
65
+ seo: {
66
+ siteNameFallback: { pl: '<NAZWA MUZEUM>', en: '<MUSEUM NAME>' },
67
+ defaultOgImage: { url: '/gfx/og-default.jpg', width: 1200, height: 630 },
68
+ siteUrl: () => environment.siteUrl,
69
+ },
70
+ }),
131
71
  provideEnvironmentInitializer(() => {
132
- const envService = inject(EnvironmentService);
133
- envService.init();
72
+ inject(EnvironmentService).init();
134
73
  }),
135
- provideStore(),
136
- provideEffects(),
137
74
  provideStoreDevtools({
138
75
  maxAge: 25,
139
76
  logOnly: !isDevMode(),
@@ -154,44 +91,6 @@ export const appConfig: ApplicationConfig = {
154
91
  },
155
92
  },
156
93
  },
157
- provideAppInitializer(async () => {
158
- const httpClient = inject(HttpClient);
159
- const translateService = inject(TranslateService);
160
- const localStorageService = inject(LocalStorageService);
161
-
162
- // Initialize language before fetching translations so the
163
- // language interceptor sends the correct Accept-Language header.
164
- localStorageService.initLang();
165
-
166
- async function fetchTranslations() {
167
- const url = environment.production
168
- ? environment.apiUrl + 'setting/theme'
169
- : '/api/translations';
170
-
171
- const translations = (await firstValueFrom(
172
- httpClient.get<{ data: any }>(url).pipe(
173
- timeout(5000),
174
- map((response) => {
175
- return response?.data?.translations ?? { en: {}, pl: {} };
176
- }),
177
- catchError(() => of(null)),
178
- ),
179
- )) as {
180
- en: InterpolatableTranslationObject;
181
- pl: InterpolatableTranslationObject;
182
- } | null;
183
-
184
- if (!translations || !translations.en || !translations.pl) {
185
- console.warn('Translations are missing or malformed:', translations);
186
- return;
187
- }
188
-
189
- translateService.setTranslation('en', translations.en);
190
- translateService.setTranslation('pl', translations.pl);
191
- }
192
-
193
- await fetchTranslations();
194
- }),
195
94
  ],
196
95
  };
197
96
  ```
@@ -48,11 +48,12 @@ FEATURE-OWNED. A typical FEATURE-OWNED set (confirm with the classifier on your
48
48
  | `news-grid-item`, `news-list-item` | `mobilems-articles` (news) |
49
49
  | `widget/gallery-article` | `mobilems-articles` |
50
50
 
51
- Build everything else — `app`, `header`, `footer`, `page`, the `filter*` family, `breadcrumbs`,
52
- `pagination`, `searchbar`, `loader`, `error-retry`, `scroll-top`, `skip-links`, `wcag`, `gallery`,
51
+ Build everything else — `header`, `footer`, `page`, the `filter*` family, `breadcrumbs`,
52
+ `pagination`, `searchbar`, `loader`, `error-retry`, `scroll-top`, `wcag`, `gallery`,
53
53
  `map`, `slider`, `carousel`, the three carousels above, `category-dropdown`, `dropdown`, `menu-item`,
54
54
  `next-page`, `calendar`, `confirmation-modal`, `newsletter-*`, `image-info-panel`, `icons`, `tabs`,
55
- and the `widget` container with its foundation widgets.
55
+ and the `widget` container with its foundation widgets. The `app` shell and `skip-links` are
56
+ framework components (`smart-mobilems-app`, `smart-mobilems-skip-links`) — do not copy them.
56
57
 
57
58
  > Trust the script over this table for the **leaf** components (the grid/list cards and the
58
59
  > `widget/gallery-*` sub-widgets) — if the reference diverges, the script is authoritative there.
@@ -102,13 +103,17 @@ components duplicate exports that now exist; copying those is the single most co
102
103
  step. The app-level set the framework does **not** provide, and that the module skills expect to find
103
104
  here:
104
105
 
105
- | Kind | Expected members |
106
- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
107
- | pipes | `altText`, `capitalize`, `fileUrl`, `truncateHtml` |
108
- | tools | `isInternalLink`, `normalizeUrl`, `stripHtml` (+ the shared `tools.ts` helpers) |
109
- | directives | `externalLink` |
110
- | interceptors | `language`, `preview-auth`, `ssr-timeout` |
111
- | services | `museum-seo`, `filter-persistence`, `local-storage`, `orientation`, `confirmation-modal`, `contact`, `data`, `environment`, `wcag` |
106
+ | Kind | Expected members |
107
+ | ------------ | ----------------------------------------------------------------------------------------------------------------- |
108
+ | pipes | `altText`, `fileUrl`, `truncateHtml` (`capitalize` is a framework pipe) |
109
+ | tools | `isInternalLink`, `normalizeUrl`, `stripHtml` (+ the shared `tools.ts` helpers) |
110
+ | directives | `externalLink` |
111
+ | interceptors | none — `LanguageInterceptor`, `PreviewAuthInterceptor`, `SsrTimeoutInterceptor`, `DedupeRequestInterceptor` are framework classes |
112
+ | services | `filter-persistence`, `orientation`, `confirmation-modal`, `contact`, `data`, `environment`, `wcag` (`museum-seo` is the framework `MuseumSeoService`) |
113
+
114
+ `local-storage`, `configs-cache`, `museum-seo`, `not-found-content`, the interceptors and `decode-html-entities` are gone: language lives in the framework `TranslationService`
115
+ (`setLanguage()` for the header switcher, `currentLanguage` for `museum-seo`), the config cache in
116
+ `ConfigsFacade.init()`.
112
117
 
113
118
  Adjust to what the reference actually ships, but keep the rule: one implementation, in this lib,
114
119
  exported from the barrel, documented in the app's own README so the module skills can find it.