@smartsoft001-mobilems/claude-plugins 2.87.0 → 2.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/mobilems/.claude-plugin/plugin.json +1 -1
- package/plugins/mobilems/skills/mobilems-articles/SKILL.md +24 -0
- package/plugins/mobilems/skills/mobilems-object-collections/SKILL.md +25 -0
- package/plugins/mobilems/skills/mobilems-objects/SKILL.md +28 -0
- package/plugins/mobilems/skills/mobilems-project-init/SKILL.md +21 -4
- package/plugins/mobilems/skills/mobilems-project-init/references/app-shell.md +10 -2
- package/plugins/mobilems/skills/mobilems-project-init/references/qa-hardening.md +124 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/server-env.md +8 -0
- package/plugins/mobilems/skills/mobilems-project-init/references/tailwind-styles.md +6 -0
- package/plugins/mobilems/skills/mobilems-project-init/scripts/verify-init.sh +8 -0
package/package.json
CHANGED
|
@@ -75,6 +75,30 @@ and the reference repo path (same one init used).
|
|
|
75
75
|
library exists, the `forFeature` carries the chosen `entity` (and template, if given), the route is
|
|
76
76
|
registered, and the article card shared components are built and exported. Fix and re-run.
|
|
77
77
|
|
|
78
|
+
## Known QA pitfalls (from live deployments — fix while copying, not after)
|
|
79
|
+
|
|
80
|
+
Each of these was a real Linear bug on a museum project built with this skill (Ostrowiec MOB-962,
|
|
81
|
+
Tomaszów MOB-957/993). The cross-cutting rules live in `mobilems-project-init`
|
|
82
|
+
`references/qa-hardening.md`; these are the article-module-specific ones:
|
|
83
|
+
|
|
84
|
+
- **Author filter dictionary key is lowercase `authors`.** `dictionaryKey="authors"` routes to
|
|
85
|
+
`api/article/authors/...`; `"Author"` silently falls through to `api/dictionary/Author/...` →
|
|
86
|
+
HTTP 409 and an empty options list (one-character bug that shipped to QA).
|
|
87
|
+
- **alt from CMS titles must strip HTML.** The detail header image shipped with
|
|
88
|
+
`alt="<p>Tytuł</p>"` — pipe the title through `smartRemoveHtml` before binding it to `alt`.
|
|
89
|
+
- **Detail page structure:** an `h1` (article title — copied reference had only `h2`), breadcrumbs
|
|
90
|
+
(`/ Artykuły / {tytuł}`), and gallery image captions rendered as text, NOT `h3` (an `h1 → h3`
|
|
91
|
+
jump is a recurring axe-core finding).
|
|
92
|
+
- **List item field order per mockup:** date ABOVE the title (implementations kept shipping it at
|
|
93
|
+
the bottom), keywords as inline links styled per the design, and mockup-exact sort labels
|
|
94
|
+
("Sortowanie: Od najnowszych", not "Sortuj: Data publikacji - od najnowszych").
|
|
95
|
+
- **List spacing:** in list view the items need a real gap (`gap-4` fixed the "too close together"
|
|
96
|
+
QA bug).
|
|
97
|
+
- **Search debounce 250 ms** (`@debounce(250)`), total count + shared `NextPageComponent` on the
|
|
98
|
+
list — consistent with objects/collections.
|
|
99
|
+
- **Warning-clean templates:** remove needless `?.` on non-nullable signals (NG8107) and unused
|
|
100
|
+
`@let` declarations (NG8112) after copying.
|
|
101
|
+
|
|
78
102
|
## Notes
|
|
79
103
|
|
|
80
104
|
- One run builds one named article module. Run the skill again — with a different name + template — for
|
|
@@ -45,6 +45,31 @@ run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`;
|
|
|
45
45
|
(top-level + search child), the `collections-grid-item` component is built and exported, and the
|
|
46
46
|
three public-collections deps are present. Fix and re-run.
|
|
47
47
|
|
|
48
|
+
## Known QA pitfalls (from live deployments — fix while copying, not after)
|
|
49
|
+
|
|
50
|
+
Each of these was a real Linear bug on a museum project built with this skill (Ostrowiec MOB-961/994,
|
|
51
|
+
Tomaszów MOB-956). The cross-cutting rules live in `mobilems-project-init`
|
|
52
|
+
`references/qa-hardening.md`; these are the collections-specific ones:
|
|
53
|
+
|
|
54
|
+
- **One card component, reused everywhere.** The home page and the collection detail must render the
|
|
55
|
+
SAME shared `collections-grid-item` / object card components as the list views — a second ad-hoc
|
|
56
|
+
card on the home page was a High-priority bug (MOB-994). Never fork a card per page.
|
|
57
|
+
- **No hardcoded UI strings.** The detail heading was shipped as a literal `Zbiory {{ title }}` and
|
|
58
|
+
stayed Polish in EN — every static label goes through a translation key (PL + EN in
|
|
59
|
+
`translations.json`). The list page needs a VISIBLE translated heading (not only `sr-only`), and
|
|
60
|
+
exactly one `h1` per page.
|
|
61
|
+
- **Card image fallback:** when a collection thumbnail fails to load, show the app's `no-image`
|
|
62
|
+
placeholder instead of a broken image.
|
|
63
|
+
- **Hover:** underline the card **title** on hover (not the "Zobacz więcej" link) — same pattern as
|
|
64
|
+
objects.
|
|
65
|
+
- **Search debounce 250 ms** in the filters (`@debounce(250)`), total count + shared
|
|
66
|
+
`NextPageComponent` on the list — consistent with objects/news.
|
|
67
|
+
- **Keyword/cycle chips wrap on 360 px** — chips caused page-level horizontal scroll on mobile.
|
|
68
|
+
- **Heading font must cover Polish diacritics** — collection card titles rendered in a fallback
|
|
69
|
+
font because the configured serif (Bacasime Antique) lacks `ą ć ę ł ń ś ź ż`; verify the Tailwind
|
|
70
|
+
heading `fontFamily` is non-empty and latin-ext complete (see qa-hardening).
|
|
71
|
+
- **Warning-clean templates:** remove needless `?.` on non-nullable signals (NG8107) after copying.
|
|
72
|
+
|
|
48
73
|
## Notes
|
|
49
74
|
|
|
50
75
|
- This skill builds only the collections module + its shared component + its route. It never adds
|
|
@@ -45,6 +45,34 @@ run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`;
|
|
|
45
45
|
and exported, the widget map registers the object gallery widget, and `ISpecificObject` is present.
|
|
46
46
|
Fix and re-run.
|
|
47
47
|
|
|
48
|
+
## Known QA pitfalls (from live deployments — fix while copying, not after)
|
|
49
|
+
|
|
50
|
+
Each of these was a real Linear bug on a museum project built with this skill (Ostrowiec MOB-960/991,
|
|
51
|
+
Tomaszów MOB-949/993). The cross-cutting rules live in `mobilems-project-init`
|
|
52
|
+
`references/qa-hardening.md`; these are the objects-specific ones:
|
|
53
|
+
|
|
54
|
+
- **Search debounce.** The panel full-text search (`app-objects-searchbar variant="panel"`) must
|
|
55
|
+
re-emit through a handler with `@debounce(250)` (`lodash-decorators`) — without it every keystroke
|
|
56
|
+
hits the API. Collections and news filters already do this; keep the modules consistent.
|
|
57
|
+
- **List page:** show the total result count (`facade.totalCount()`) and add the shared
|
|
58
|
+
`NextPageComponent` at the bottom (same `selectedPage`/`lastPageIndex` pattern as the news list).
|
|
59
|
+
- **Map tab coordinates.** Read coordinates through ONE helper (`getObjectCoordinates()`) used by
|
|
60
|
+
both the tab-visibility predicate and the map render: `archeoMap.latitude/longitude` first, then
|
|
61
|
+
fall back to the first valid `createPlaces[]` entry; accept **both `lon` and `long`** (upstream
|
|
62
|
+
`MsObjectPlace` says `long`, the real API sends `lon`); validate with `Number.isFinite` (lat `0`
|
|
63
|
+
is valid; `Number('') === 0` — reject empty strings explicitly). Duplicating the predicate in two
|
|
64
|
+
components caused a "map never renders" bug.
|
|
65
|
+
- **Field labels match the client's field spec everywhere** — list card, detail and filters must use
|
|
66
|
+
the same names (QA rejected "Materiał" vs "Tworzywo", "Miejsca:" vs "Miejsce powstania").
|
|
67
|
+
- **Keywords section** maps the API `tags[]` field (not `keywords[]`) and renders clickable links
|
|
68
|
+
that filter the list.
|
|
69
|
+
- **Warning-clean templates:** after copying, remove needless `?.` on non-nullable signals (NG8107)
|
|
70
|
+
and drop unused entries from `imports: []` (NG8113 — the tab container often imports
|
|
71
|
+
Model3d/Map/VirtualWalk tabs its template no longer references). Masonry grid items must not set
|
|
72
|
+
`width` after change detection (NG0100).
|
|
73
|
+
- **A11y:** exactly one `h1` (detail = object title); cards exposed as `role="link"` must activate
|
|
74
|
+
on Enter/Space; focused/hovered cards change label colours together with the rest of the content.
|
|
75
|
+
|
|
48
76
|
## Notes
|
|
49
77
|
|
|
50
78
|
- This skill builds only the objects module + its shared components + its route. It never adds other
|
|
@@ -100,16 +100,33 @@ the static pages (`home`, `page`, `contact`, `search`, `not-found`) + their reso
|
|
|
100
100
|
catch-all). Each module skill adds its own feature route to the `appRoutes` array when it runs
|
|
101
101
|
(`article-preview` is articles-coupled — `mobilems-articles` adds it, not the foundation).
|
|
102
102
|
|
|
103
|
-
### 9.
|
|
103
|
+
### 9. QA hardening — SEO, i18n, WCAG, branding
|
|
104
|
+
|
|
105
|
+
Apply `references/qa-hardening.md` — a checklist distilled from real QA bug reports on live museum
|
|
106
|
+
deployments. The copied foundation carries the _reference museum's_ identity, so this step removes
|
|
107
|
+
leftovers and closes the recurring gaps QA always finds:
|
|
108
|
+
|
|
109
|
+
- dynamic `/sitemap.xml` + `/robots.txt` SSR endpoints driven by `SITE_URL` (never static copies —
|
|
110
|
+
they shipped another museum's domain twice),
|
|
111
|
+
- og/twitter images + URLs on the target domain, favicon set, skip links rewritten for this app,
|
|
112
|
+
- meta description + OpenGraph on home in both languages; `og:locale` follows the active language,
|
|
113
|
+
- default language = Polish, `<html lang>` synced on switch, zero hardcoded UI strings,
|
|
114
|
+
- WCAG: one `h1`, no skipped heading levels, contrast ≥4.5:1 (reference greys fail), heading font
|
|
115
|
+
with full Polish diacritics,
|
|
116
|
+
- warning-clean build & console (NG8107/NG8112/NG8113/NG0100/NG0913, Swiper loop, duplicate API
|
|
117
|
+
calls).
|
|
118
|
+
|
|
119
|
+
### 10. .claude extension
|
|
104
120
|
|
|
105
121
|
Apply `references/claude-config.md`: add `flow@mobilems` to `enabledPlugins`, grow the
|
|
106
122
|
MobilEMS-specific permissions / MCP allow-lists, and seed `tools/specs` + `tools/prompts` pointers.
|
|
107
123
|
|
|
108
|
-
###
|
|
124
|
+
### 11. Verify
|
|
109
125
|
|
|
110
126
|
Run `scripts/verify-init.sh <workspace-root> <PREFIX>`. It asserts the delta landed (Tailwind,
|
|
111
|
-
multimediaUrl, MobilEMS providers, populated shared-lib barrel)
|
|
112
|
-
|
|
127
|
+
multimediaUrl, MobilEMS providers, populated shared-lib barrel), the QA-hardening basics (sitemap +
|
|
128
|
+
robots endpoints, favicon, non-empty heading font), AND the foundation is self-contained (no
|
|
129
|
+
`libs/museum-*`, no feature routes, and the build-safety invariant — no file under
|
|
113
130
|
`libs/shared/angular/src` imports a feature package). Fix and re-run until it passes. Gate
|
|
114
131
|
`npm install` behind an explicit ask — skip it during self-validation/eval.
|
|
115
132
|
|
|
@@ -10,7 +10,11 @@ Copy verbatim and thread `<PREFIX>`. The root component imports the `COMPONENTS`
|
|
|
10
10
|
`SkipLinksComponent` from `@<PREFIX>/angular`, injects `ConfigsFacade` + `SeoService` (from
|
|
11
11
|
`@smartsoft001-mobilems/angular`), `LocalStorageService` (shared lib) and `Router`; it inits the
|
|
12
12
|
language, loads the global `Config` in `ngOnInit`, and focuses `#skip-links` on each `NavigationEnd`.
|
|
13
|
-
|
|
13
|
+
|
|
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`:
|
|
14
18
|
|
|
15
19
|
```html
|
|
16
20
|
<app-skip-links></app-skip-links>
|
|
@@ -29,7 +33,11 @@ Copy `pages/{home,page,contact,search,not-found}` + the `home` and `page` resolv
|
|
|
29
33
|
`NewsCarouselComponent`, `PartnersCarouselComponent`) and the `IObject`/`IArticle`/`IPartner` types
|
|
30
34
|
from the shared `models/` dir, plus the `DataService` home loaders — all foundation code (see
|
|
31
35
|
`references/shared-lib.md`). The home page renders fully; its carousels populate from the API at
|
|
32
|
-
runtime.
|
|
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.
|
|
33
41
|
- `search.component` renders results through a `<router-outlet>`; it has no feature import.
|
|
34
42
|
|
|
35
43
|
Run a feature-package grep over the pages you copy as a guard. **Do not copy `article-preview`** — the
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# QA hardening — lessons from live museum deployments
|
|
2
|
+
|
|
3
|
+
Every item below is a **real defect reported in QA on production museum projects** (Linear: Muzeum
|
|
4
|
+
Ostrowiec MOB-938…MOB-995, Muzeum Tomaszów Lubelski MOB-948…MOB-996). The foundation is copied from
|
|
5
|
+
a reference repo, so the default failure mode is **reference-project leftovers** plus a handful of
|
|
6
|
+
recurring SEO/i18n/WCAG gaps. Work through this checklist when applying the delta — each item is
|
|
7
|
+
cheap to do during init and expensive to chase later in QA.
|
|
8
|
+
|
|
9
|
+
## 1. No reference-project leftovers (branding)
|
|
10
|
+
|
|
11
|
+
The reference repo belongs to a _different museum_. Everything carrying that museum's identity must
|
|
12
|
+
be replaced or generated for the target project:
|
|
13
|
+
|
|
14
|
+
- **`sitemap.xml` + `robots.txt`** — never copy static files (QA found `zbiory.muzeum-orkana.pl`
|
|
15
|
+
URLs shipped to another museum). Implement them as **dynamic SSR endpoints** driven by the
|
|
16
|
+
`SITE_URL` env var — see section 2.
|
|
17
|
+
- **`og:image` / `og:image:secure_url` / `twitter:image` / `og:url`** — must point at the target
|
|
18
|
+
museum's domain, not the reference deploy (QA found `mobilems-muzeum-swiatowid-qa.smartsoft.biz.pl/gfx/social-logo.jpg`).
|
|
19
|
+
Use `SITE_URL`/`siteUrl` to build absolute URLs; ship a local `gfx/social-logo.jpg` for this app.
|
|
20
|
+
- **Favicon** — the base often has none. Ship the full set: `favicon.ico`, 32×32 + 192×192 PNG,
|
|
21
|
+
`apple-touch-icon` (two projects opened QA bugs "Brak ikony").
|
|
22
|
+
- **Skip links** — keep them (they are required), but rewrite their labels/targets for THIS app's
|
|
23
|
+
sections; copied skip links still said "Światowid". Localize them (PL/EN translation keys).
|
|
24
|
+
- **Footer / contact data** — target museum's phone, e-mail, socials; links come from the CMS API,
|
|
25
|
+
not hardcode.
|
|
26
|
+
- Local static assets (funding logos etc.) must be served near their rendered size — oversized
|
|
27
|
+
copies trigger Angular `NG0913` warnings and hurt LCP.
|
|
28
|
+
|
|
29
|
+
## 2. Dynamic sitemap + robots endpoints (server.ts)
|
|
30
|
+
|
|
31
|
+
Pattern proven in MOB-948 — put the logic in **pure, testable functions** in
|
|
32
|
+
`apps/web/src/seo/seo-endpoints.ts` (`resolveSiteUrl`, `buildSitemapXml`, `buildRobotsTxt`), and
|
|
33
|
+
wire thin routes in `server.ts`:
|
|
34
|
+
|
|
35
|
+
- Register `GET /sitemap.xml` and `GET /robots.txt` **after** `/api/translations` but **before**
|
|
36
|
+
`express.static` and the `'/**'` SSR catch-all — otherwise SSR answers with HTML and status 200
|
|
37
|
+
and the endpoints "work" only apparently.
|
|
38
|
+
- Normalize `SITE_URL`: strip trailing `/`, prepend `https://` when the schema is missing; fall back
|
|
39
|
+
to the request host when the env var is unset.
|
|
40
|
+
- Headers: `application/xml; charset=utf-8` / `text/plain; charset=utf-8`,
|
|
41
|
+
`Cache-Control: public, max-age=3600`.
|
|
42
|
+
- Sitemap content: home + the module list pages (`/strona-glowna`, `/obiekty`, `/kolekcje`, …). Do
|
|
43
|
+
**not** include CMS static pages (`/strona/:id`) — their ids are CMS-assigned and go stale.
|
|
44
|
+
- `robots.txt` points `Sitemap:` at the same `SITE_URL`.
|
|
45
|
+
|
|
46
|
+
## 3. SEO meta (home + every routed page)
|
|
47
|
+
|
|
48
|
+
- `<meta name="description">` + complete OpenGraph (`og:title`, `og:description`, `og:image`,
|
|
49
|
+
`og:url`) + Twitter Card on the home page, in **both** languages, re-set on language switch.
|
|
50
|
+
- `og:locale` must follow the active language — a hardcoded `pl_PL` was a QA bug. In
|
|
51
|
+
`museum-seo.service.ts` use a locale map (`pl → pl_PL`, `en → en_GB`) read from
|
|
52
|
+
`translateService.currentLang` (fallback via `defaultLang` — `currentLang` can be `undefined`
|
|
53
|
+
during SSR), and emit one `og:locale:alternate`.
|
|
54
|
+
|
|
55
|
+
## 4. i18n invariants
|
|
56
|
+
|
|
57
|
+
- **Default language is Polish.** A Polish museum site rendering English on first visit while
|
|
58
|
+
`<html lang="pl">` is a WCAG 3.1.1 violation and was a reported bug.
|
|
59
|
+
- `<html lang>` must track the actually rendered language after every switch; `<title>`, meta
|
|
60
|
+
description and `og:title` retranslate with it.
|
|
61
|
+
- **No hardcoded UI strings in templates.** QA repeatedly caught literals like `Zbiory {{ title }}`
|
|
62
|
+
untranslated in EN. Every static label goes through a translation key with both PL and EN values
|
|
63
|
+
in `translations.json`.
|
|
64
|
+
- Dictionary keys for filter dictionaries are **lowercase** conventions of the framework:
|
|
65
|
+
`dictionaryKey="authors"` routes to `api/article/authors/...`; `"Author"` silently falls through
|
|
66
|
+
to `api/dictionary/Author/...` → HTTP 409 and an empty options list.
|
|
67
|
+
|
|
68
|
+
## 5. WCAG invariants
|
|
69
|
+
|
|
70
|
+
- **Contrast:** the reference grey `#9ca3af` on white is 2.53:1 — fails AA and was flagged on menus
|
|
71
|
+
and breadcrumbs in two projects. Use ≥4.5:1 for text: `#6f6f6f` ≈ 5.0:1, `#4b5563` ≈ 7.6:1.
|
|
72
|
+
Figma mockups themselves sometimes specify failing colours — match the design _intent_ with the
|
|
73
|
+
nearest compliant shade instead of copying the hex blindly, and note the deviation.
|
|
74
|
+
- **Headings:** exactly one `h1` per page; no skipped levels (an `h1 → h3` jump was a recurring
|
|
75
|
+
axe-core finding). Detail pages get an `h1` (the item title), image captions are NOT headings.
|
|
76
|
+
- **alt text:** when the alt comes from a CMS title, strip HTML first (`smartRemoveHtml`) —
|
|
77
|
+
`alt="<p>Tytuł</p>"` shipped to QA.
|
|
78
|
+
- **Keyboard:** any card implemented as `role="link"`/`tabindex=0` must activate on Enter/Space
|
|
79
|
+
(or better, be a real `<a href>`). Focus must be visible; labels should react to focus/hover the
|
|
80
|
+
same way the rest of the card content does.
|
|
81
|
+
- **Fonts must cover Polish diacritics.** Bacasime Antique, Prata and Italiana lack 16/18 of
|
|
82
|
+
`ą ć ę ł ń ś ź ż` (+ uppercase) — titles rendered in a fallback font and QA rejected it. Verified
|
|
83
|
+
full-latin-ext replacements: **Gilda Display** (closest to Bacasime) or **Playfair Display**.
|
|
84
|
+
Never leave a `tailwind.config.js` `fontFamily` entry as an empty array — it silently falls back
|
|
85
|
+
to system sans.
|
|
86
|
+
|
|
87
|
+
## 6. Console hygiene (build + runtime must be warning-clean)
|
|
88
|
+
|
|
89
|
+
QA files bugs for compiler warnings, so treat them as errors after copying code:
|
|
90
|
+
|
|
91
|
+
- `NG8107` — remove `?.` on receivers whose type excludes null/undefined (typical after copying
|
|
92
|
+
templates: `list()?.length`, `filter()?.searchText`).
|
|
93
|
+
- `NG8112` — remove unused `@let` declarations.
|
|
94
|
+
- `NG8113` — remove components from `imports: []` that the template doesn't use (copied tab
|
|
95
|
+
containers often import Model3d/Map/VirtualWalk tabs conditionally rendered away).
|
|
96
|
+
- `NG0100` (`ExpressionChangedAfterItHasBeenCheckedError`) — masonry/grid items must not set their
|
|
97
|
+
`width` binding after change detection completes.
|
|
98
|
+
- **Never log API payloads to the console** — QA flagged leaked responses.
|
|
99
|
+
- Swiper: enable `loop` only when there are enough slides for the configured
|
|
100
|
+
`slidesPerView`/`slidesPerGroup`, otherwise Swiper logs a loop warning on every load.
|
|
101
|
+
- Avoid duplicate API calls on page load (home fired `object-of-the-week` 3×) — share/replay the
|
|
102
|
+
request in the service.
|
|
103
|
+
|
|
104
|
+
## 7. Cross-module UI consistency
|
|
105
|
+
|
|
106
|
+
These conventions were enforced by QA across modules — bake them in from the start:
|
|
107
|
+
|
|
108
|
+
- **One card component per entity, reused everywhere.** The home page, module lists and search
|
|
109
|
+
results must render the same shared `*-grid-item` / `*-list-item` components — a second ad-hoc
|
|
110
|
+
card implementation on the home page was a High-priority bug (MOB-994).
|
|
111
|
+
- **Full-text search inputs debounce 250 ms** (`@debounce(250)` from `lodash-decorators`) — in every
|
|
112
|
+
module's filters (objects, collections, articles/news).
|
|
113
|
+
- **Every list page shows the total result count** (`facade.totalCount()`), independent of
|
|
114
|
+
pagination (MOB-995/996).
|
|
115
|
+
- **"Next page" button** at the bottom of each list follows one shared pattern
|
|
116
|
+
(`NextPageComponent`: `selectedPage`/`lastPageIndex` from `totalCount()` + limit, scroll-to-top on
|
|
117
|
+
advance, hidden on the last page).
|
|
118
|
+
- **Breadcrumbs:** everything except the active (last) item greyed — separators `#8e8e8e`,
|
|
119
|
+
intermediate links a WCAG-compliant grey (`#4b5563`/`#6f6f6f`), active item dark/semibold.
|
|
120
|
+
- **Card hover:** underline the card **title** only (not the "Zobacz więcej" link) — pattern
|
|
121
|
+
aligned across objects/collections.
|
|
122
|
+
- **Image fallback:** cards show the app's `no-image` placeholder when a thumbnail fails to load.
|
|
123
|
+
- Keyword/tag chips must wrap on 360 px wide screens — no page-level horizontal scroll at
|
|
124
|
+
360/768/1024/1440.
|
|
@@ -87,3 +87,11 @@ app.get('/api/translations', (req, res) => {
|
|
|
87
87
|
The `app.config.ts` `provideAppInitializer` hits `/api/translations` in dev and `apiUrl + 'setting/theme'`
|
|
88
88
|
in prod (see `references/config-providers.md`). Optionally seed an empty
|
|
89
89
|
`apps/web/public/translations.json` (`{"en":{},"pl":{}}`) so dev SSR doesn't 404.
|
|
90
|
+
|
|
91
|
+
## 4. `/sitemap.xml` + `/robots.txt` — dynamic endpoints (required)
|
|
92
|
+
|
|
93
|
+
Never ship static `sitemap.xml`/`robots.txt` copied from the reference — two projects went to QA
|
|
94
|
+
with another museum's domain in them. Build them as SSR endpoints from `SITE_URL`, with the logic in
|
|
95
|
+
pure functions (`apps/web/src/seo/seo-endpoints.ts`) and thin routes in `server.ts`, registered
|
|
96
|
+
after `/api/translations` and **before** `express.static` + the `'/**'` catch-all. Full pattern
|
|
97
|
+
(normalization, headers, content rules): `references/qa-hardening.md` §2.
|
|
@@ -62,6 +62,12 @@ module.exports = {
|
|
|
62
62
|
> The `content` globs already cover `libs/**`, so feature libs added later are picked up — no edit
|
|
63
63
|
> needed when modules arrive. `darkMode` keys off `contrast-high` for the WCAG high-contrast toggle.
|
|
64
64
|
|
|
65
|
+
> **QA gotcha (fonts):** when the project's design swaps in its own heading font, verify it covers
|
|
66
|
+
> Polish diacritics (`ą ć ę ł ń ś ź ż` + uppercase). Bacasime Antique, Prata and Italiana lack them
|
|
67
|
+
> — titles silently render in a fallback font and QA rejects it (verified replacements: Gilda
|
|
68
|
+
> Display, Playfair Display). Never leave a `fontFamily` entry as an empty array — it falls back to
|
|
69
|
+
> system sans. See `references/qa-hardening.md` §5.
|
|
70
|
+
|
|
65
71
|
## 2. postcss
|
|
66
72
|
|
|
67
73
|
Add Tailwind + autoprefixer to postcss. If the base has a `postcss.config.js`/`.json`, add the
|
|
@@ -24,6 +24,14 @@ grep -q "SharedModule.forRoot" apps/web/src/app/app.config.ts && pass "SharedMod
|
|
|
24
24
|
grep -q "SEARCH_CONFIG_TOKEN" apps/web/src/app/app.config.ts && pass "SEARCH_CONFIG_TOKEN" || bad "SEARCH_CONFIG_TOKEN in app.config.ts"
|
|
25
25
|
grep -q "multimediaUrl" apps/web/src/server.ts && pass "multimediaUrl in server.ts" || bad "multimediaUrl in server.ts"
|
|
26
26
|
grep -q "/api/translations" apps/web/src/server.ts && pass "/api/translations endpoint" || bad "/api/translations in server.ts"
|
|
27
|
+
|
|
28
|
+
echo "== QA hardening (references/qa-hardening.md) =="
|
|
29
|
+
# dynamic sitemap/robots endpoints (static copies shipped another museum's domain in past projects)
|
|
30
|
+
grep -q "sitemap.xml" apps/web/src/server.ts && pass "dynamic /sitemap.xml endpoint" || bad "/sitemap.xml endpoint in server.ts (SITE_URL-driven, never a static copy)"
|
|
31
|
+
grep -q "robots.txt" apps/web/src/server.ts && pass "dynamic /robots.txt endpoint" || bad "/robots.txt endpoint in server.ts (SITE_URL-driven, never a static copy)"
|
|
32
|
+
if ls apps/web/public/favicon.ico apps/web/src/favicon.ico >/dev/null 2>&1; then pass "favicon present"; else bad "favicon.ico missing (QA bug 'Brak ikony' in two projects)"; fi
|
|
33
|
+
# an empty fontFamily array silently falls back to system sans (QA-rejected typography)
|
|
34
|
+
if grep -qE ":\s*\[\s*\]" tailwind.config.js 2>/dev/null; then bad "empty fontFamily array in tailwind.config.js (heading font falls back to system sans)"; else pass "no empty fontFamily entries"; fi
|
|
27
35
|
grep -q "./lib/components" libs/shared/angular/src/index.ts && pass "shared-lib components barrel populated" || bad "components barrel in shared-lib index.ts"
|
|
28
36
|
[ -f .claude/settings.json ] && grep -q "flow@mobilems" .claude/settings.json && pass "flow@mobilems enabled" || bad "flow@mobilems in .claude/settings.json"
|
|
29
37
|
|