diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 4ff01cc..8c9c89a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -959,7 +959,7 @@ Plans: 3. Public pages that PHP Journal + the current theme rendered have a named successor on Go; sitemap is in if the site still needs it. 4. The new code has unit tests, delivered in the phase's last plan. -**Plans:** 2/5 plans executed +**Plans:** 3/5 plans executed Plans: **Wave 1** @@ -969,7 +969,7 @@ Plans: - [x] 16-02-PLAN.md — vue-grzybyfunkcjonalne-app: SSR site on the layer with the PHP URL map, homepage/menu/FAQ/kontakt content port, GA via runtime config; submodule in the host **Wave 3** *(blocked on Wave 2 completion)* -- [ ] 16-03-PLAN.md — sm-grzybyfunkcjonalne-app: /gfadmin, app plugin with featured-image and category-content commands, migration runbook, two-process deploy files, urls.txt and local rehearsal +- [x] 16-03-PLAN.md — sm-grzybyfunkcjonalne-app: /gfadmin, app plugin with featured-image and category-content commands, migration runbook, two-process deploy files, urls.txt and local rehearsal **Wave 4** *(blocked on Wave 3 completion)* - [ ] 16-04-PLAN.md — Staging deploy on the PHP site's server (human checkpoint) and smoke over every old URL @@ -1104,7 +1104,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → | 14.2. Local Fonoteka frontend on local SummerCMS backend (INSERTED) | 0/TBD | Not started | - | | 14.3. One-command app scaffolding (INSERTED) | 4/4 | In Progress| | | 15. Journal plugin | 4/4 | Complete | 2026-10-06 | -| 16. grzybyfunkcjonalne.pl on reusable blog views | 2/5 | In Progress| | +| 16. grzybyfunkcjonalne.pl on reusable blog views | 3/5 | In Progress| | | 17. Blog companions | 0/TBD | Not started | - | | 18. Payment Gateway | 0/TBD | Not started | - | | 19. Chat and ChatChannels | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index fee1d6a..8c34d77 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,8 +4,8 @@ milestone: v1.0 current_phase: 16 current_phase_name: grzybyfunkcjonalne.pl on reusable blog views status: executing -stopped_at: Phase 18 context gathered -last_updated: "2026-10-09T16:45:15.252Z" +stopped_at: Completed 16-03-PLAN.md +last_updated: "2026-10-09T16:56:24.832Z" last_activity: 2026-10-09 last_activity_desc: Phase 16 execution started state_head: 65d577b8285e7119db790b640324917fb180b98e @@ -13,7 +13,7 @@ progress: total_phases: 31 completed_phases: 13 total_plans: 148 - completed_plans: 141 + completed_plans: 142 gsd_state_version: "1.0" milestone_name: milestone --- @@ -30,7 +30,7 @@ See: .planning/PROJECT.md (updated 2026-09-16) ## Current Position Phase: 16 (grzybyfunkcjonalne.pl on reusable blog views) — EXECUTING -Plan: 3 of 5 +Plan: 4 of 5 Status: Ready to execute Last activity: 2026-10-09 - Completed quick task 261009-mys: subtitle, avatar and My account page for backend users @@ -196,6 +196,7 @@ Progress: [██████████] 100% | Phase 14.3 P04 | 19min | 3 tasks | 32 files | | Phase 16 P01 | 1h 34m | 4 tasks | 55 files | | Phase 16 P02 | 16 min | 3 tasks | 44 files | +| Phase 16 P03 | 22 min | 3 tasks | 32 files | ## Accumulated Context @@ -628,6 +629,10 @@ Recent decisions affecting current work: - [Phase 16]: Under routeRules swr the site app records the visitor address in event.context (Nitro request hook) and overrides useJournalApi, because Nitro's cached handler strips request headers; the proper fix belongs in vue-journal-layer - [Phase 16]: Site page URLs answer errors with the HTML error page even for fetch-like requests (server/plugins/html-errors.ts); /api/, /_ and .json paths keep Nuxt JSON errors - [Phase 16]: Site rich-text copy stays in pl.json (i18n compilation.strictMessage false); contact data lives in app/data/contact.ts +- [Phase 16]: Plugin commands in a summer-build host open and publish the database themselves (lookupDB via lagoon); the journal import runs as grzyby:import-posts because the generated main.go publishes no DB for plugin commands +- [Phase 16]: migrate.sh imports from a copy of the PHP export with empty translations [] arrays normalized to {}; the importer fix is deferred to sm-journal-plugin +- [Phase 16]: The URL rehearsal gives each URL its own X-Forwarded-For visitor (203.0.113.N) so ~90 SSR pages stay under Go's 120/min per-visitor journal limit +- [Phase 16]: The staging nginx image location declares its own types map and matches extensions case-insensitively so images keep an image type under nosniff ### Pending Todos @@ -697,6 +702,6 @@ Items acknowledged and carried forward from previous milestone close: ## Session Continuity -Last session: 2026-10-09T16:45:14.650Z -Stopped at: Phase 18 context gathered -Resume file: .planning/phases/18-payment-gateway/18-CONTEXT.md +Last session: 2026-10-09T16:56:24.257Z +Stopped at: Completed 16-03-PLAN.md +Resume file: None diff --git a/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-03-SUMMARY.md b/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-03-SUMMARY.md new file mode 100644 index 0000000..4ba4b98 --- /dev/null +++ b/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-03-SUMMARY.md @@ -0,0 +1,373 @@ +--- +phase: 16-grzybyfunkcjonalne-pl-on-reusable-blog-views +plan: 03 +subsystem: infra (Go host, migration runbook, staging deploy) +tags: [summercms, go, journal, migration, postgres, nginx, supervisor, smoke, nuxt-ssr] +status: complete + +requires: + - phase: 16-02 + provides: "vue-grzybyfunkcjonalne-app (submodule b50c808) answering the PHP URL map with SSR" + - phase: quick-261009-i6e + provides: "D-20 anonymous author endpoints (sm-journal-plugin 908ade9, 57a347c)" + - phase: quick-261009-mgk + provides: "D-21 category content column and API field (sm-journal-plugin 740dcd0, f22d089, 3d533d4)" +provides: + - "admin at /gfadmin (config/backend.yaml uri: /gfadmin); no /backend route" + - "app plugin golem15.grzyby (plugins/grzyby, commands only): grzyby:import-posts, grzyby:import-featured-images, grzyby:import-categories" + - "scripts/migrate.sh: the ordered D-25/D-17 runbook (migrate, fresh-db guard, pl default, import once, categories, media, featured images, author, gap report)" + - "scripts/php-export/{featured-images.sql,categories.sql,README.md}: JSON Lines exports from the PHP database" + - "deploy/: staging nginx vhost, supervisor programs grzybyfunkcjonalne-go (8096) and grzybyfunkcjonalne-nuxt (3016), env templates" + - "scripts/build.sh, scripts/check-deploy.sh, scripts/smoke.sh (full, --go-only, --url) and scripts/urls.txt (71 live sitemap URLs + 27 more)" + - "host README; todo journal-imported-html-admin-resave" +affects: [16-04, 16-05] + +actuals: + tokens: 25500 # chars/4 over the realized diff (host 33bc5e0..8f7f2de without go.sum and the PNG, plus the todo file) + tasks: 3 + commits: 9 # MEASURED in sm-grzybyfunkcjonalne-app: git rev-list --count 33bc5e0..HEAD; plus 1 docs commit in summercms.go (6ff7d5d) +commits: 9 +plan_head_before: 33bc5e04bfc2ad2c79c33b01c962d49ed21dd7cc # sm-grzybyfunkcjonalne-app +plan_head_after: 8f7f2de7650d9946970bc50e84cc9cc632057059 # sm-grzybyfunkcjonalne-app, pushed to origin/master + +tech-stack: + added: [] + patterns: + - "App-local plugin as an in-repo module (plugins/grzyby) registered by summer plugin:add; commands only, no routes" + - "Plugin commands open and publish the database themselves (lookupDB via lagoon.OpenFromApp + lagoon.Publish)" + - "JSON Lines maps produced by read-only SQL on the PHP database drive idempotent Go import commands" + - "Runbook script with a fresh-database guard and an asserted locale switch before a one-shot import" + - "URL rehearsal from a line grammar shared with the app smoke ([remote] STATUS PATH [contains|notcontains|type])" + +key-files: + created: + - ../sm-grzybyfunkcjonalne-app/plugins/grzyby/plugin.go + - ../sm-grzybyfunkcjonalne-app/plugins/grzyby/console/helpers.go + - ../sm-grzybyfunkcjonalne-app/plugins/grzyby/console/import_posts.go + - ../sm-grzybyfunkcjonalne-app/plugins/grzyby/console/import_featured_images.go + - ../sm-grzybyfunkcjonalne-app/plugins/grzyby/console/import_categories.go + - ../sm-grzybyfunkcjonalne-app/plugins/grzyby/testdata/featured/map.jsonl + - ../sm-grzybyfunkcjonalne-app/plugins/grzyby/testdata/categories/map.jsonl + - ../sm-grzybyfunkcjonalne-app/scripts/migrate.sh + - ../sm-grzybyfunkcjonalne-app/scripts/smoke.sh + - ../sm-grzybyfunkcjonalne-app/scripts/build.sh + - ../sm-grzybyfunkcjonalne-app/scripts/check-deploy.sh + - ../sm-grzybyfunkcjonalne-app/scripts/urls.txt + - ../sm-grzybyfunkcjonalne-app/scripts/php-export/featured-images.sql + - ../sm-grzybyfunkcjonalne-app/scripts/php-export/categories.sql + - ../sm-grzybyfunkcjonalne-app/scripts/php-export/README.md + - ../sm-grzybyfunkcjonalne-app/deploy/nginx/grzybyfunkcjonalne-staging.conf + - ../sm-grzybyfunkcjonalne-app/deploy/supervisor/grzybyfunkcjonalne-go.conf + - ../sm-grzybyfunkcjonalne-app/deploy/supervisor/grzybyfunkcjonalne-nuxt.conf + - ../sm-grzybyfunkcjonalne-app/deploy/env.example + - ../sm-grzybyfunkcjonalne-app/deploy/nuxt.env.example + - ../sm-grzybyfunkcjonalne-app/README.md + - .planning/todos/pending/journal-imported-html-admin-resave.md + modified: + - ../sm-grzybyfunkcjonalne-app/config/backend.yaml + - ../sm-grzybyfunkcjonalne-app/boot_test.go + - ../sm-grzybyfunkcjonalne-app/summer.yaml + - ../sm-grzybyfunkcjonalne-app/go.work + - ../sm-grzybyfunkcjonalne-app/go.mod + - ../sm-grzybyfunkcjonalne-app/plugins.gen.go + - ../sm-grzybyfunkcjonalne-app/plugins/golem15/journal + - ../sm-grzybyfunkcjonalne-app/.gitignore + - .planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md + +key-decisions: + - "Plugin commands in a summer-build host open and publish the database themselves, because the generated main.go publishes none for them; the journal import runs as grzyby:import-posts (the Journal command's own Run after the DB is opened)" + - "migrate.sh imports from a copy of the export in which only empty \"translations\": [] arrays become {}; the export directory stays untouched and the importer fix is deferred to sm-journal-plugin" + - "The URL rehearsal gives each URL its own X-Forwarded-For visitor (203.0.113.N): one address for ~90 SSR pages exceeds Go's 120/min per-visitor journal limit, and a broken XFF chain would still surface as 429s" + - "The nginx image location declares its own types map (jpeg/png/gif/webp, octet-stream default) so images keep an image type under nosniff whatever the server's mime.types; the regex is case-insensitive (~*) for upper-case extensions" + - "The boot test loads a temp copy of config/ so a developer's gitignored .env cannot override backend.uri" + +patterns-established: + - "scripts/smoke.sh --url BASE as the deployed-site check over urls.txt (16-04 reuses it on staging)" + +requirements-completed: [SC-1, SC-3] + +coverage: + - id: D1 + description: "Admin at /gfadmin, no /backend route; the host journal gitlink points at 3d533d4 with the D-20 and D-21 additions (D-09)" + requirement: SC-1 + verification: + - kind: unit + ref: "go -C ../sm-grzybyfunkcjonalne-app test ./... -run '^(TestBootUserTranslateJournal|TestCORS)$' (assertAdminMount, assertGrzybyCommands)" + status: pass + - kind: integration + ref: "scripts/smoke.sh --go-only: GET /gfadmin non-404, GET /backend 404" + status: pass + human_judgment: false + - id: D2 + description: "grzyby:import-featured-images copies the original under attach.BlobKey and attaches it to the post; the post answers on its pl slug with featured_image_url /storage/app/uploads/public/665/f1e/2a3/665f1e2a3b4c5d6e7f8091.png; a second run creates nothing (D-22)" + requirement: SC-1 + verification: + - kind: integration + ref: "scripts/smoke.sh --go-only -> smoke: ok (go) (featured_image_url, file on disk equals source, base slug 404, 'Results: 0 created, 1 skipped, 0 failed')" + status: pass + human_judgment: false + - id: D3 + description: "grzyby:import-categories fills name, description and content preferring pl; cordyceps carries the content marker, amanita-muscaria is listed with post_count 0; a rerun reports 0 updated, 1 unchanged (D-21, D-24)" + requirement: SC-3 + verification: + - kind: integration + ref: "scripts/smoke.sh --go-only (categories?include_empty=1 checks and the second run)" + status: pass + - kind: integration + ref: "scripts/php-export/categories.sql and featured-images.sql run on a throwaway MariaDB 12.3 fixture; output parsed by the Go command (--dry-run)" + status: pass + human_judgment: false + - id: D4 + description: "scripts/migrate.sh runs migrate, the fresh-database guard, the asserted pl default, the one-shot import, categories, media, featured images, author and the gap report in that order (D-25, D-17, D-15)" + requirement: SC-1 + verification: + - kind: integration + ref: "scripts/smoke.sh (full) runs migrate, admin:create and migrate.sh --author-login admin --author-slug admin; 63 published posts served (meta.total)" + status: pass + - kind: manual_procedural + ref: "migrate.sh --dry-run and the unknown-author path run by hand on a scratch database (dies with 'author login ... does not exist')" + status: pass + human_judgment: false + - id: D5 + description: "Staging deploy files: nginx vhost passes nginx -t; both supervisor programs parse with the required keys; /backend 404, image regex with nosniff, plain /storage/ 404, GET/HEAD only to Nuxt (D-07, D-10)" + requirement: SC-1 + verification: + - kind: integration + ref: "scripts/check-deploy.sh -> check-deploy: ok" + status: pass + - kind: manual_procedural + ref: "the vhost run under a non-root nginx 1.30.4 on loopback: images 200 image/png with nosniff and HSTS, x.php and config.yaml 404, /backend 404, POST / 403, HTTP 301 to HTTPS" + status: pass + human_judgment: false + - id: D6 + description: "Local rehearsal answers every non-remote urls.txt line: all 71 live sitemap URLs, /blog?page=2 keeping ?page=2, /amanita-muscaria 200, /autor/admin 200, /backend 404, /en/blog 404, /rss.xml as RSS (D-15, D-18)" + requirement: SC-3 + verification: + - kind: e2e + ref: "scripts/build.sh && scripts/smoke.sh -> smoke: ok (95 urls)" + status: pass + human_judgment: false + - id: D7 + description: "The runbook and deploy files work on the real PHP server (table names, morphs, Postgres and Node versions, paths)" + verification: [] + human_judgment: true + rationale: "Only the staging deploy in 16-04 can confirm the server-side values the SQL files and deploy files assume" + +duration: 22 min +completed: 2026-10-09 +--- + +# Phase 16 Plan 03: Host, migration runbook and staging deploy Summary + +**`sm-grzybyfunkcjonalne-app` is now the deployable host.** + +- The admin is at `/gfadmin`. +- An app plugin `golem15.grzyby` imports featured images under their Winter disk names and the category pillar content from JSON Lines maps produced by SQL on the PHP database. +- One ordered runbook does the migration: migrate, an asserted pl default, a one-shot journal import, categories, media, featured images, the author and a gap report. +- Staging deploy files cover two processes (Go on 8096, Nuxt on 3016 behind one nginx vhost). +- A local rehearsal on a throwaway database answers all 95 local URL checks, including every one of the 71 live sitemap URLs. + +## Performance + +- **Duration:** 22 min +- **Started:** 2026-10-09T16:31:36Z +- **Completed:** 2026-10-09T16:54:03Z +- **Tasks:** 3 (Task 1 a tracer, auto-verified end to end before expansion) +- **Files:** 32 changed in the host (2,701 insertions), plus 1 todo and `deferred-items.md` in summercms.go + +## Accomplishments + +- **D-09.** `config/backend.yaml` sets `uri: /gfadmin`. The boot test asserts that some route starts with `/gfadmin/` and that none is under `/backend`. The nginx vhost answers 404 for `/backend`. +- **Journal gitlink.** It now points at `3d533d4`, which carries the D-20 author endpoints and the D-21 category content. + - The nested clone had working-tree drift. I saved it as a patch and checked it against the upstream commits; all of it was already contained in `be4f33a..3d533d4`. + - I then discarded the drift file by file and checked out `3d533d4`. +- **`golem15.grzyby`.** It was scaffolded with `summer make:plugin` and registered with `summer plugin:add`. `summer build` regenerated `plugins.gen.go` (`main.go` was unchanged). I stripped the plugin down to commands only: + - `grzyby:import-featured-images` (D-22, T-16-12): validates the disk name, applies the content-type allowlist and sniffs the bytes, keeps the source path inside `--source` and checks the size. It falls back from the base slug to the pl slug through the export, writes the blob under `attach.BlobKey` and inserts the `system_files` row. It is idempotent. + - `grzyby:import-categories` (D-21): prefers pl values and finds the category by pl slug, then base slug. It accepts `translations.pl` as an object, a JSON string (as MariaDB nests it) or null. Equal values count as unchanged. + - `grzyby:import-posts`: see deviation 1. +- **The runbook (`scripts/migrate.sh`).** Steps run in the D-25 order. + - It refuses a database that already holds posts (Pitfall 3). + - It asserts that `pl` is the only enabled default before importing (Pitfall 1). + - The author step sets `golem15_bloghub_author_slug` and assigns every post without an author. + - The gap report flags and does not patch (D-17). + - `--dry-run` runs only the steps that write nothing. +- **PHP exports.** + - `featured-images.sql` and `categories.sql` emit one `JSON_OBJECT` per row. I ran both against a throwaway MariaDB 12.3 with a fixture schema, and the Go command parsed the output. + - The README names the server checks: the post and category morphs and tables, and the `winter_`/`rainlab_`/`golem15_` translate table prefixes. It also gives the `sed` rewrites for the Blog plugin and the copy steps. +- **Deploy.** + - nginx vhost with: + - `/gfadmin` and `/_journal/` proxied to Go (64 MiB body limit); + - the image regex location with nosniff, an explicit types map and 30-day expiry; + - a plain `/storage/` prefix answering 404; + - `limit_except GET HEAD` for Nuxt; + - `X-Forwarded-For` on every proxy. + - Supervisor programs `grzybyfunkcjonalne-go` and `grzybyfunkcjonalne-nuxt` (Node `--env-file`). + - `env.example` (adds `SUMMER_URL`) and `nuxt.env.example` (`NUXT_SITE_INDEXABLE=false`, empty GA). +- **Build and checks.** + - `scripts/build.sh` builds `bin/grzybyfunkcjonalne` (linux/amd64) and `build/nuxt/.output`, and refuses generated-source drift. + - `scripts/check-deploy.sh` runs nginx -t in a temp prefix, checks both supervisor programs with configparser, and greps the vhost for its security locations. +- **`scripts/urls.txt`.** + - It holds the **71 live sitemap URLs** fetched 2026-10-09 (`/`, `/blog`, 7 categories, 62 posts), the same set as at research time. + - It adds 27 more lines: pagination, static pages, 404 cases, Go paths and 3 remote nginx lines, for 98 checks in total. + - `scripts/smoke.sh` checks all 95 non-remote lines locally and every line with `--url`. + +## Task Commits + +All code commits are in `../sm-grzybyfunkcjonalne-app` (`master`, pushed `33bc5e0..8f7f2de` after sm-journal-plugin reached origin): + +0. `90f2dbd` chore: point journal gitlink at author endpoint and category content +1. **Task 1 (tracer):** + - `5ccbc62` feat: mount the admin at /gfadmin + - `893cf42` feat: app plugin golem15.grzyby with featured-image import + - `a3266c3` fix: open the database for grzyby commands and add grzyby:import-posts + - `3f17f1c` feat: migration runbook and Go rehearsal +2. **Task 2:** + - `30233d5` feat: grzyby:import-categories for category content + - `81ffce3` feat: runbook categories, media and author steps; PHP export queries + - `6ff7d5d` (summercms.go) docs: todo for Journal HTML content re-save +3. **Task 3:** + - `07390b2` feat: staging deploy files and full URL rehearsal + - `8f7f2de` docs: host README + +**Tracer gate.** Interactive mode with `end-of-phase` and an automated-only ``. The verify was re-run and passed (vet, boot test, plugin vet, build, `smoke: ok (go)`, no `uri: /backend`) before expansion. + +**Plan metadata:** the docs commit in summercms.go (this SUMMARY, `deferred-items.md`, and my STATE/ROADMAP lines only). + +## Files Created/Modified + +See `key-files` in the frontmatter. Also: + +- `plugins/grzyby/go.mod`/`go.sum` (local replaces for the journal, translate and user modules). +- The host `go.mod` and `go.work` (written by `summer plugin:add`). +- The testdata 1×1 PNG at `plugins/grzyby/testdata/featured/uploads/665/f1e/2a3/665f1e2a3b4c5d6e7f8091.png` (69 bytes). + +## Decisions Made + +See `key-decisions` in the frontmatter. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] Plugin commands get no database in a `summer build` host** +- **Found during:** Task 1 (first rehearsal run: `journal: database is not configured`). +- **Issue:** The generated `main.go` publishes no `*gorm.DB` for plugin commands. Only lagoon, cabana and `serve` open one. So `journal:import-posts` cannot run from the host CLI at all. +- **Fix:** + - `lookupDB` opens and publishes `database.dsn` through `lagoon.OpenFromApp` and `lagoon.Publish`, which also runs the `OnDatabase` hooks. + - A thin `grzyby:import-posts` runs the Journal command's own `Run`, with the same flags, after that. + - `migrate.sh` calls it, and its step names say `journal:import-posts`. + - The framework fix is in `deferred-items.md`. This is outside this plan's files and the concurrency boundary (`modules/`). +- **Files modified:** `plugins/grzyby/console/{helpers.go,import_posts.go}`, `plugin.go`, `boot_test.go`, `scripts/migrate.sh`. +- **Verification:** The rehearsal imports 105 posts and serves 63 published. +- **Commit:** `a3266c3`. + +**2. [Rule 3 - Blocking] The journal importer rejects `"translations": []`** +- **Found during:** Task 1 (61 of 63 published posts served). +- **Issue:** The PHP exporter writes empty translations as `[]`. Five files failed as "Invalid JSON", including the live post `ekstrakt-z-turkey-tail`. +- **Fix:** `migrate.sh` imports from a temp copy in which only that empty array becomes `{}`, and prints the count. The importer fix is deferred to sm-journal-plugin. +- **Commit:** `3f17f1c`. + +**3. [Rule 3 - Blocking] A local `.env` overrode `backend.uri` in the boot test** +- **Issue:** compass reads `../.env` next to `config/`. The developer's gitignored host `.env`, which I did not read, keeps the admin at `/backend`, so the new `/gfadmin` assertion failed locally even though a clean work dir mounts `/gfadmin`. +- **Fix:** The boot test loads a temp copy of `config/`. +- **Commit:** `893cf42`. + +**4. [Rule 1 - Plan data] `/sitemap.xml` content type** +- **Issue:** `@nuxtjs/sitemap` serves `text/xml; charset=UTF-8`, not `application/xml`. +- **Fix:** `urls.txt` expects `type=text/xml`. +- **Commit:** `07390b2`. + +**5. [Rule 1 - Test design] One visitor address exhausted the Go rate limit** +- **Issue:** With `X-Forwarded-For: 203.0.113.7` on every URL, 28 checks got 429. About 90 SSR pages make more than 120 API calls a minute for that one visitor. This also proved the XFF chain works: the bucket used was the visitor's, not Nuxt's. +- **Fix:** Each URL uses its own `203.0.113.N`. +- **Commit:** `07390b2`. + +**6. [Rule 2 - Missing critical] Image types under nosniff** +- **Issue:** Under a bare `http {}`, as in check-deploy, nginx served the images as `text/plain` with nosniff. +- **Fix:** The image location declares its own `types` map and an octet-stream default, and matches case-insensitively (`~*`) for files like `.PNG`. +- **Verification:** On loopback, the images answer `200 image/png`. +- **Commit:** `07390b2`. + +**7. [Rule 2 - Missing critical] Featured-image hardening beyond the plan** +- **Fix:** `http.DetectContentType` must match the declared type, and the file size must match the map. +- **Commit:** `893cf42`. + +**8. [Scope adjustment] The UAT-host gap report** +- **Issue:** The 2 known `gf.uat.golem15.com` references are only in the base text of one export file. The pl import replaces that text, so no imported body carries them. +- **Fix:** The report lists the export file and the imported count (0) instead of claiming 2 broken bodies. +- **Commit:** `81ffce3`. + +**9. [Scope adjustment] The scaffold was trimmed** +- **Issue:** `make:plugin` writes the full scaffold (config, lang, mail views, `registry.gen.go`, `routes.go`). +- **Fix:** I removed it. The plugin is `plugin.go` plus `console/`, with no routes, so D-14 is unchanged. `registry.gen.go` is refreshed only by `make:*` artifact commands, not by `summer build`. +- **Commit:** `893cf42`. + +--- + +**Total deviations:** 9. Three were blocking fixes, two were missing-critical additions, two were plan-data or test-design corrections, and two were scope adjustments. + +**Impact on plan:** Every planned artifact, command name, flag and key link exists. The only additions are `grzyby:import-posts` and the export normalization, both workarounds for upstream gaps that are now recorded in `deferred-items.md`. + +## Issues Encountered + +- **Precondition.** `SMOKE_ADMIN_DSN` was unset and Docker was unreachable for user `golem`. + - I used the orchestrator's sanctioned fallback: a private PostgreSQL 18.6 cluster (`initdb`, ICU, trust auth) on 127.0.0.1:55494 in the scratchpad. + - `SMOKE_ADMIN_DSN` pointed at that cluster for every rehearsal. + - The cluster and a throwaway MariaDB were stopped and removed at the end. + - No temporary override touched any repo file. +- **pnpm.** pnpm ran with `TMPDIR` set to the scratchpad and the project-drive store (`npm_config_store_dir`), as in 16-01 and 16-02. +- **`@nuxt/fonts`.** Its metadata fetches are blocked in the sandbox. The builds succeed with warnings only. +- **Pre-existing state.** The host's untracked `.gsd/` was left alone. +- **Concurrent edits.** Another session has uncommitted STATE/ROADMAP edits (Phase 18.1). This plan's metadata commit stages only its own changes and leaves theirs in the working tree. + +## Known Stubs + +None. + +## Threat Flags + +None beyond the plan's threat model. T-16-10 through T-16-17 and T-16-SC are implemented as planned: + +- no new Go HTTP endpoint; +- no new Go or npm module (the plugin uses gorm, gocloud.dev, the framework and sm-journal-plugin, all already in the graph); +- templates carry empty secrets, which the negative grep confirms; +- the smoke drops only its `grzyby_smoke_` database. + +## User Setup Required + +None for this plan. `SMOKE_ADMIN_DSN` is optional (Docker or the user's own Postgres). The server values are confirmed in 16-04. + +## Next Phase Readiness + +**16-04 (staging deploy).** + +- On the server: + 1. Run the two SQL files after the checks in `scripts/php-export/README.md`. + 2. Copy the uploads, media and export. + 3. Follow the README runbook (migrate, `admin:create`, `migrate.sh`). + 4. Install the deploy files. + 5. Run `scripts/smoke.sh --url https://staging.grzybyfunkcjonalne.pl`. +- Decide on `first-journal-post` (published in the export, absent live). +- Swap `listen ... http2` for `http2 on;` if the server's nginx is 1.25.1 or newer. + +**16-05 (unit tests).** These can target: + +- `ReadFeaturedImageMap` and `ImportFeaturedImages` (mem:// bucket, idempotency, path escape, type allowlist and sniffing); +- `ReadCategoryMap`, `CategoryTranslations.UnmarshalJSON` and `ImportCategories` (unchanged detection); +- `validDiskName`, `flagOrDefault` and `flagOn`. + +## Self-Check: PASSED + +- FOUND: every `key-files.created` path in the host and the todo in summercms.go. +- FOUND: host commits `90f2dbd 5ccbc62 893cf42 a3266c3 3f17f1c 30233d5 81ffce3 07390b2 8f7f2de` on `origin/master`, and summercms.go `6ff7d5d`. +- Plan verification, re-run after the last commit: + - `check-deploy: ok`; + - `build: bin/grzybyfunkcjonalne and build/nuxt/.output ready`; + - `smoke: ok (95 urls)`; + - `go vet ./...` and `go test ./... -count=1` ok; + - the env.example negative grep passes; + - every one of the 71 live locs has its `200 PATH` line in `urls.txt`. + +--- +*Phase: 16-grzybyfunkcjonalne-pl-on-reusable-blog-views* +*Completed: 2026-10-09* diff --git a/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md b/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md index aba36fb..f6c48a0 100644 --- a/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md +++ b/.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md @@ -5,3 +5,11 @@ - **vue-journal-layer: visitor address lost under Nitro swr route caching.** `routeRules` swr renders a page from a copy of the request that keeps only `varies` headers, so the layer's `useJournalApi` reads no `x-forwarded-for` and forwards only the Nuxt peer. Every SSR cache miss would then share one Go rate-limit bucket (RESEARCH Pitfall 5). The site app works around it with `server/plugins/client-address.ts` (records the address in `event.context` on the Nitro `request` hook) and an app-level `useJournalApi` override. Fix it in the layer (a layer Nitro plugin plus the composable reading `event.context`), release a new tag, bump the app and delete the app override. The layer's `/rss.xml` and sitemap source have the same gap on their cache misses (once per TTL). - **vue-journal-layer smoke tool sends no Accept header.** Undici's `fetch` sends `sec-fetch-mode: cors`, so Nuxt answers errors with JSON and an expectation such as `404 /404 contains=...` cannot see the error page. The app works around it with `server/plugins/html-errors.ts`, which treats page paths as HTML requests. An `Accept: text/html` default in `testing/smoke-ssr.mjs` would let other apps check error copy without such a plugin. - **`public/images/cta.png` is 5.5 MB** (1792×1024 PNG, kept byte for byte from the live site as the plan required). It is the CTA band and blog hero background. A WebP derivative (the hero photo is 0.47 MB as WebP) would cut page weight; decide before the production cutover. + +## From 16-03 (sm-grzybyfunkcjonalne-app host, runbook and deploy) + +- **summercms.go: the generated main.go publishes no database for plugin commands.** `lagoon`, `cabana` and `surf serve` open the database themselves (`lagoon.OpenFromApp`), but a plugin's `pact.HasCommands` command finds no `*gorm.DB` on the app. So `journal:import-posts`, `journal:export-posts` and `user:token:*` stop with "database is not configured" in every `summer build` host (fonoteka.go's hand-written `app.go` publishes it). The host works around it with `grzyby:import-posts` and a `lookupDB` helper that open and publish the database through lagoon. Fix it in the framework, for example as a lazy publish before plugin command `Run`, so the wrapper can go. +- **sm-journal-plugin: `journal:import-posts` rejects `"translations": []`.** The PHP exporter writes a post without translations as `[]` (json_encode of an empty PHP array), and the Go `PostFile.Translations map[string]map[string]any` fails to unmarshal it ("Invalid JSON"). This affects 5 of the 105 grzybyfunkcjonalne export files; 2 of them are published, including the live post `ekstrakt-z-turkey-tail`. `scripts/migrate.sh` imports from a copy in which only that empty array becomes `{}`. The importer should accept an empty array as an empty map (PHP parity). +- **`first-journal-post` is published in the export but not on the live site.** "First journal post" was published 2026-02-23. It is absent from the live sitemap, so Go serves 63 published posts where the live site lists 62. Unpublish or delete it after the staging import (16-04), or confirm it should stay. +- **The UAT-host image references are not in the imported bodies.** The 2 `https://gf.uat.golem15.com/storage/...` references sit only in the base (older) content of `wprowadzenie-do-grzyba-cordyceps`. With pl as the default, the pl body replaces the base text, so the gap report shows 1 export file and 0 imported bodies. Nothing is broken on the new stack. The report keeps listing the export file. +- **nginx `listen ... http2` is deprecated on nginx 1.25.1 and newer** (a warning, not an error). The staging vhost keeps the summercms.io form because the PHP server's nginx version is unknown. Switch to `http2 on;` once 16-04 confirms the version.