Files
summercms/.planning/phases/04-cli-scaffolding-i18n-and-mail/04-DISCUSSION-LOG.md
2026-09-18 02:14:25 +02:00

208 lines
7.7 KiB
Markdown

# Phase 4: CLI scaffolding, i18n and mail - 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-09-17 / 2026-09-18
**Phase:** 04-cli-scaffolding-i18n-and-mail
**Areas discussed:** Lang file & plural syntax, Mail template format, Scaffold stub behavior, Mail drivers & test send
---
## Lang file & plural syntax
### Plural message form
| Option | Description | Selected |
|--------|-------------|----------|
| CLDR category map | `{one, few, many, other}` YAML map; 1:1 onto go-i18n; covers Polish's 4th category | |
| Laravel pipe strings | `":count wpis\|:count wpisy\|:count wpisów"` plus `{0}`/`[2,*]` ranges, per summer-phrasebook | |
| Both accepted | Map = CLDR categories; string with `\|` = Laravel-style | ✓ |
**Notes:** Fonoteka PHP lang files contain no pipe plurals; the pipe form is for line-by-line ports of other WinterCMS plugins.
### Parameter placeholders
| Option | Description | Selected |
|--------|-------------|----------|
| `:name` | WinterCMS/Laravel style with `:Name`/`:NAME` variants; phrasebook substitutes, go-i18n only selects plural category | ✓ |
| `{{.Name}}` go-i18n native | Every ported string rewritten | |
| You decide | | |
### Missing key
| Option | Description | Selected |
|--------|-------------|----------|
| Fallback chain, then raw key | requested → parent → fallback → key string; logged once in dev/test | ✓ |
| Fallback chain, then error | `(string, error)` or panic in dev | |
| You decide | | |
### Default locale
| Option | Description | Selected |
|--------|-------------|----------|
| Config-driven, en default | `app.locale` / `app.fallback_locale`, framework default `en` | ✓ |
| Config-driven, pl default | | |
| You decide | | |
---
## Mail template format
### File shape
| Option | Description | Selected |
|--------|-------------|----------|
| YAML front matter + body | `---` header parsed with goccy/go-yaml | |
| Keep WinterCMS INI + `==` | Exactly the PHP file shape; small hand-written header parser | ✓ |
| Go registration + body-only files | Subject/layout declared in Go | |
### Body format
| Option | Description | Selected |
|--------|-------------|----------|
| Markdown, add a renderer | html/template for vars, then Markdown → HTML; raw Markdown is the text part; one new dependency (goldmark expected) | ✓ |
| Plain HTML only | Zero new deps; six templates hand-converted; no text part | |
| HTML now, Markdown later | Body-renderer seam, decide at Phase 12/13 | |
### Per-locale suffix
| Option | Description | Selected |
|--------|-------------|----------|
| Suffix lookup with fallback | postcard tries `name-<locale>` then `name` | |
| Caller picks the full name | Exactly like PHP; no locale logic in postcard | ✓ |
| You decide | | |
### Layouts
| Option | Description | Selected |
|--------|-------------|----------|
| Registered by short name, HTML + text | Mirrors `registerMailLayouts` and the three-section PHP layout; framework ships `default` | ✓ |
| HTML wrapper only | Drops the text section | |
| You decide | | |
---
## Scaffold stub behavior
### Wiring
| Option | Description | Selected |
|--------|-------------|----------|
| Auto-wire via generated registry file | Per-plugin `registry.gen.go`; plugin.go never rewritten | ✓ |
| Edit plugin.go in place | go/ast insertion | |
| Write file, print the line to add | | |
### make:model output
| Option | Description | Selected |
|--------|-------------|----------|
| Model + migration, flag to skip | `--no-migration` | ✓ |
| Model only | | |
| Model + migration + fields.yaml/columns.yaml | No consumer until Phase 9 | |
### make:admin-controller before Phase 9
| Option | Description | Selected |
|--------|-------------|----------|
| Minimal interface + placeholder YAML | Tiny pact interface, WinterCMS-shaped YAML placeholders | ✓ |
| Go stub only, no YAML | | |
| You decide | | |
### Stub templates
| Option | Description | Selected |
|--------|-------------|----------|
| Embedded text/template .tmpl files | `internal/build/stubs`, go/format, compile test in examples/hello | ✓ |
| Keep Go string builders | | |
| You decide | | |
### make:job before River
| Option | Description | Selected |
|--------|-------------|----------|
| Framework job interface, River-shaped | `Kind()` on args, `Work(ctx, args) error`; no River import | ✓ |
| Import River now | River enters go.mod six phases early | |
| You decide | | |
### File layout inside a plugin
| Option | Description | Selected |
|--------|-------------|----------|
| Flat, one package, name suffixes | fonoteka.go Phase 3 style; no intra-plugin imports | |
| WinterCMS-style subpackages | models/, controllers/, console/, jobs/, updates/ as packages | ✓ (via /gsd-explore) |
| Flat Go, WinterCMS dirs for non-Go assets | | |
**User's choice:** Deferred to `/gsd-explore` ("This is a difficult one"), then decided: WinterCMS-style subpackages because agents converting golem15/jz/pxpx plugins lean on the PHP **directory**, not the class graph. Added rule: `models/` is a leaf package (user agreed "first option seems the best"), casts move in, service-calling hooks become callbacks from `classes/`/`plugin.go`, enforced by `summer build`. Evidence: Fonoteka has 40 `classes→models` edges and exactly 1 `models→classes` edge (a cast and a hook in `Album.php`). Recorded in `.planning/notes/plugin-layout-winter-directories.md`.
### Coexistence with hand-written plugins
| Option | Description | Selected |
|--------|-------------|----------|
| Opt-in, merged by plugin.go | Hand-written plugins call generated accessors; fonoteka.go untouched | ✓ |
| Migrate existing plugins in this phase | Touches fonoteka.go, out of phase scope | |
| You decide | | |
### Argument shape
| Option | Description | Selected |
|--------|-------------|----------|
| `make:model <vendor.plugin> <Name>` | Plugin ID inferred from plugin.go when inside a plugin dir | ✓ |
| Always explicit, no inference | | |
| You decide | | |
---
## Mail drivers & test send
### Drivers
| Option | Description | Selected |
|--------|-------------|----------|
| smtp + log + memory | go-mail, logger, in-memory slice | ✓ |
| smtp + memory only | | |
| smtp only | | |
### Test send
| Option | Description | Selected |
|--------|-------------|----------|
| Unit test + SMTP integration test | memory driver unit test plus Mailpit container test gated like testcontainers Postgres | ✓ |
| Unit test with memory driver only | | |
| `summer mail:test` command | Manual verification | |
### Send API
| Option | Description | Selected |
|--------|-------------|----------|
| Mailer service on the container | `postcard.Mailer.Send(ctx, Message{Template, To, Vars})` | ✓ |
| Typed message per template | | |
| You decide | | |
### Failures
| Option | Description | Selected |
|--------|-------------|----------|
| Error returned, unknown template fails at boot | Consistent with Phase 1 D-10 | ✓ |
| Error returned, lazy template check | | |
| You decide | | |
---
## Claude's Discretion
- phrasebook API surface, aliases/overrides, request-context seam for Phase 7
- Markdown renderer pick (research) and escaping of substituted values
- registry.gen.go naming and accessor names; how the models-leaf check locates packages
- What make:plugin scaffolds beyond Phase 1's minimum in the new layout
- make:command naming under the plugin's colon prefix
- Mailpit image and API access
- Plan count and split
## Deferred Ideas
- Restructure Phase 3 fonoteka.go plugins into the directory layout — Phase 5
- Validate the models-leaf rule on keios.eu / jz / pxpx plugins — todo `verify-models-leaf-rule`
- `summer mail:test` production smoke command
- Attachments on `postcard.Message`