@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 +1 -1
- package/plugins/mobilems/.claude-plugin/plugin.json +1 -1
- package/plugins/mobilems/skills/mobilems-project-init/references/app-shell.md +65 -27
- package/plugins/mobilems/skills/mobilems-project-init/references/config-providers.md +41 -142
- package/plugins/mobilems/skills/mobilems-project-init/references/shared-lib.md +15 -10
package/package.json
CHANGED
|
@@ -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`
|
|
7
|
+
## App root — `app.ts` / `app.html`
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
<
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
</
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`references/shared-lib.md`).
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`
|
|
41
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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 — `
|
|
52
|
-
`pagination`, `searchbar`, `loader`, `error-retry`, `scroll-top`, `
|
|
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`, `
|
|
108
|
-
| tools | `isInternalLink`, `normalizeUrl`, `stripHtml` (+ the shared `tools.ts` helpers)
|
|
109
|
-
| directives | `externalLink`
|
|
110
|
-
| interceptors | `
|
|
111
|
-
| services | `
|
|
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.
|