From 02936b15bcf7b2dd21788d5dfce12a1582446d30 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 9 Oct 2026 17:53:00 +0200 Subject: [PATCH] docs(17): capture phase context --- .../phases/17-blog-companions/17-CONTEXT.md | 163 ++++++++++++++++++ .../17-blog-companions/17-DISCUSSION-LOG.md | 141 +++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 .planning/phases/17-blog-companions/17-CONTEXT.md create mode 100644 .planning/phases/17-blog-companions/17-DISCUSSION-LOG.md diff --git a/.planning/phases/17-blog-companions/17-CONTEXT.md b/.planning/phases/17-blog-companions/17-CONTEXT.md new file mode 100644 index 0000000..86a40dc --- /dev/null +++ b/.planning/phases/17-blog-companions/17-CONTEXT.md @@ -0,0 +1,163 @@ +# Phase 17: Blog companions - Context + +**Gathered:** 2026-10-09 +**Status:** Ready for planning + + +## Phase Boundary + +Port `Golem15.Sitemap`, `Golem15.Menu` and `Golem15.Faq` as shared plugins in their own repos: `sm-sitemap-plugin`, `sm-menu-plugin` and `sm-faq-plugin` under `git.golem15.com/golem15/`. The phase also adds four things: + +- **Framework (`summercms.go`):** a typed successor of the `pages.menuitem.listTypes`, `getTypeInfo` and `resolveItem` events, plus the cabana field and list features the three plugins' admin screens need. +- **`sm-journal-plugin`:** registers its menu item types through the new registry, as an additive change. +- **Nuxt:** two new layers, `vue-menu-layer` and `vue-faq-layer`. +- **grzybyfunkcjonalne.pl:** the site moves its menu, FAQ and sitemap from app code to the plugins. + +Out of scope: a pages plugin (Winter.Pages), the sitemanager and blocks plugins, and the production cutover. + + + + +## Implementation Decisions + +### Menu-item type registry (framework) +- **D-01:** The successor of `pages.menuitem.*` is an **optional capability interface**. It follows the `pact.Has*` pattern and could be named something like `HasMenuItemTypes`. A plugin returns typed `MenuItemType` values that carry their own info (label, reference options, nesting support) and a `Resolve` method. A framework registry collects them at boot. There are no stringly festival events for list, info or resolve. — **Reversibility:** costly — Journal, menu, sitemap and every later content plugin (pages, blog) implement this contract. +- **D-02:** **Headless URLs come from patterns** such as `/blog/:post` and `/:category`, resolved in three layers. Each type declares a default pattern. The host config can override the pattern per type, and that override is how grzyby keeps its P16 D-15 URLs. A menu item or sitemap item can override it once more. — **Reversibility:** costly — the config keys and item fields become a published plugin contract. +- **D-03:** **Resolved items carry per-locale alternates.** `Resolve` returns title, URL, mtime and a locale→URL alternates map, built from translate-plugin slugs. Nested `items` are returned for types that expand, such as `all-journal-categories`. Sitemap hreflang and localized menus both read these alternates. A single-locale site gets a single entry. — **Reversibility:** costly — the shape of the resolve result. +- **D-04:** **Journal registers its 5 PHP types under the PHP keys:** `journal-category`, `all-journal-categories`, `journal-post`, `all-journal-posts` and `category-journal-posts`. The source is `Plugin.php` `extendWinterPagesPlugin` in the frozen Journal tree. This is an additive change to `sm-journal-plugin` and leaves its existing API untouched. +- **D-05:** **Menu types are menu built-ins plus registry types.** + - `url` (link) and `divider` stay menu built-ins. + - Every content-backed type comes from the registry. + - PHP `blog_categories` maps to Journal's `all-journal-categories`. + - PHP `page` (Winter.Pages) is dropped. + - The import or DB copy maps the stored type values to the new ones. + - **Reversibility:** costly — stored type values in the menu tables. + +### Admin editing (framework first) +- **D-06:** Close the cabana gaps **in `summercms.go` this phase**, following the P15 markdown-field pattern: generic features with fail-loud schema validation, the README and `docs/` updated, the admin OpenAPI and generated TS types refreshed, and `dist/` committed in the same change. — **Reversibility:** costly — new field types in the typed admin schema that later plugins will use. +- **D-07:** **`repeater` field type.** It edits a JSON list stored in a jsonable column, with a nested `form.fields`. FAQ `content` (question/answer) uses it, and so do the sitemap Definition items (`data`). +- **D-08:** **A dropdown with options that depend on a sibling field** (Winter's `dependsOn`). Its options come from a provider keyed on the value of the sibling `type` field and are served through the registry's type info. The sitemap repeater rows and the menu entry form use this same type/reference binding, so the registry has exactly one admin binding. +- **D-09:** **Drag-and-drop tree reorder page.** It succeeds Winter's ReorderController and works on any `parent_id` + `sort_order` model, writing both columns. Menu entries use it, scoped to one menu. It replaces the PHP reorder screen and the "Children" partial. +- **D-10:** **`richeditor` field type (HTML WYSIWYG)** in cabana and the SPA, which folds in the todo `cabana-richeditor-field-type.md`. The server sanitizes on save, the way Journal's `classes.CheckHTML` does, and the field must not bypass that gate. FAQ `description` and the repeater `answer` use it, matching PHP. Journal's category `content` switches from `textarea` to `richeditor`, as that todo asks. This decision replaces an earlier choice of the markdown field: the markdown preview drops raw HTML, so it can't show the imported PHP FAQ HTML. — **Reversibility:** costly — a new field type plus a JS editor dependency in the admin SPA. + +### Sitemap headless adaptation +- **D-11:** **One definition per app.** The `theme` column stays so the schema is 1:1, filled with a fixed key. `/sitemap.xml` serves that single definition. The admin opens it directly, like a settings page, the same way PHP auto-created one definition per theme. +- **D-12:** **Item types are URL plus registry types only.** Static Page, All Static Pages and the CMS Page field are dropped. In the user's words: "no static pages or cms pages, just URL items, we configure it like that in wintercms sitemap plugin on horoskopia etc". `updates_manifest_path` is ported 1:1 as an optional setting. +- **D-13:** **Priority tiers stay, with configurable patterns.** `enable_priority_tiers` and the tier values port unchanged. The tier path patterns move from code to settings or host config. The PHP golem15.com patterns (`/uslugi`, `/realizacje`, legal pages and so on) become documented defaults or an example, not hardcoded behavior. A site configured the same way must produce the same output as PHP. +- **D-14:** `sitemap.xml` and `sitemap.xsl` keep their PHP behavior: + - the `frontend_url` rewrite of ``, of `xhtml:link href` and of the XSL href + - `frontend_default_locale` + - the 50,000 URL limit + - XML escaping + + The PHP `tests/security` cases (`XmlEscapingTest`, `MassAssignmentTest`) are a behavioral spec. The `golem15.sitemap.beforeAddItem` and `addItem` hooks become typed festival events (planner's call on shape). +- **D-15:** **grzybyfunkcjonalne.pl switches to the plugin's sitemap.** nginx routes `/sitemap.xml` and `/sitemap.xsl` to Go, the Nuxt `@nuxtjs/seo` sitemap module is disabled in the app (or layer config), and editors manage the sitemap in `/gfadmin`. This reverses **P16 D-13** for the sitemap only; per-post meta, OG, canonical and robots stay on `@nuxtjs/seo`. — **Reversibility:** reversible — the nginx route and the Nuxt module toggle. + +### Public APIs + Nuxt consumption +- **D-16:** **Menu API: `GET /_menu/api/v1/menus/{code}`**, mirroring `/_journal/api/v1`, anonymous. It returns the published, nested tree with registry types already resolved: URLs built, expanding types expanded into child items, plus title, target, css_class, icon and code. The Nuxt side only renders, and computes the active state from the current route. — **Reversibility:** one-way — a published API that the layers and later sites consume. +- **D-17:** **FAQ API: `GET /_faq/api/v1/faqs/{slug}?locale=pl`**, active rows only. It returns name, slug, description and content `[{question, answer}]`, with HTML sanitized server-side. The `locale_id` → translate `Locale` relation stays 1:1 (one FAQ row per locale), and the locale defaults to the app default. — **Reversibility:** one-way — published API. +- **D-18:** **Sibling Nuxt layers `vue-menu-layer` and `vue-faq-layer`**, new repos under `git.golem15.com/golem15/`. They use the same distribution as `vue-journal-layer` (P16 D-02 to D-04: pnpm git dependency pinned to a tag, a neutral design driven by CSS tokens, components the app can override). Each one mirrors one plugin, so a site without a blog can use them without Journal. Each ships a typed client and render components. — **Reversibility:** costly — repo and distribution layout. +- **D-19:** **grzyby data is copied from the PHP DB.** Tables keep their PHP names and columns, so rows for the menu, the FAQ (slug `what-mushrooms-can-give-you`) and the sitemap definition are copied from the live Winter DB as a scripted, ordered migration step, the same way P16 handles its import. Menu and sitemap type values are mapped (D-05). After the copy, the P16 typed menu config and the static FAQ content are removed from `vue-grzybyfunkcjonalne-app`, which then uses the new layers. + +### Claude's Discretion +- Go package, module and identifier names for the registry, and whether it lives in `pact` (interface) plus an existing or new module (registry), provided D-01 to D-03 hold and the module README and docs are updated. +- Config key names for the per-type URL patterns and for the priority tier patterns. +- The exact JSON field names in the menu and FAQ responses, provided they cover the fields listed in D-16 and D-17. +- Which JS WYSIWYG editor the SPA uses for `richeditor`. It must be stdlib-first-compatible and add no Go dependency; if an npm dependency is needed, name it in research. +- Plan count and split, subject to the plan-count checkpoint and "unit tests are the last plan". The framework work (registry plus cabana features) likely lands first and gets a framework tag. +- Whether the PHP sitemap `ImportCommand` JSON format is kept byte-compatible. Default: yes, port 1:1. +- Admin permissions and navigation for the three plugins follow the PHP `Plugin.php` registrations. +- Run the security-review agent: the phase adds public HTTP endpoints and an HTML-accepting field type. + +### Folded Todos +- `sitemap-plugin-port.md`: the Golem15.Sitemap 1:1 port, adapted per D-11 to D-15. +- `menu-plugin-port.md`: the Golem15.Menu port with a public read API, per D-05, D-09 and D-16. +- `faq-plugin-port.md`: the Golem15.Faq port with a read API by slug, per D-07, D-10 and D-17. +- `cabana-richeditor-field-type.md`: the cabana `richeditor` field, plus switching the Journal category content to it (D-10). + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Roadmap and decisions +- `.planning/ROADMAP.md` § Phase 17: goal, repos, port sources, success criteria +- `.planning/notes/core-plugins-own-repos.md`: the `sm--plugin` repo, module path, submodule and `go.work` layout, and the submodule commit workflow +- `.planning/notes/core-plugin-port-inventory.md`: port order and plugin sizes +- `.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/16-CONTEXT.md`: D-02 to D-04 (layer distribution), D-09 (`/gfadmin`, nginx split), D-12 (menu and FAQ in the app for now), D-13 (sitemap via `@nuxtjs/seo`, reversed for the sitemap by D-15 here), D-15 (URLs), D-17 and D-25 (migration ordering) +- `.planning/phases/15-journal-plugin/15-CONTEXT.md`: D-11 (the markdown-field framework-first pattern), D-14 (the `/_journal/api/v1` convention), D-21 to D-23 (local checkouts) + +### Todos (folded) +- `.planning/todos/pending/sitemap-plugin-port.md` +- `.planning/todos/pending/menu-plugin-port.md` +- `.planning/todos/pending/faq-plugin-port.md` +- `.planning/todos/pending/cabana-richeditor-field-type.md` + +### PHP port sources +- `/media/nvme/dev/golem15/fonoteka/plugins/golem15/sitemap/`: `models/Definition.php` (generation, events, frontend rewrite), `classes/DefinitionItem.php`, `routes.php`, `models/settings/fields.yaml`, `console/ImportCommand.php`, `assets/sitemap.xsl`, `tests/security/*`, `README.md` +- `/media/nvme/dev/golem15/grzybyfunkcjonalne.pl/plugins/golem15/menu/`: `models/{Menu,MenuEntry}.php`, `models/*/fields.yaml`, `updates/*` (schema), `components/Menu.php`, `controllers/menuentries/reorder_config.yaml` +- `/media/nvme/dev/golem15/grzybyfunkcjonalne.pl/plugins/golem15/faq/`: `models/FAQ.php` (jsonable `content`, `locale` belongsTo), `models/faq/fields.yaml`, `updates/v1.0.1/*` +- `/media/nvme/dev/golem15/grzybyfunkcjonalne.pl/themes/arsha/partials/g15_menu/default.htm`, `g15_faq/` and `g15_faq_whyus/`: the rendered markup the layers replace +- `/media/nvme/dev/golem15/fonoteka/plugins/golem15/journal/Plugin.php` `extendWinterPagesPlugin()`, plus `Category::getMenuTypeInfo/resolveMenuItem` and `Post::getMenuTypeInfo/resolveMenuItem`: the Journal type semantics (frozen tree, P15 D-01) + +### Framework +- `modules/pact/capabilities.go`: the `Has*` capability-interface pattern (D-01) +- `modules/festival/README.md`: typed events for the sitemap add-item hooks (D-14) +- `modules/cabana/README.md` and `modules/cabana/form_schema.go`: the field type set (`formFieldTypes`), widget and action contracts, fail-loud schema rules (D-06 to D-10) +- `modules/cabana/field_markdown.go`: the precedent for adding a field type + + + + +## Existing Code Insights + +### Reusable Assets +- `pact` `Has*` capability interfaces (`HasRoutes`, `HasAdminControllers`, `HasSettings`, `HasLang`, …): the model for the registry interface and for the plugins' wiring. +- `festival.Bus` (`Fire`, `Collect`, `UntilHandled`): for the sitemap `beforeAddItem`/`addItem` successors. +- cabana `relation`, `dropdown`, `switch`, `fileupload`, `markdown` and `mltext` fields, `HasSettings` settings pages, and the `type: widget` plus action contract. +- `sm-journal-plugin/classes/format_html.go` `CheckHTML`: the HTML sanitize gate that `richeditor` must keep. +- `sm-translate-plugin/models/locale.go` `Locale`: the FAQ `locale_id` relation and the slug alternates for D-03. +- `sm-journal-plugin/routes.go`: the `/_journal/api/v1` route, limiter and CORS pattern for `/_menu` and `/_faq`. +- `vue-journal-layer`: the layer structure and token conventions to copy for `vue-menu-layer` and `vue-faq-layer`. + +### Established Patterns +- Framework first: a new cabana field type updates the module README, `docs/`, admin OpenAPI, generated TS types and the committed SPA `dist/` in one change (P15 D-11). +- Plugins keep PHP table names, columns, permission codes and YAML, so the ports and DB copies stay 1:1. +- Framework READMEs never name a consuming application. + +### Integration Points +- `sm-grzybyfunkcjonalne-app` (`/media/nvme/dev/golem15/summercms.io/summercms/sm-grzybyfunkcjonalne-app/`) mounts the three new plugins as submodules at `plugins/golem15/{sitemap,menu,faq}`, adds them to `go.work`, `go.mod` and `summer.yaml`, and adds the nginx routes for `/sitemap.xml`, `/sitemap.xsl`, `/_menu` and `/_faq`. +- `vue-grzybyfunkcjonalne-app` extends `vue-menu-layer` and `vue-faq-layer`, and removes its P16 menu config, static FAQ and Nuxt sitemap module. +- `sm-journal-plugin` implements the registry interface (D-04) and switches the category content field to `richeditor` (D-10). + + + + +## Specific Ideas + +- Sitemap setup mirrors how the Winter sitemap plugin is configured on Horoskopia and the other PHP sites: URL items plus plugin types, with no CMS or static pages. +- Dependency note: Phase 16 is planned but not yet executed. D-15 and D-19 change Phase 16 deliverables (the Nuxt sitemap, the app menu config, the static FAQ) after it lands, so Phase 17 plans must run after Phase 16 execution. +- Concurrency note: during this discussion, another session was editing `modules/cabana` and `modules/lagoon` for quick task 261009-mys (backend user subtitle and avatar). Framework plans here touch `modules/cabana` too and should start from a clean tree. + + + + +## Deferred Ideas + +- Pages plugin (Winter.Pages successor) and its Static Page menu and sitemap types: dropped here per D-12. A future pages port would register through the D-01 registry. +- Sitemap definitions keyed per host, for multi-domain apps. +- FAQ index listing endpoint (`GET /faqs`). + +### Reviewed Todos (not folded) +- `journal-author-profile-fields.md`: belongs to the Journal author API work and quick task 261009-mys, not this phase. +- `nest-framework-packages-under-modules.md`: keyword match only; Phase 10.2 covers it. +- The remaining matches (READMEs, scaffold, wristband, lagoon, benchmark, translate JSON editor): keyword matches only. + + + +--- + +*Phase: 17-blog-companions* +*Context gathered: 2026-10-09* diff --git a/.planning/phases/17-blog-companions/17-DISCUSSION-LOG.md b/.planning/phases/17-blog-companions/17-DISCUSSION-LOG.md new file mode 100644 index 0000000..67eb9cb --- /dev/null +++ b/.planning/phases/17-blog-companions/17-DISCUSSION-LOG.md @@ -0,0 +1,141 @@ +# Phase 17: Blog companions - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-10-09 +**Phase:** 17-blog-companions +**Areas discussed:** Menu-item type registry, Admin editing gaps, Sitemap headless adaptation, Public APIs + Nuxt consumption + +--- + +## Menu-item type registry + +| Option | Description | Selected | +|--------|-------------|----------| +| Capability interface | Optional `Has*` interface returning typed MenuItemType with Info()/Resolve(); registry collects at boot | ✓ | +| Typed festival events | Three typed events on the festival bus, the closest mirror of PHP | | +| Both | Interface plus filter events | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Config default + item override | Type default pattern, host-config override per type, then per-item override | ✓ | +| Per-item pattern only | Every item stores its own pattern | | +| Host config only | No per-item override | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Built-ins + registry | url/divider built in; content types from the registry; blog_categories → all-journal-categories; page dropped | ✓ | +| Registry only | Even url and divider registered | | +| Keep PHP type values | Map at read time | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Yes, in the contract | Resolve returns locale→URL alternates | ✓ | +| No, sitemap computes it | Prefixing as in PHP | | + +--- + +## Admin editing gaps + +| Option | Description | Selected | +|--------|-------------|----------| +| Framework first | Generic cabana repeater and tree reorder | ✓ | +| Plugin widgets | Bespoke custom elements per plugin | | +| Simplify screens | Relation managers, FAQ schema change | | + +Rich text, first answer: + +| Option | Description | Selected | +|--------|-------------|----------| +| Markdown field, keep HTML data | Reuse the P15 markdown field | ✓ (later revised) | +| New richeditor field | WYSIWYG field in cabana | | +| Convert HTML → markdown on import | Lossy risk | | + +Rich text, revisited after finding that the markdown preview drops raw HTML (todo `cabana-richeditor-field-type.md`): + +| Option | Description | Selected | +|--------|-------------|----------| +| Add richeditor this phase | Fold the todo; FAQ and the Journal category use it | ✓ | +| Markdown + HTML→md on import | Convert during the DB copy | | +| Textarea for now | Raw HTML textarea | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Generic repeater + dependent dropdown | One type/reference binding shared by sitemap and menu | ✓ | +| Dedicated sitemap widget | Plugin custom element | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Drag-and-drop tree reorder | ReorderController successor with re-parenting | ✓ | +| Flat reorder + parent dropdown | Sibling-only reorder | | +| Nested repeater on Menu | Whole menu as one repeater | | + +--- + +## Sitemap headless adaptation + +| Option | Description | Selected | +|--------|-------------|----------| +| One definition per app | Keep the theme column with a fixed key | ✓ | +| Keyed by site/host | Multi-domain | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Configurable pattern list | Tier patterns from settings/config | ✓ | +| Port verbatim | Hardcoded golem15.com patterns | | +| Drop tiers | Per-item priority only | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Keep @nuxtjs/seo for grzyby | Plugin proven by golden tests only | | +| Switch grzyby to the plugin | nginx routes the sitemap to Go; reverses P16 D-13 | ✓ | +| Mount, decide at cutover | Both on staging | | + +| Option | Description | Selected | +|--------|-------------|----------| +| URL items + updates manifest | | | +| URL items only | | | +| App-registered type | | | + +**User's choice:** free text: "no static pages or cms pages, just URL items, we configure it like that in wintercms sitemap plugin on horoskopia etc" + +--- + +## Public APIs + Nuxt consumption + +| Option | Description | Selected | +|--------|-------------|----------| +| Resolved tree by code | GET /_menu/api/v1/menus/{code}, server-resolved | ✓ | +| Raw entries | Nuxt resolves types | | + +| Option | Description | Selected | +|--------|-------------|----------| +| By slug + locale | GET /_faq/api/v1/faqs/{slug}?locale= | ✓ | +| By slug, locale via header | Accept-Language | | +| List + by slug | Add an index endpoint | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Sibling layers | vue-menu-layer, vue-faq-layer | ✓ | +| Inside vue-journal-layer | | | +| One vue-companions-layer | | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Copy from PHP DB | Scripted copy with type mapping; remove the P16 app content | ✓ | +| Seed from P16 app content | | | +| Re-enter in admin | | | + +**Notes:** The grzyby sitemap definition is copied in the same step (grzyby runs the PHP sitemap plugin). + +--- + +## Claude's Discretion + +- Registry package and identifier names, config key names, JSON field names, the WYSIWYG JS editor choice, plan split, the ImportCommand format (default: 1:1), permissions and navigation following PHP. + +## Deferred Ideas + +- Pages plugin and the Static Page types; per-host sitemap definitions; the FAQ index endpoint. +- Todos reviewed but not folded: `journal-author-profile-fields.md`, `nest-framework-packages-under-modules.md`, other keyword-only matches.