From 070da81907bdde9d5c855a37db2538f6eb4232e4 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 9 Oct 2026 18:29:54 +0200 Subject: [PATCH] docs(16-02): complete vue-grzybyfunkcjonalne-app plan --- .../16-02-SUMMARY.md | 306 ++++++++++++++++++ .../deferred-items.md | 7 + 2 files changed, 313 insertions(+) create mode 100644 .planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-02-SUMMARY.md create mode 100644 .planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md diff --git a/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-02-SUMMARY.md b/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-02-SUMMARY.md new file mode 100644 index 0000000..b29a92f --- /dev/null +++ b/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-02-SUMMARY.md @@ -0,0 +1,306 @@ +--- +phase: 16-grzybyfunkcjonalne-pl-on-reusable-blog-views +plan: 02 +subsystem: frontend (Nuxt site app) +tags: [nuxt, ssr, vue-journal-layer, i18n, nuxtjs-seo, swr, google-analytics, submodule] +status: complete + +requires: + - phase: 16-01 + provides: "vue-journal-layer v0.1.0 (journal.routes, useJournalApi/useJournalLinks, gateJournalHtml, --journal-* tokens, testing/smoke-ssr.mjs and fixture-api.mjs)" +provides: + - "vue-grzybyfunkcjonalne-app repo (private, SSH) on git.golem15.com, main b50c808" + - "submodule vue-grzybyfunkcjonalne-app in sm-grzybyfunkcjonalne-app (gitlink b50c808, pushed)" + - "PHP URL map on the layer: /blog, /blog/:slug, /:category, /autor/:author, no tag pages; /404, /error and unknown roots 404" + - "homepage, typed menu, FAQ, footer, /kontakt, /trwaja-prace, error.vue with the live Polish copy" + - "GA through NUXT_PUBLIC_GA_ID only (gaHeadScripts, GA_ID_PATTERN)" + - "smoke:ssr (35 checks) and smoke:ga (2 checks) on 14 fixture posts from the PHP export" +affects: [16-03, 16-04, 16-05] + +actuals: + tokens: 30493 # chars/4 over the 44 tracked app files, pnpm-lock.yaml and public/ binaries excluded + tasks: 3 + commits: 6 # 3 in vue-grzybyfunkcjonalne-app (git rev-list --count HEAD, new repo) + 3 in sm-grzybyfunkcjonalne-app (git rev-list --count ff20e6c..HEAD) +commits: 6 +plan_head_before: ff20e6cce25628341c4c1e6b5879f1f3979baa2d # sm-grzybyfunkcjonalne-app; vue-grzybyfunkcjonalne-app started empty +plan_head_after: 33bc5e04bfc2ad2c79c33b01c962d49ed21dd7cc # sm-grzybyfunkcjonalne-app; app repo main b50c808f88e43fd97072690e52bc9144efbcc3a8 + +tech-stack: + added: ["vue-grzybyfunkcjonalne-app (new repo): nuxt 4.4.8, vue 3.5.35, typescript 5.9.3, vue-journal-layer git tag v0.1.0 (lock commit 783426c)"] + patterns: + - "Site app extends the layer with no modules key; journal.routes carries the site's URL shape" + - "Visitor address recorded in event.context on the Nitro request hook, so swr-cached renders still forward X-Forwarded-For" + - "Site copy in pl.json (HTML columns allowed via i18n compilation.strictMessage false), data in typed app/data files, every v-html through gateJournalHtml" + - "Colours only in tokens.css (:root:root overrides of --journal-* plus --site-*); components hold no colour literals" + +key-files: + created: + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/nuxt.config.ts + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/package.json + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/pnpm-lock.yaml + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/app.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/error.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/layouts/default.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/pages/index.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/pages/kontakt.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/pages/trwaja-prace.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/components/SiteHeader.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/components/Journal/IndexHeader.vue + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/composables/useJournalApi.ts + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/data/menu.ts + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/data/contact.ts + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/data/faq.pl.json + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/utils/analytics.ts + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/assets/css/tokens.css + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/app/assets/css/base.css + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/server/plugins/client-address.ts + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/server/plugins/html-errors.ts + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/i18n/locales/pl.json + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/tests/fixtures/expect.txt + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/tests/fixtures/expect-ga.txt + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/README.md + - .planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md + modified: + - ../sm-grzybyfunkcjonalne-app/.gitmodules + +key-decisions: + - "Under routeRules swr the site app records the visitor address in event.context on the Nitro request hook and overrides useJournalApi to forward it, because Nitro's cached handler strips request headers and every SSR cache miss would otherwise share one Go rate-limit bucket; the proper fix belongs in vue-journal-layer (deferred-items.md)" + - "Page URLs of the site answer errors with the HTML error page even for fetch-like requests (server/plugins/html-errors.ts); only /api/, /_ and .json paths keep Nuxt's JSON errors" + - "Site rich-text copy stays in pl.json with i18n compilation.strictMessage false; contact data (email, phone) lives in app/data/contact.ts because an email address is not a valid vue-i18n message" + - "The host sm-grzybyfunkcjonalne-app master was pushed to its empty remote together with the submodule mount" + +patterns-established: + - "App-side SSR smoke: layer smoke-ssr.mjs with app fixtures built from the PHP export by a script, one expectations file per environment variant" + +requirements-completed: [SC-1, SC-3] + +coverage: + - id: D1 + description: "The site app extends vue-journal-layer v0.1.0 from the git tag (lockfile commit 783426c), builds as an SSR Node server with swr route rules and is mounted as a submodule of sm-grzybyfunkcjonalne-app (D-04, D-07, D-08)" + requirement: SC-1 + verification: + - kind: integration + ref: "pnpm -C ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app install --frozen-lockfile && build -> ok; git -C ../sm-grzybyfunkcjonalne-app ls-files -s vue-grzybyfunkcjonalne-app -> 160000 b50c808" + status: pass + - kind: integration + ref: "go -C ../sm-grzybyfunkcjonalne-app vet ./... && go test ./... -run '^(TestBootUserTranslateJournal)$' -> PASS" + status: pass + human_judgment: false + - id: D2 + description: "Every D-15 URL form answers with the expected status: post by pl slug 200, base slug 404, /cordyceps with content, /amanita-muscaria 200, unknown roots, /rss, /feed, /404, /error 404, /blog?page=2 200, ?page=3 404, /en/blog 404, /autor/admin 200, /autor/nobody 404, /rss.xml RSS, sitemap entries (D-15, D-16, D-24)" + requirement: SC-3 + verification: + - kind: integration + ref: "pnpm run smoke:ssr -> smoke-ssr: ok (35 checks), tests/fixtures/expect.txt" + status: pass + human_judgment: false + - id: D3 + description: "Homepage (hero, latest 3 posts, about, CTA, reasons, FAQ with 16 entries), menu with all 7 categories, footer, /kontakt with mailto and no form, /trwaja-prace, the PHP 404 copy and the blog header carry the live Polish copy (D-11, D-12, D-14)" + requirement: SC-1 + verification: + - kind: integration + ref: "tests/fixtures/expect.txt homepage, kontakt, trwaja-prace, /404 and /blog lines in smoke:ssr (35 checks ok); ! grep -rn ' no match" + status: pass + human_judgment: false + - id: D4 + description: "Google Analytics G-MLSV7WJ6W4 loads only with NUXT_PUBLIC_GA_ID; empty or malformed ids give no script (D-23, T-16-07)" + requirement: SC-1 + verification: + - kind: integration + ref: "smoke:ssr '200 / notcontains=googletagmanager'; pnpm run smoke:ga -> smoke-ssr: ok (2 checks)" + status: pass + - kind: command + ref: "node -e gaHeadScripts('') / gaHeadScripts(\"G-ABC');alert(1);//\") / gaHeadScripts('UA-123') -> [] each" + status: pass + human_judgment: false + - id: D5 + description: "The refreshed design (Jost/Open Sans, green palette, roomier sections) reads as a cleaner successor of the arsha look on desktop and mobile (D-01, D-03)" + verification: [] + human_judgment: true + rationale: "Visual quality is a judgment call; the smoke only proves markup, copy and that components hold no colour literals. 16-04's staging checkpoint is the natural place to look at it." + +duration: 16 min +completed: 2026-10-09 +--- + +# Phase 16 Plan 02: vue-grzybyfunkcjonalne-app Summary + +**grzybyfunkcjonalne.pl is now a Nuxt 4 SSR app that extends `vue-journal-layer` v0.1.0 from its git tag. It answers the PHP site's URLs (`/blog`, `/blog/:slug`, `/:category`, `/autor/:author`, `/kontakt`, `/trwaja-prace`) with full HTML, returns real 404s where the PHP site soft-200'd, and carries the live Polish homepage, menu, FAQ, footer and error copy in a refreshed green design. Analytics loads only from `NUXT_PUBLIC_GA_ID`. The app is pushed (private, SSH) and mounted as a submodule of `sm-grzybyfunkcjonalne-app`.** + +## Performance + +- **Duration:** 16 min (wall clock as measured by `date`) +- **Started:** 2026-10-09T16:11:41Z +- **Completed:** 2026-10-09T16:28:10Z +- **Tasks:** 3 (Task 1 a tracer, auto-verified end to end before expansion) +- **Files:** 44 tracked files in the new app repo; `.gitmodules` and the gitlink in the host + +## Accomplishments + +- **URL parity (D-15, D-16, D-24).** `journal.routes` is `{ index: '/blog', post: '/blog/:slug', category: '/:category', author: '/autor/:author', tag: false }`, with page sizes 12/10/10 as in `blog.htm`, `index.htm` and `autor.htm`. Static pages win over `/:category`. + - Unknown roots, `/rss`, `/feed`, `/404` and `/error` all answer 404. + - `/amanita-muscaria` answers 200 with an empty list. + - Posts answer on the Polish slug only, and `/en/blog` is a 404. +- **SSR with swr (D-07).** `'/**'` is cached 300 s and `'/blog/**'` 120 s on the Node server. Pages stay full HTML. +- **Content port (D-11, D-12, D-14).** All Polish copy comes from the live HTML (curl, 2026-10-09): + - Homepage: hero, latest 3 posts (layer `JournalPostCard`), "Czym są grzyby funkcjonalne?" with both rich-text columns, "Skorzystaj z wiedzy ekspertów", "Dlaczego warto?" with 4 reasons, and the FAQ (16 Q&A in `app/data/faq.pl.json`). + - `MAIN_MENU` is typed config. Its `journal-categories` dropdown lists all 7 categories from the API, the empty one included. + - Footer: address, `tel:` and `mailto:` links, footer and project links, X profile. + - `/kontakt` shows the contact details only, with no form. + - `/trwaja-prace` shows "Niebawem". + - `error.vue` shows the PHP 404 copy ("Nie Znaleziono / Nie znaleziono tej strony") or the error copy ("Wystąpił Błąd / Spróbuj ponownie za chwilę"). + - The blog hero overrides `JournalIndexHeader`: "Artykuły" with "Przeczytaj nasze artykuły…", plus "Strona N" after page 1. +- **Design (D-01, D-03).** + - Colours: `tokens.css` overrides `--journal-*` on `:root:root` and adds `--site-*` tokens seeded from #0D705D, #27AE60, #138C74, #1AAC8F and #444444. + - Fonts: Jost for headings and Open Sans for body text, self-hosted by `@nuxt/fonts`. + - `base.css` holds the global styles with breakpoints at 768px and 1100px. Components contain no colour literals. + - The layer is unchanged. +- **Analytics (D-23).** `gaHeadScripts(id)` returns nothing for an empty id or one that fails `GA_ID_PATTERN`. Otherwise it returns the gtag loader and config script. `app.vue` feeds it `runtimeConfig.public.gaId`. +- **SEO.** + - `robots.txt` disallows `/gfadmin` and `/_journal`. + - The sitemap lists posts and every category but not `/trwaja-prace`. + - `linkChecker` skips Go-served paths. + - Titles take the form `Witamy | Grzyby Funkcjonalne` (the homepage), as on the live site. +- **Tests.** `tests/fixtures/api` holds 14 published posts generated from the PHP journal export (pl text, Go wire dates, the admin author the 16-03 runbook assigns) and the 7 PHP categories. `smoke:ssr` runs 35 checks and `smoke:ga` runs 2. + +## Task Commits + +App repo `vue-grzybyfunkcjonalne-app` (branch `main`, pushed over SSH): + +1. **Task 1 (tracer): the app on layer v0.1.0 with the PHP URL map, mounted in the host.** `2b2d1c7` (feat). Host: `467d0a0` (chore: mount vue-grzybyfunkcjonalne-app submodule). +2. **Task 2: homepage, typed menu, FAQ and footer.** `caf4d3f` (feat). Host: `6063e9b` (chore: point vue-grzybyfunkcjonalne-app at the homepage). +3. **Task 3: kontakt, trwaja-prace, error pages, blog header, runtime GA and README.** `b50c808` (feat). Host: `33bc5e0` (chore: point vue-grzybyfunkcjonalne-app at kontakt and analytics). + +The tracer gate was auto mode off, `end-of-phase` and automated-only ``. The tracer verify was re-run (install, build, smoke, gitlink, host vet and boot test all passed) before expansion. + +**Remote state:** + +- App `refs/heads/main` is at `b50c808`. +- The host `master` is at `33bc5e0`. The remote was empty, so this was its first push, made together with the mount. + +**Plan metadata:** the docs commit in summercms.go (this SUMMARY, `deferred-items.md`, STATE, ROADMAP, REQUIREMENTS). + +## Files Created/Modified + +All of these are in `../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app` (new repository) unless noted: + +- `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`, `.nvmrc`, `.gitignore`, `tsconfig.json`: the package. + - Scripts: build, dev, preview, prepare, `smoke:ssr` and `smoke:ga`. + - Dependencies: nuxt 4.4.8, vue 3.5.35 and the layer tag. The layer's modules are not re-listed. +- `nuxt.config.ts`: config for the layer, routes, i18n, site, routeRules, runtime config, robots, sitemap, linkChecker, css and fonts. +- `app/app.vue` and `app/error.vue`; `app/layouts/default.vue` (header, main, footer, with Journal routes inside `.journal-layout`). +- `app/pages/{index,kontakt,trwaja-prace}.vue`. +- `app/components/{SiteHeader,SiteFooter,HeroSection,LatestPosts,AboutSection,CtaSection,ServicesSection,FaqSection,PageHero}.vue` and `app/components/Journal/IndexHeader.vue`. +- `app/data/{menu.ts,contact.ts,faq.pl.json}`, `app/utils/analytics.ts`, `app/composables/useJournalApi.ts`. +- `server/plugins/{client-address,html-errors}.ts`. +- `app/assets/css/{tokens,base}.css`, `i18n/locales/pl.json`. +- `public/favicon.ico`, `public/images/{hero-forest.webp,cta.png}`. +- `tests/fixtures/api/{posts,categories,tags,authors}.json`, `tests/fixtures/api/rss.xml`, `tests/fixtures/{expect,expect-ga}.txt`. +- `README.md`. +- In the host: `../sm-grzybyfunkcjonalne-app/.gitmodules` and the `vue-grzybyfunkcjonalne-app` gitlink. + +## Decisions Made + +See `key-decisions` in the frontmatter. Smaller choices: + +- **Button targets.** The hero "Czytam", the CTA "Czytam" and "Poznaj naszą misję" all link to `/blog`. On the live site they pointed at `#`. +- **Homepage meta description.** It is the live one, "Odkryj Sekrety Natury dla Lepszego Zdrowia". `site.description` keeps the tagline. +- **Footer labels.** They read "Telefon" and "E-mail". The live labels were the English "Phone number" and "Email". +- **FAQ heading.** It is kept verbatim from the live page, including its typo ("Grzyby funcjonalne - Pytania i Odpowiedzi"). +- **Social link.** The live footer renders no social links, but the sitemanager data holds `https://x.com/ophiel118`, which the plan names. The footer shows it. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 2 - Missing critical] Visitor address lost on swr-cached SSR renders** +- **Found during:** Task 1 (first smoke run: `smoke-ssr: FAIL x-forwarded-for not forwarded`). +- **Issue:** Nitro's cached handler (used for `routeRules` swr) renders from a request copy that keeps only `varies` headers. The layer's `useJournalApi` therefore forwarded only the Nuxt peer address. Every cache miss, including attacker-chosen query strings, would share the Go limiter's 120/min bucket (Pitfall 5, T-16-03). +- **Fix:** + - `server/plugins/client-address.ts` records `x-forwarded-for` plus the peer in `event.context` on the Nitro `request` hook. The context object is shared with the cached render. + - `app/composables/useJournalApi.ts` overrides the layer composable (the project wins over a layer by import priority) and forwards that value. + - The layer is unchanged. The layer fix is logged in `deferred-items.md`. +- **Files modified:** `server/plugins/client-address.ts`, `app/composables/useJournalApi.ts` (both new, outside the plan's file list). +- **Verification:** The smoke's forwarding assertion passes on every run. +- **Commit:** 2b2d1c7. + +**2. [Rule 3 - Blocking] Nuxt answered error pages with JSON to the smoke's fetch** +- **Found during:** Task 2 (inspecting `/nieistnieje`). +- **Issue:** Node's fetch sends `sec-fetch-mode: cors`, so Nuxt's error handler returned JSON. The plan's `404 /404 contains=Nie znaleziono tej strony` could never pass. A curl-based check would hit the same thing. +- **Fix:** `server/plugins/html-errors.ts` adds `text/html` to `Accept` for page paths that do not ask for JSON. `/api/`, `/_…` and `.json` paths keep Nuxt's behaviour. Client navigation uses `_payload.json`, so it is unaffected. +- **Verification:** `404 /404 contains=Nie znaleziono tej strony` passes. +- **Commit:** b50c808. + +**3. [Rule 1 - Bug in plan data] Homepage "latest post" expectation** +- **Issue:** The plan's line `200 / contains=Najnowsze Badania nad Cordycepsem` assumed the cordyceps post is among the 3 newest. With the real export dates (2024-06-12) and a fixture set covering all 6 categories, it is not. The 3 newest are Lion's Mane posts from October 2024. +- **Fix:** The fixtures keep the true dates. The homepage lines check the actual newest titles ("Jak Dawkować Lion…", "Wprowadzenie do Grzyba Lwia Grzywa"), which matches the acceptance wording "the latest post title from the fixtures". The cordyceps title is still checked on its own post page. +- **Commit:** caf4d3f. + +**4. [Rule 3 - Blocking] `smoke:ga` could not observe forwarding with a single `/` line** +- **Issue:** `nuxt-server.mjs`'s readiness probe renders `/` into the swr cache, so the GA run's `/` request made no API call. The smoke's forwarding assertion then failed. +- **Fix:** `expect-ga.txt` also checks the gtag config on `/blog`, an uncached page. +- **Commit:** b50c808. + +**5. [Rule 3 - Blocking] Build constraints** +- `app.head.htmlAttrs.lang` was removed after nuxt-seo-utils flagged it. i18n renders `lang="pl-PL"`. +- `i18n.compilation.strictMessage: false` was added, because `@nuxtjs/i18n` rejects HTML in locale messages by default and the plan stores the about columns as HTML strings in `pl.json`. All such HTML is still gated before `v-html`. +- **Commits:** 2b2d1c7, caf4d3f. + +**6. [Scope adjustment] favicon.ico** +- The live `/favicon.ico` is a soft-200 HTML page, so there were no original bytes to keep. +- `public/favicon.ico` (16/32/48) was converted with ImageMagick from the live icon (`thumb_105_64…webp`). The hero and CTA images are byte-identical copies. +- **Commit:** caf4d3f. + +--- + +**Total deviations:** 6 (2 Rule 2/3 security and correctness fixes in new app files, 1 plan-data correction, 2 build/test blockers, 1 asset substitution). +**Impact on plan:** All artifacts and key links in the plan exist. The two server plugins and the composable override are additions; they change no route, option or copy. + +## Issues Encountered + +- **pnpm store workaround.** The sandbox `/tmp` pnpm store is read-only, so installs ran with `TMPDIR=` and `npm_config_store_dir`/`pnpm_config_store_dir` pointed at the project-drive store, as in 16-01. +- **Font metadata fetches.** `@nuxt/fonts` retries metadata fetches to fonts.bunny.net and api.fontshare.com, which the sandbox blocks. These are warnings only, and the builds succeed. +- **Pre-existing host state.** The host working tree shows ` m plugins/golem15/journal` (submodule working tree drift) and an untracked `.gsd/`. Both predate this plan and were left alone; only `.gitmodules` and the app gitlink were committed. +- **Host push.** The host remote `sm-grzybyfunkcjonalne-app` had no branches (`origin/master [gone]`). Pushing `master` created it with the full local history. + +## Known Stubs + +None. Every section renders real data or committed copy. + +`featured_image_url` is `null` in the fixtures because the export carries no images; 16-03 migrates them. Cards then render without an image, the same as for any post without one. + +## Threat Flags + +| Flag | File | Description | +|------|------|-------------| +| threat_flag: header-trust | server/plugins/client-address.ts | Copies the client-supplied `x-forwarded-for` plus the peer address into the SSR fetch header, the same values the layer forwards. Go reads the rightmost untrusted hop and trusts only 127.0.0.1, so a client still cannot pick another visitor's bucket. Same trust model as T-16-03. | +| threat_flag: error-format | server/plugins/html-errors.ts | Rewrites `Accept` on page paths so errors render as HTML. It does not touch `/api/`, `/_…` or `.json` paths and does not expose more data than the JSON error did. | + +## User Setup Required + +None for this plan. Production sets `NUXT_PUBLIC_GA_ID=G-MLSV7WJ6W4` through the 16-03 env template. + +## Next Phase Readiness + +- **16-03** builds `.output` from the submodule and runs it with `NUXT_JOURNAL_API_BASE` and `NUXT_SITE_INDEXABLE=false` on staging. + - Its curl-based smoke gets HTML error pages for page paths thanks to `html-errors.ts`. + - The Go limiter must trust 127.0.0.1 so the forwarded XFF is honoured; the host `config/http.yaml` already does. +- **16-05** unit tests: + - `app/utils/analytics.ts` (`GA_ID_PATTERN`, `gaHeadScripts`) and `app/data/*` shape checks (16 FAQ items, `MAIN_MENU` kinds) can run under `node --test`. + - The two server plugins are small enough to unit-test with a stub event. +- **Layer follow-ups** are in `deferred-items.md`: forward the client address under swr in the layer, and give the smoke tool an `Accept: text/html` default. + +## Self-Check: PASSED + +- FOUND: app files `nuxt.config.ts`, `app/data/menu.ts`, `app/data/faq.pl.json`, `app/utils/analytics.ts`, `tests/fixtures/expect.txt`, `tests/fixtures/expect-ga.txt`, `README.md`, and the host `.gitmodules` entry with url `git@git.golem15.com:golem15/vue-grzybyfunkcjonalne-app.git`. +- FOUND: app commits 2b2d1c7, caf4d3f and b50c808 on origin/main; host commits 467d0a0, 6063e9b and 33bc5e0 on origin/master. +- Plan verification: + - `pnpm install --frozen-lockfile` and `build` both passed. + - `smoke:ssr` printed `smoke-ssr: ok (35 checks)` and `smoke:ga` printed `smoke-ssr: ok (2 checks)`. + - The host gitlink is `160000 b50c808`, and host `go vet ./...` and `TestBootUserTranslateJournal` pass. + - The `