docs(14.2.1): lock Go tables to golem15_translate_*

Execute-phase Task 2 chose golem15-prefix over the researched winter_translate_* names so the plugin ships vendor tables; PHP winter names stay a later import mapping.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-10-06 11:36:28 +02:00
parent 28bd45fa04
commit bd0a27f59f
9 changed files with 73 additions and 72 deletions

View File

@@ -838,7 +838,7 @@ Plans:
**Repos:** `sm-translate-plugin` (new); `sm-grzybyfunkcjonalne-app` (proof host); `summercms.go` for ML field types and the surf locale seam
**Success Criteria** (what must be TRUE):
1. Plugin repo exists with `winter_translate_*` schema (locales, attributes, indexes, empty messages table), Locale model, en/pl seed, and context-safe Translator.
1. Plugin repo exists with `golem15_translate_*` schema (locales, attributes, indexes, empty messages table), Locale model, en/pl seed, and context-safe Translator.
2. Translatable API (`Translatable()`, `WithLocale`, get/set, default-locale fallback) and permissioned Locales admin work; a fixture model saves and reads `en`+`pl`.
3. Cabana `markdown`/`mltext`/`mlmarkdown` compose; nested locale writes are not dropped; docs, OpenAPI, TS types and `boardwalk` `dist/` update in the same change.
4. Proof host `sm-grzybyfunkcjonalne-app` boots with user+translate; unit tests are the last plan.
@@ -848,7 +848,7 @@ Plans:
Plans:
**Wave 1**
- [ ] 14.2.1-01-PLAN.md — Plugin repo, `winter_translate_*` schema, Locale, en/pl seed, Translator, surf Resolver
- [ ] 14.2.1-01-PLAN.md — Plugin repo, `golem15_translate_*` schema, Locale, en/pl seed, Translator, surf Resolver
**Wave 2** *(blocked on Wave 1 completion)*
- [ ] 14.2.1-02-PLAN.md — Translatable API, Locales admin, fixture save/read

View File

@@ -207,6 +207,7 @@ Progress: [███████░░░] 67%
Decisions are logged in PROJECT.md Key Decisions table.
Recent decisions affecting current work:
- [Phase 14.2.1]: D-10 Go tables are `golem15_translate_*` (execute Task 2 `golem15-prefix`, 2026-10-06). PHP pin still uses `winter_translate_*`; do not copy those names into Go DDL.
- [Phase 14.1]: 14.1-01 Task 1 `ship-shared-full` — create `golem15_user_oauth_identities` in the shared sm-user-plugin with the full PHP column set (locked D-01 and D-07).
- [Phase 14.1]: Identity Index/Destroy are host-mounted on the JWT group; the user plugin does not register them under `/_user`. `/me` unrestricted `collection_ids` is JSON null (D-09); `scopes` stay `[]`.
- [Phase 14.1]: Corpus is 175/175/0 with zero pending routes. Live Nuxt Connected accounts and fonoteka-mcp `me()` stay Phase 15 UAT.

View File

@@ -12,10 +12,10 @@ files_modified:
- ../sm-translate-plugin/models/registry.go
- ../sm-translate-plugin/models/locale.go
- ../sm-translate-plugin/updates/registry.go
- ../sm-translate-plugin/updates/202610060001_create_winter_translate_locales.go
- ../sm-translate-plugin/updates/202610060002_create_winter_translate_attributes.go
- ../sm-translate-plugin/updates/202610060003_create_winter_translate_indexes.go
- ../sm-translate-plugin/updates/202610060004_create_winter_translate_messages.go
- ../sm-translate-plugin/updates/202610060001_create_golem15_translate_locales.go
- ../sm-translate-plugin/updates/202610060002_create_golem15_translate_attributes.go
- ../sm-translate-plugin/updates/202610060003_create_golem15_translate_indexes.go
- ../sm-translate-plugin/updates/202610060004_create_golem15_translate_messages.go
- ../sm-translate-plugin/updates/202610060005_seed_en_pl_locales.go
- ../sm-translate-plugin/classes/translator.go
- modules/surf/locale_resolver.go
@@ -26,7 +26,7 @@ requirements: [D-01, D-02, D-03, D-04, D-07, D-08, D-10, D-12, D-15, D-16]
must_haves:
truths:
- "D-01/D-02/D-03/D-04: implementation is derived only from the read-only PHP tree at SHA 725d547ec839f02b5fdc0f0a6faaed601a414d50; no PHP file changes."
- "D-10: gormigrate creates final `winter_translate_locales`, `winter_translate_attributes`, `winter_translate_indexes`, and empty `winter_translate_messages`; it creates no rainlab-era or `golem15_translate_*` tables."
- "D-10: gormigrate creates final `golem15_translate_locales`, `golem15_translate_attributes`, `golem15_translate_indexes`, and empty `golem15_translate_messages`; it creates no rainlab-era or `winter_translate_*` tables."
- "D-08: an idempotent seed creates enabled `en` as default at sort_order 1 and enabled non-default `pl` as Polski at sort_order 2, and never seeds `de`."
- "D-07/D-12: a backpack-published resolver selects URL prefix, valid user preferred_locale, remembered `golem15.translate.locale`, cookie-gated Accept-Language, then default, and surf stores only a validated code on context."
- "KERN-07: no package-level mutable current locale exists; `Translator.Locale(ctx)` and `towel.WithLocale` carry request state."
@@ -35,12 +35,12 @@ must_haves:
- path: "../sm-translate-plugin/plugin.go"
provides: "compiled Plugin registration, migrations/models/config, Resolver publication"
contains: "golem15.translate"
- path: "../sm-translate-plugin/updates/202610060001_create_winter_translate_locales.go"
- path: "../sm-translate-plugin/updates/202610060001_create_golem15_translate_locales.go"
provides: "squashed Locale schema"
contains: "winter_translate_locales"
- path: "../sm-translate-plugin/updates/202610060004_create_winter_translate_messages.go"
contains: "golem15_translate_locales"
- path: "../sm-translate-plugin/updates/202610060004_create_golem15_translate_messages.go"
provides: "empty compatibility Messages table without admin"
contains: "winter_translate_messages"
contains: "golem15_translate_messages"
- path: "../sm-translate-plugin/classes/translator.go"
provides: "context-only Translator and request Resolver"
contains: "func ("
@@ -93,7 +93,7 @@ Phase 14.2.1 has no mapped REQUIREMENTS.md IDs, so requirement probing is visibl
## Artifacts this phase produces
- Module `git.golem15.com/golem15/sm-translate-plugin`, package `translate`, `Plugin.ID() == "golem15.translate"`.
- `models.Locale`, model and migration registries, five gormigrate entries, and exact `winter_translate_*` tables.
- `models.Locale`, model and migration registries, five gormigrate entries, and exact `golem15_translate_*` tables.
- `classes.Translator`, `Translator.Locale(context.Context)`, and a request resolver implementing surf's `LocaleResolver`.
- Surf optional resolver seam with unchanged fallback behavior when the translate plugin is absent.
- Application-neutral plugin and surf README updates for exported contracts.
@@ -122,22 +122,22 @@ Phase 14.2.1 has no mapped REQUIREMENTS.md IDs, so requirement probing is visibl
</task>
<task type="checkpoint:decision" gate="blocking-human">
<name>Task 2: Confirm the one-way Winter table contract</name>
<name>Task 2: Confirm the one-way table contract</name>
<files>../sm-translate-plugin/updates/</files>
<read_first>.planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md (D-10), .planning/phases/14.2.1-translate-plugin/14.2.1-RESEARCH.md (Tables, columns, indexes), /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/updates/version.yaml, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/models/Locale.php</read_first>
<action>Record the selected schema contract before migrations are implemented. Research resolved D-10's provisional prefix against the frozen source: the only compatible selection is the final `winter_translate_*` table family. This gate records the one-way import/storage choice; it does not reopen the PHP pin or permit a JSON-column alternative.</action>
<action>Record the selected schema contract before migrations are implemented. Execute-phase Task 2 locked `golem15-prefix`: Go tables are `golem15_translate_*` with the PHP pin's columns and indexes. PHP still uses `winter_translate_*`; do not copy those names into Go DDL. This gate does not reopen the PHP pin or permit a JSON-column alternative.</action>
<decision>Which persistent table contract should this shared plugin publish?</decision>
<context>Once released and imported by Journal, renaming these tables requires coordinated data migrations in every host. The frozen PHP pin explicitly reads `winter_translate_*` after its RainLab rename history.</context>
<context>Once released and imported by Journal, renaming these tables requires coordinated data migrations in every host. The frozen PHP pin reads `winter_translate_*`; the Go plugin deliberately uses `golem15_translate_*`.</context>
<options>
<option id="winter-final">
<name>Squash directly to the four final winter_translate_* tables</name>
<pros>Matches SHA 725d547, Winter imports, and Journal cutover.</pros>
<cons>The names become a shared persistent contract.</cons>
<cons>Not the locked Go contract.</cons>
</option>
<option id="golem15-prefix">
<name>Use provisional golem15_translate_* names</name>
<pros>Vendor-style naming.</pros>
<cons>Contradicts the frozen source and breaks direct imports.</cons>
<name>Use golem15_translate_* names</name>
<pros>Vendor-style naming; locked D-10 execute decision.</pros>
<cons>Winter import needs a later mapping; PHP source still says winter_translate_*.</cons>
</option>
<option id="host-json">
<name>Store translations on host-model JSON columns</name>
@@ -151,22 +151,22 @@ Phase 14.2.1 has no mapped REQUIREMENTS.md IDs, so requirement probing is visibl
</verify>
<acceptance_criteria>
- The response is recorded in the summary.
- Task 3 starts only for `winter-final`; either alternative stops execution as a locked-decision conflict.
- Task 3 starts only for `golem15-prefix`; `host-json` stops as a locked-decision conflict. `winter-final` is the discarded research recommendation.
</acceptance_criteria>
<resume-signal>Select `winter-final` to implement the locked/researched contract, or select an alternative to stop.</resume-signal>
<resume-signal>Select `golem15-prefix` (locked). `winter-final` is no longer the implementation path.</resume-signal>
</task>
<task type="tracer">
<name>Task 3: Resolve one URL-prefixed request through the plugin, enabled Locale row, surf, and context</name>
<reversibility rating="one-way">The four table names and columns become the import target; changing them after release requires data migrations in every host.</reversibility>
<precondition>Tasks 1 and 2 completed, with schema option `winter-final`.</precondition>
<files>../sm-translate-plugin/go.mod, ../sm-translate-plugin/plugin.go, ../sm-translate-plugin/README.md, ../sm-translate-plugin/config/config.yaml, ../sm-translate-plugin/models/registry.go, ../sm-translate-plugin/models/locale.go, ../sm-translate-plugin/updates/registry.go, ../sm-translate-plugin/updates/202610060001_create_winter_translate_locales.go, ../sm-translate-plugin/updates/202610060002_create_winter_translate_attributes.go, ../sm-translate-plugin/updates/202610060003_create_winter_translate_indexes.go, ../sm-translate-plugin/updates/202610060004_create_winter_translate_messages.go, ../sm-translate-plugin/updates/202610060005_seed_en_pl_locales.go, ../sm-translate-plugin/classes/translator.go, modules/surf/locale_resolver.go, modules/surf/router.go, modules/surf/README.md</files>
<precondition>Tasks 1 and 2 completed, with schema option `golem15-prefix`.</precondition>
<files>../sm-translate-plugin/go.mod, ../sm-translate-plugin/plugin.go, ../sm-translate-plugin/README.md, ../sm-translate-plugin/config/config.yaml, ../sm-translate-plugin/models/registry.go, ../sm-translate-plugin/models/locale.go, ../sm-translate-plugin/updates/registry.go, ../sm-translate-plugin/updates/202610060001_create_golem15_translate_locales.go, ../sm-translate-plugin/updates/202610060002_create_golem15_translate_attributes.go, ../sm-translate-plugin/updates/202610060003_create_golem15_translate_indexes.go, ../sm-translate-plugin/updates/202610060004_create_golem15_translate_messages.go, ../sm-translate-plugin/updates/202610060005_seed_en_pl_locales.go, ../sm-translate-plugin/classes/translator.go, modules/surf/locale_resolver.go, modules/surf/router.go, modules/surf/README.md</files>
<read_first>../fonoteka.go/plugins/golem15/user/go.mod, ../fonoteka.go/plugins/golem15/user/plugin.go, ../fonoteka.go/plugins/golem15/user/models/registry.go, ../fonoteka.go/plugins/golem15/user/updates/registry.go, ../fonoteka.go/plugins/golem15/user/updates/00_base.go, modules/backpack/app.go, modules/surf/router.go, modules/surf/locale_from_principal.go, modules/towel/context.go, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/Translator.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/LocaleMiddleware.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/ApiLocaleMiddleware.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/config/config.php, .planning/phases/14.2.1-translate-plugin/14.2.1-PATTERNS.md</read_first>
<action>Implement the production tracer and the complete Plan 01 scope.
Create the Go 1.27 module using only existing GORM/gormigrate/framework dependencies. Register the compiled plugin in `init`, expose config/models/migrations, and publish a framework-owned `surf.LocaleResolver` in Boot. Keep `Requires()` empty so user-preference lookup is optional.
Implement exact squashed DDL for `winter_translate_locales`, `winter_translate_attributes`, `winter_translate_indexes`, and `winter_translate_messages`, including all final columns and PHP-indexed columns from RESEARCH. Messages is DDL only and remains empty. Use explicit gormigrate up/down; never AutoMigrate and never create rainlab or provisional-prefixed tables. Seed only en/pl idempotently with D-08 values.
Implement exact squashed DDL for `golem15_translate_locales`, `golem15_translate_attributes`, `golem15_translate_indexes`, and `golem15_translate_messages`, including all final columns and PHP-indexed columns from RESEARCH. Messages is DDL only and remains empty. Use explicit gormigrate up/down; never AutoMigrate and never create rainlab or `winter_translate_*` tables. Seed only en/pl idempotently with D-08 values.
Implement `Locale` with no timestamps, table name, code/name rules, and Fillable limited to code/name/is_enabled. Implement Translator/Resolver with the exact D-07 order: enabled URL first segment; valid principal preferred_locale; remembered `golem15.translate.locale`; Accept-Language only when browser detection is enabled and `locale_manually_set` is absent; enabled default. The manual cookie is a flag with value 1, never a locale. Validate every candidate against enabled rows. URL-prefix handling strips only a valid enabled prefix before routing and sets the manual flag using configured expiry. Provide the API resolver variant preferred → Accept-Language → default without persistence. All current-locale access takes context.
@@ -176,7 +176,7 @@ Add the surf interface and optional lookup without importing the plugin. When no
<fails_when>Non-zero exit, any package reports build failed, or either test command reports FAIL.</fails_when>
</verify>
<acceptance_criteria>
- All four migration files contain their exact `winter_translate_*` table name; no source file contains `golem15_translate_` or `rainlab_translate_`.
- All four migration files contain their exact `golem15_translate_*` table name; no source file contains `winter_translate_` or `rainlab_translate_`.
- Seed source contains en and pl with en default/enabled and pl enabled/non-default, and contains no Deutsch seed.
- Plugin module/ID/package match D-15 and plugin Boot publishes the framework resolver interface.
- Resolver order is URL → principal → remembered locale → cookie-gated Accept-Language → default.
@@ -217,7 +217,7 @@ Run the Task 3 command. Confirm `git diff -- /media/nvme/dev/golem15/fonoteka/pl
<success_criteria>
- The plugin and surf packages compile and pass short tests.
- Exact final tables and en/pl seed are implemented through gormigrate.
- Exact final `golem15_translate_*` tables and en/pl seed are implemented through gormigrate.
- Full resolver order is implemented without process-wide locale state.
- No deferred Messages UI, CMS components, AI/theme commands, import/export, or PHP edits are introduced.
</success_criteria>

View File

@@ -27,7 +27,7 @@ requirements: [D-05, D-08, D-09, D-10, D-11, D-12, D-17]
must_haves:
truths:
- "D-09/D-12: a host model declares `Translatable() []string`, `TranslatableIndexes() []string`, and `MorphName() string`; callers use exported get/set and `WithLocale` APIs."
- "D-10: default-locale values remain in host columns; each non-default locale is one JSON object row in `winter_translate_attributes`, and indexed values are mirrored to `winter_translate_indexes`."
- "D-10: default-locale values remain in host columns; each non-default locale is one JSON object row in `golem15_translate_attributes`, and indexed values are mirrored to `golem15_translate_indexes`."
- "D-11: a missing non-default value reads the host model's default-locale field."
- "D-05: Locales is ordinary cabana CRUD, permission-gated by exactly `golem15.translate.manage_locales`; no Messages controller, permission, or navigation exists."
- "Locale form writes only code/name/is_enabled; is_default and sort_order cannot be mass-assigned, while a permissioned make-default action preserves disabled/default lifecycle guards."
@@ -47,11 +47,11 @@ must_haves:
contains: "locale:"
key_links:
- from: "../sm-translate-plugin/classes/translatable.go"
to: "winter_translate_attributes"
to: "golem15_translate_attributes"
via: "non-default SetTranslated upserts one locale/model row"
pattern: "attribute_data"
- from: "../sm-translate-plugin/classes/translatable.go"
to: "winter_translate_indexes"
to: "golem15_translate_indexes"
via: "indexed fields upsert locale/model/item/value"
pattern: "TranslatableIndexes"
- from: "../sm-translate-plugin/controllers/locales.go"
@@ -99,10 +99,10 @@ No REQUIREMENTS.md IDs are mapped to this phase. Probe predicates are intentiona
<task type="tracer">
<name>Task 1: Save one fixture title in en and pl, then read pl through the exported API</name>
<files>../sm-translate-plugin/classes/translatable.go, ../sm-translate-plugin/classes/translatable_smoke_test.go, ../sm-translate-plugin/models/attribute.go, ../sm-translate-plugin/models/index.go, ../sm-translate-plugin/models/registry.go</files>
<read_first>/media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/behaviors/TranslatableModel.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/TranslatableBehavior.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/tests/unit/behaviors/TranslatableModelTest.php, ../sm-translate-plugin/models/locale.go, ../sm-translate-plugin/updates/202610060002_create_winter_translate_attributes.go, ../sm-translate-plugin/updates/202610060003_create_winter_translate_indexes.go, ../fonoteka.go/plugins/golem15/user/models/user.go (MorphName), .planning/phases/14.2.1-translate-plugin/14.2.1-PATTERNS.md (Translatable assignment)</read_first>
<action>Implement the minimum stable D-09/D-12 API without Eloquent-style magic. Define a host-model contract with `Translatable() []string` and `MorphName() string`, plus an explicit indexed-field contract `TranslatableIndexes() []string`. Reject fields not declared by the model and locale codes not enabled in `winter_translate_locales`.
<read_first>/media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/behaviors/TranslatableModel.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/TranslatableBehavior.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/tests/unit/behaviors/TranslatableModelTest.php, ../sm-translate-plugin/models/locale.go, ../sm-translate-plugin/updates/202610060002_create_golem15_translate_attributes.go, ../sm-translate-plugin/updates/202610060003_create_golem15_translate_indexes.go, ../fonoteka.go/plugins/golem15/user/models/user.go (MorphName), .planning/phases/14.2.1-translate-plugin/14.2.1-PATTERNS.md (Translatable assignment)</read_first>
<action>Implement the minimum stable D-09/D-12 API without Eloquent-style magic. Define a host-model contract with `Translatable() []string` and `MorphName() string`, plus an explicit indexed-field contract `TranslatableIndexes() []string`. Reject fields not declared by the model and locale codes not enabled in `golem15_translate_locales`.
`SetTranslated` writes the default locale directly to the host model field/column and writes non-default values to one `(locale, model_id, model_type)` `winter_translate_attributes` row as a JSON object keyed by field. Use explicit model primary-key extraction and explicit exported-field/GORM-column mapping; do not use `reflect.Type.String()` as morph identity. Attribute rows have no public controller.
`SetTranslated` writes the default locale directly to the host model field/column and writes non-default values to one `(locale, model_id, model_type)` `golem15_translate_attributes` row as a JSON object keyed by field. Use explicit model primary-key extraction and explicit exported-field/GORM-column mapping; do not use `reflect.Type.String()` as morph identity. Attribute rows have no public controller.
`Translated` returns the host field for default locale; for another enabled locale it reads the JSON key and, when absent, returns the default host field per D-11. Keep an explicit locale argument even when context has a UI locale.
@@ -113,7 +113,7 @@ Create a neutral fixture model only in the smoke test, migrate its host table ex
</verify>
<acceptance_criteria>
- Exported interfaces and functions compile from an external test package.
- The default English value is in the fixture host row, not duplicated into `winter_translate_attributes`.
- The default English value is in the fixture host row, not duplicated into `golem15_translate_attributes`.
- The Polish value is present under `attribute_data.title` in exactly one pl/model row.
- Undeclared fields and unenabled locale codes return errors without database writes.
- Test fixture is test-only and no production fixture plugin is globally registered.
@@ -125,7 +125,7 @@ Create a neutral fixture model only in the smoke test, migrate its host table ex
<name>Task 2: Add indexed locale lookup, WithLocale query scope, and default fallback</name>
<files>../sm-translate-plugin/classes/translatable.go, ../sm-translate-plugin/classes/translatable_smoke_test.go, ../sm-translate-plugin/README.md</files>
<read_first>../sm-translate-plugin/classes/translatable.go, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/behaviors/TranslatableModel.php (scopeTransWhere and index writes), /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal/models/Post.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal/models/Category.php, .planning/phases/14.2.1-translate-plugin/14.2.1-RESEARCH.md (Minimum exported Go API)</read_first>
<action>Expand from the proven save/read path. `WithLocale(ctx, db, locale)` validates the explicit locale and carries it on the GORM statement context; it must not mutate global state. Add an indexed equality helper/scope that joins `winter_translate_indexes` by locale, morph name, model id, and item, while default-locale lookup uses the host column and non-default lookup falls back to that host column only when no translated index match exists, matching the frozen PHP behavior.
<action>Expand from the proven save/read path. `WithLocale(ctx, db, locale)` validates the explicit locale and carries it on the GORM statement context; it must not mutate global state. Add an indexed equality helper/scope that joins `golem15_translate_indexes` by locale, morph name, model id, and item, while default-locale lookup uses the host column and non-default lookup falls back to that host column only when no translated index match exists, matching the frozen PHP behavior.
When SetTranslated writes a declared indexed field, upsert the corresponding index row atomically with the attribute JSON update. Rewriting one locale/field must preserve other translated fields in the same JSON object. Empty translated values stay explicit empty values rather than silently borrowing fallback during admin editing; ordinary `Translated` still applies D-11 fallback.
@@ -136,7 +136,7 @@ Document only the exported identifiers that exist, MorphName's import-stability
</verify>
<acceptance_criteria>
- `WithLocale` returns a GORM handle carrying an explicit validated locale on context and has no singleton mutation.
- Setting an indexed Polish slug writes one matching `winter_translate_indexes` row.
- Setting an indexed Polish slug writes one matching `golem15_translate_indexes` row.
- Polish indexed lookup finds the fixture; default-locale lookup uses the host slug.
- Missing Polish title returns English under normal reads per D-11.
- README names every exported API identifier accurately and remains application-neutral.
@@ -166,7 +166,7 @@ Port the plugin.* and locale.* UI phrases to `lang/en/lang.yaml` and `lang/pl/la
- Unknown/unprivileged admin gets 403; privileged admin gets the Locales schema/list.
- A crafted body cannot persist is_default or sort_order.
- The permissioned make-default action rejects disabled locales and atomically leaves exactly one enabled default locale.
- English/Polish phrasebook files are independent from `winter_translate_attributes`.
- English/Polish phrasebook files are independent from `golem15_translate_attributes`.
</acceptance_criteria>
<done>Authorized administrators can manage the lean Locale surface while default-state and phrasebook/model-translation boundaries remain enforced.</done>
</task>

View File

@@ -26,7 +26,7 @@ autonomous: true
requirements: [D-01, D-02, D-03, D-04, D-05, D-06, D-07, D-08, D-09, D-10, D-11, D-12, D-13, D-14, D-15, D-16, D-17]
must_haves:
truths:
- "A real-Postgres integration test migrates all four winter_translate tables, activates user+translate+fixture, saves en/pl through cabana ML fields, reads via WithLocale, and reaches Locales admin."
- "A real-Postgres integration test migrates all four golem15_translate tables, activates user+translate+fixture, saves en/pl through cabana ML fields, reads via WithLocale, and reaches Locales admin."
- "Migration up/down verifies every final column/index, empty Messages table, idempotent en/pl seed, absence of de, and complete rollback."
- "Translator tests prove URL → preferred_locale → remembered locale → cookie-gated Accept-Language → default, invalid-code rejection, API order, plugin-absent surf behavior, and concurrent request isolation."
- "Translatable tests prove default-row storage, non-default JSON, default fallback, indexed lookup, undeclared-field rejection, invalid-locale rejection, and atomic preservation of sibling fields."
@@ -38,7 +38,7 @@ must_haves:
contains: "TestTranslateEndToEnd"
- path: "../sm-translate-plugin/updates/migrations_test.go"
provides: "real Postgres migration up/down and seed proof"
contains: "winter_translate_messages"
contains: "golem15_translate_messages"
- path: "modules/surf/locale_resolver_test.go"
provides: "resolver-present/absent and request-isolation tests"
contains: "TestLocaleResolver"
@@ -154,7 +154,7 @@ Expand host `TestBootUserTranslate` to prove both actual gitlink plugins activat
<task type="auto">
<name>Task 2: Complete migration, Translator, Translatable, admin, ML, markdown, and SPA test matrices</name>
<files>../sm-translate-plugin/updates/migrations_test.go, ../sm-translate-plugin/classes/translator_test.go, ../sm-translate-plugin/classes/translatable_test.go, ../sm-translate-plugin/locales_admin_test.go, modules/surf/locale_resolver_test.go, modules/cabana/ml_test.go, modules/cabana/markdown_test.go, modules/cabana/openapi_conformance_test.go, admin/tests/form/MLFields.test.ts, admin/tests/form/MarkdownField.test.ts</files>
<read_first>/media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/tests/unit/behaviors/TranslatableModelTest.php, ../sm-translate-plugin/integration_test.go, ../sm-translate-plugin/updates/202610060001_create_winter_translate_locales.go, ../sm-translate-plugin/classes/translator.go, ../sm-translate-plugin/classes/translatable.go, modules/surf/locale_from_principal_test.go, modules/cabana/form_schema_test.go, modules/cabana/field_permission.go, admin/tests/form/DatepickerField.test.ts, admin/tests/form/registry.test.ts</read_first>
<read_first>/media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/tests/unit/behaviors/TranslatableModelTest.php, ../sm-translate-plugin/integration_test.go, ../sm-translate-plugin/updates/202610060001_create_golem15_translate_locales.go, ../sm-translate-plugin/classes/translator.go, ../sm-translate-plugin/classes/translatable.go, modules/surf/locale_from_principal_test.go, modules/cabana/form_schema_test.go, modules/cabana/field_permission.go, admin/tests/form/DatepickerField.test.ts, admin/tests/form/registry.test.ts</read_first>
<action>Complete the decision and security matrix without adding unrelated PHP-suite surfaces.
Migration tests: assert every final table, column type/default, PHP-indexed column, empty Messages row count, no rainlab/provisional tables, idempotent seed, en/pl exact flags/order/names, no de, and rollback removes all four tables in reverse-safe order. Re-migrate after rollback.

View File

@@ -33,19 +33,19 @@ Out of scope: the Messages catalogue and `manage_messages` admin, locale-picker
### Model API
- **D-09:** A Go model declares translatable attributes with a `Translatable() []string` (or equivalent tagged list) the way PHP uses `$translatable`. Journal copies the PHP Post/Category lists. — **Reversibility:** costly — every translatable model in shared plugins will call this.
- **D-10:** Storage uses the same tables and columns as this PHP pin (`golem15_translate_*` attributes/indexes/locales — researcher confirms exact names from the frozen updates). No JSON-column alternative. — **Reversibility:** one-way — Winter import and Journal cutover expect those tables.
- **D-10:** Go storage tables are `golem15_translate_locales`, `golem15_translate_attributes`, `golem15_translate_indexes`, and `golem15_translate_messages`, with the PHP pin's columns and indexes. No JSON-column alternative. PHP at SHA `725d547` uses `winter_translate_*`; do not copy those names into Go DDL. Winter import needs a later mapping. Locked at execute-phase Task 2 (2026-10-06) as `golem15-prefix`. — **Reversibility:** one-way — Journal and every host migrate against these names.
- **D-11:** A missing translation falls back to the default locale, as PHP does.
- **D-12:** The exported API other plugins call this phase is the minimum: `WithLocale` on queries, get/set an attribute in a locale, and current locale from Translator. Do not 1:1 every PHP `TranslatableModel` / `Translator` method name unless research shows Journal needs it.
### Where it boots
- **D-13:** The host is `sm-grzybyfunkcjonalne-app` (same as Phase 15 D-18). This phase mounts `sm-user-plugin` + `sm-translate-plugin`; Journal is added in Phase 15. — **Reversibility:** one-way — submodule layout lands in that app.
- **D-14:** Local app checkout: `/media/nvme/dev/golem15/summercms.io/summercms/sm-grzybyfunkcjonalne-app/` (sibling of `summercms.go`).
- **D-15:** The user creates the public Gitea remote `git@git.golem15.com:golem15/sm-translate-plugin.git` as a manual plan step (it does not exist yet). Module path `git.golem15.com/golem15/sm-translate-plugin`, package `translate`, plugin ID `golem15.translate`.
- **D-15:** The public Gitea remote is `git@git.golem15.com:golem15/sm-translate-plugin.git` (created 2026-10-06). Module path `git.golem15.com/golem15/sm-translate-plugin`, package `translate`, plugin ID `golem15.translate`.
- **D-16:** Local plugin checkout: `/media/nvme/dev/golem15/summercms.io/summercms/sm-translate-plugin/` (sibling of `summercms.go`). The host mounts it as a submodule at `plugins/golem15/translate` with a `go.work` replace.
- **D-17:** Phase proof: the app boots with user+translate; Locales admin works; a fixture model can save and read `en`+`pl` through ML fields and `WithLocale`.
### Claude's Discretion
- Exact `golem15_translate_*` table/column names and migration IDs, taken from the frozen PHP updates (D-10), not invented.
- Exact `golem15_translate_*` Go table names (D-10 execute lock); column/index shapes still come from the frozen PHP updates, not invented.
- ML field YAML type names and SPA locale-switcher chrome, provided they follow cabana fail-loud rules and update the module README, `docs/`, admin OpenAPI, generated TS types and committed `dist/` in the same change.
- How `mlmarkdown` shares implementation with Phase 15's markdown field (land the shared primitive here if Journal is otherwise blocked).
- Plan count and split, subject to the plan-count checkpoint and "unit tests are the last plan".
@@ -85,7 +85,7 @@ Out of scope: the Messages catalogue and `manage_messages` admin, locale-picker
- `docs/backend/forms.md`, `docs/backend/admin-controllers.md`
### Remotes
- `git@git.golem15.com:golem15/sm-translate-plugin.git` — **does not exist yet**; user creates it, public
- `git@git.golem15.com:golem15/sm-translate-plugin.git` — public, created; local sibling checkout on `main`
- `git@git.golem15.com:golem15/sm-grzybyfunkcjonalne-app.git` — empty proof host, already created
- `git@git.golem15.com:golem15/sm-journal-plugin.git` — public, empty; mounted in Phase 15

View File

@@ -15,7 +15,7 @@ Path roots:
All analog paths below are git-tracked (`git ls-files` in `summercms.go`, inside the user submodule, inside `sm-bm-app`, and inside the PHP plugin). No gitignored mirror is named. `modules/boardwalk/dist`, `admin/openapi/admin.json` and `admin/src/api/schema.d.ts` are generated outputs: regenerate them, never hand-edit.
**Table names (locked by RESEARCH against the frozen PHP models):** `winter_translate_locales`, `winter_translate_attributes`, `winter_translate_indexes`, `winter_translate_messages`. CONTEXT D-10's `golem15_translate_*` placeholder is wrong. Do not invent `golem15_translate_*`.
**Table names (locked D-10 execute-phase 2026-10-06):** Go tables are `golem15_translate_locales`, `golem15_translate_attributes`, `golem15_translate_indexes`, `golem15_translate_messages`. PHP at SHA 725d547 still uses `winter_translate_*`; copy column/index shape from PHP, not the Winter table names. Do not emit `winter_translate_*` or `rainlab_translate_*` in Go DDL.
Suggested plan split from RESEARCH (present at the plan-count checkpoint; unit tests last): **14.2.1-01** plugin repo + squashed schema + Locale + Translator + surf Resolver seam; **14.2.1-02** Translatable API + Locales admin + fixture; **14.2.1-03** cabana `markdown`/`mltext`/`mlmarkdown` + SPA + proof host boot; **14.2.1-04** unit/integration tests last.
@@ -37,10 +37,10 @@ Suggested plan split from RESEARCH (present at the plan-count checkpoint; unit t
| `TR/models/locale.go` | model | CRUD | `USR/models/user_group.go` (Fillable/Rules/TableName) + `PHP/models/Locale.php` (guards) | exact |
| `TR/models/attribute.go` | model | CRUD | `USR/models/api_token.go` shape + `PHP/models/Attribute.php` fillable | exact |
| `TR/updates/registry.go` | utility | — | `USR/updates/registry.go` | exact |
| `TR/updates/202610060001_create_winter_translate_locales.go` | migration | CRUD | `USR/updates/00_base.go` + `USR/updates/202610020001_create_user_groups.go` (CREATE + seed style) | exact |
| `TR/updates/202610060002_create_winter_translate_attributes.go` | migration | CRUD | `USR/updates/00_base.go` | exact |
| `TR/updates/202610060003_create_winter_translate_indexes.go` | migration | CRUD | `USR/updates/00_base.go` | exact |
| `TR/updates/202610060004_create_winter_translate_messages.go` | migration | CRUD | `USR/updates/00_base.go` (DDL only; no Message admin) | exact |
| `TR/updates/202610060001_create_golem15_translate_locales.go` | migration | CRUD | `USR/updates/00_base.go` + `USR/updates/202610020001_create_user_groups.go` (CREATE + seed style) | exact |
| `TR/updates/202610060002_create_golem15_translate_attributes.go` | migration | CRUD | `USR/updates/00_base.go` | exact |
| `TR/updates/202610060003_create_golem15_translate_indexes.go` | migration | CRUD | `USR/updates/00_base.go` | exact |
| `TR/updates/202610060004_create_golem15_translate_messages.go` | migration | CRUD | `USR/updates/00_base.go` (DDL only; no Message admin) | exact |
| `TR/updates/202610060005_seed_en_pl_locales.go` | migration | CRUD | `USR/updates/202610020001_create_user_groups.go` INSERT + `PHP/updates/v1.3.1/seed_all_tables.php` and `v2.4.0/seed_additional_locales.php` | exact |
| `TR/classes/translator.go` | service | request-response | `PHP/classes/Translator.php` + `LocaleMiddleware.php` + `ApiLocaleMiddleware.php` (contract) and `USR/plugin.go` Boot `app.Publish` | role-match |
| `TR/classes/translatable.go` | service | CRUD | `PHP/behaviors/TranslatableModel.php` + `classes/TranslatableBehavior.php` (no Go translatable analog) | partial |
@@ -221,7 +221,7 @@ public $timestamps = false;
// makeDefault: cannot make a disabled locale default
```
Copy: `TableName() "winter_translate_locales"`, no `CreatedAt`/`UpdatedAt`, Fillable **only** `code`, `name`, `is_enabled`. `is_default` and `sort_order` are **not** fillable (PHP). Rules: `code` required, `name` required. Port delete/unset/disabled-default as `FormBeforeDelete` / `FormBeforeUpdate` returning `&cabana.ValidationError{Details: ...}` (422) — analog `USR/controllers/usergroups_admin_controller.go` 162-175.
Copy: `TableName() "golem15_translate_locales"`, no `CreatedAt`/`UpdatedAt`, Fillable **only** `code`, `name`, `is_enabled`. `is_default` and `sort_order` are **not** fillable (PHP). Rules: `code` required, `name` required. Port delete/unset/disabled-default as `FormBeforeDelete` / `FormBeforeUpdate` returning `&cabana.ValidationError{Details: ...}` (422) — analog `USR/controllers/usergroups_admin_controller.go` 162-175.
`isValid` = code in enabled list (`PHP/models/Locale.php` 228-232). Default locale: `is_default` true row; seed `en` as default.
@@ -231,7 +231,7 @@ Copy: `TableName() "winter_translate_locales"`, no `CreatedAt`/`UpdatedAt`, Fill
**Contract:** `PHP/models/Attribute.php` lines 15-34 — table `winter_translate_attributes`, fillable `locale`, `model_type`, `model_id`, `attribute_data`, `$guarded = ['*']`.
**Go analog:** `USR/models/user_group.go` TableName + Register. No public Attribute controller. Writes only through Translatable helpers (RESEARCH T-SEC). Optional: omit a Message model entirely and only DDL `winter_translate_messages` (RESEARCH §9). If Attribute exists, Fillable matches PHP; never expose an admin controller.
**Go analog:** `USR/models/user_group.go` TableName + Register. No public Attribute controller. Writes only through Translatable helpers (RESEARCH T-SEC). Optional: omit a Message model entirely and only DDL `golem15_translate_messages` (RESEARCH §9). If Attribute exists, Fillable matches PHP; never expose an admin controller. Go `TableName()` is `golem15_translate_attributes` even though PHP's `$table` is `winter_translate_attributes`.
---
@@ -269,7 +269,7 @@ func init() { Register(migrations...) }
| `winter_translate_indexes` | `id`, `locale` indexed, `model_id` indexed nullable, `model_type` indexed nullable, `item` indexed nullable, `value TEXT` nullable |
| `winter_translate_messages` | `id`, `code` indexed nullable, `message_data TEXT` nullable, `found BOOLEAN DEFAULT TRUE`, `code_pre_2_1_0 TEXT` indexed nullable — create empty; no Messages admin |
Suggested IDs (planner may adjust date prefix, not table names): `202610060001_create_winter_translate_locales` … `202610060005_seed_en_pl_locales`.
Suggested IDs (planner may adjust date prefix, not table names): `202610060001_create_golem15_translate_locales` … `202610060005_seed_en_pl_locales`.
**Seed contract:** `PHP/updates/v1.3.1/seed_all_tables.php` 16-22 (`en` / English / default / enabled) and `PHP/updates/v2.4.0/seed_additional_locales.php` 13-20 (`pl` / Polski / not default / enabled / `sort_order` 2). Set `en.sort_order = 1`. **Do not seed `de`.** Idempotent: insert by `code` if missing (`$exists` check lines 33-36).

View File

@@ -71,9 +71,9 @@
Phase 14.2.1 ports the lean core of `Golem15.Translate` at SHA `725d547ec839f02b5fdc0f0a6faaed601a414d50` so Journal (Phase 15) can keep translatable fields. The frozen tree is present at `/media/nvme/dev/golem15/fonoteka/plugins/golem15/translate` and matches the pin. Do not clone a second copy.
**Primary recommendation:** Create `sm-translate-plugin` on the user-plugin mount pattern; squash PHP's rainlab→winter rename history into four final Postgres tables named `winter_translate_*` (not `golem15_translate_*`); export `Translatable() []string`, `WithLocale`, get/set-in-locale, and Translator current locale; land cabana `markdown` plus `mltext`/`mlmarkdown` in this phase so Phase 15 is not blocked; replace surf's Accept-Language-only house locale middleware with a backpack-published Translator when the plugin is mounted.
**Primary recommendation:** Create `sm-translate-plugin` on the user-plugin mount pattern; squash PHP's rainlab→winter rename history into four final Postgres tables named `golem15_translate_*` (not PHP's `winter_translate_*`); export `Translatable() []string`, `WithLocale`, get/set-in-locale, and Translator current locale; land cabana `markdown` plus `mltext`/`mlmarkdown` in this phase so Phase 15 is not blocked; replace surf's Accept-Language-only house locale middleware with a backpack-published Translator when the plugin is mounted.
PHP table names in this pin are `winter_translate_locales`, `winter_translate_attributes`, `winter_translate_indexes`, and `winter_translate_messages`. CONTEXT D-10's `golem15_translate_*` placeholder is wrong against the frozen source. Use the verified `winter_translate_*` names so a Winter import and Journal cutover see the same tables.
PHP table names in this pin are `winter_translate_locales`, `winter_translate_attributes`, `winter_translate_indexes`, and `winter_translate_messages`. **Execution override (2026-10-06):** D-10 is locked to Go names `golem15_translate_*` with the PHP column/index shapes. Do not emit `winter_translate_*` in Go DDL. A Winter import will need a mapping later.
## Architectural Responsibility Map
@@ -115,8 +115,8 @@ Do not use these. CONTEXT locked the storage, locale order, and YAML types.
| Instead of | Could Use | Why rejected |
|------------|-----------|--------------|
| `winter_translate_*` tables | JSON columns on host models | D-10 forbids it |
| `winter_translate_*` | Invented `golem15_translate_*` | Frozen models use `winter_translate_*` |
| `golem15_translate_*` tables | JSON columns on host models | D-10 forbids it |
| `golem15_translate_*` | PHP's `winter_translate_*` copied into Go | Execute lock 2026-10-06; Winter import is a later mapping |
| URL/session/cookie/default | Accept-Language only | D-07 forbids reducing to it |
| Cabana `mltext`/`mlmarkdown` | Plugin `type: widget` | Field types belong in cabana (12.1 pattern) |
@@ -194,10 +194,10 @@ From `updates/version.yaml` [VERIFIED: `updates/version.yaml:1-155`]:
Go migration IDs: follow the user-plugin pattern `"202609170001_create_users"` [VERIFIED: `../fonoteka.go/plugins/golem15/user/updates/00_base.go:13`]. Suggested squash IDs (planner may adjust date prefix, not the table names):
- `202610060001_create_winter_translate_locales`
- `202610060002_create_winter_translate_attributes`
- `202610060003_create_winter_translate_indexes`
- `202610060004_create_winter_translate_messages`
- `202610060001_create_golem15_translate_locales`
- `202610060002_create_golem15_translate_attributes`
- `202610060003_create_golem15_translate_indexes`
- `202610060004_create_golem15_translate_messages`
- `202610060005_seed_en_pl_locales`
Postgres types: `SERIAL` PK, `TEXT` for strings, `BOOLEAN`, `INTEGER` for `sort_order`, `TEXT` for `attribute_data`/`message_data`/`value` (PHP mediumText). Add btree indexes on the PHP-indexed columns (`code`, `name`, `locale`, `model_id`, `model_type`, `item`).
@@ -505,7 +505,7 @@ Four plans. Present this count at the plan-count checkpoint. Unit tests last.
| Plan | One-line scope |
|------|----------------|
| **14.2.1-01** | Plugin repo + squashed `winter_translate_*` schema (all four tables) + Locale model + en/pl seed + Translator + surf Resolver seam. Manual: Gitea remote + local checkouts. |
| **14.2.1-01** | Plugin repo + squashed `golem15_translate_*` schema (all four tables) + Locale model + en/pl seed + Translator + surf Resolver seam. Manual: Gitea remote + local checkouts. |
| **14.2.1-02** | Translatable API (`Translatable`, `WithLocale`, get/set, fallback, indexes) + Locales admin (`manage_locales`, YAML, phrasebook) + fixture save/read. |
| **14.2.1-03** | Cabana `markdown` + `mltext` + `mlmarkdown` (SPA switcher, nested save, docs/OpenAPI/TS/`dist/`) + proof host `sm-grzybyfunkcjonalne-app` boots user+translate. |
| **14.2.1-04** | Unit/integration tests last: Translator order, Translatable fallback, admin authz, ML save, migrations up/down. |
@@ -526,13 +526,13 @@ Public/Admin HTTP
Admin Locales CRUD
→ cabana.CRUDService (permission golem15.translate.manage_locales)
→ winter_translate_locales
→ golem15_translate_locales
Admin Journal-like form (fixture / Phase 15)
→ mltext / mlmarkdown controls
→ default locale → host table columns
→ other locales → winter_translate_attributes.attribute_data JSON
→ indexed fields → winter_translate_indexes
→ other locales → golem15_translate_attributes.attribute_data JSON
→ indexed fields → golem15_translate_indexes
Journal query (Phase 15)
→ Translator.Locale(ctx) or WithLocale(..., "pl")
@@ -578,8 +578,8 @@ Framework touches: `modules/surf/router.go` locale seam; `modules/cabana/form_sc
### Anti-Patterns to Avoid
- **JSON-column "simpler" storage:** violates D-10 and Winter import.
- **Renaming tables to `golem15_translate_*`:** frozen PHP uses `winter_translate_*`.
- **JSON-column "simpler" storage:** violates D-10 and PHP attribute-row semantics.
- **Copying PHP `winter_translate_*` names into Go DDL:** D-10 is locked to `golem15_translate_*`; Winter import needs a later mapping.
- **Translator singleton holding the request locale:** violates KERN-07.
- **Accept-Language as the only resolver:** violates D-07; also stop stuffing the raw header into context when Resolver runs.
- **Plugin-owned `type: widget` ML controls:** field types belong in cabana.
@@ -603,10 +603,10 @@ Framework touches: `modules/surf/router.go` locale seam; `modules/cabana/form_sc
## Common Pitfalls
### Pitfall 1: Wrong table prefix
**What goes wrong:** Creating `golem15_translate_locales` because CONTEXT guessed that name.
**Why:** D-10 asked the researcher to confirm; models say `winter_translate_*`.
**How to avoid:** Use the verified names in every migration and query.
**Warning signs:** `Schema::hasTable('winter_translate_locales')` in Translator.
**What goes wrong:** Creating `winter_translate_locales` in Go because PHP models use that name.
**Why:** Execute-phase D-10 is locked to `golem15_translate_*`; PHP `winter_translate_*` stays a mapping concern.
**How to avoid:** Use `golem15_translate_*` in every Go migration and query.
**Warning signs:** Go source containing `winter_translate_` or `rainlab_translate_`.
### Pitfall 2: Nested ML save dropped
**What goes wrong:** SPA posts `{title: {en, pl}}`; cabana drops maps; only default locale saves.
@@ -680,7 +680,7 @@ DATA_r3t6y0ab_END
| Old Approach (PHP) | Current Approach (Go) | When | Impact |
|--------------------|----------------------|------|--------|
| RainLab then Winter table names | Squash to `winter_translate_*` | 2.0.0 rename | Do not emit rainlab tables |
| RainLab then Winter table names | Squash to `golem15_translate_*` | 2.0.0 rename in PHP; Go never emits rainlab or winter table names | Winter import needs a later mapping |
| Eloquent `$implement` behavior | Go interface + GORM callbacks | this port | Explicit `Translatable()` |
| Winter formwidget swap at runtime | Explicit YAML `mltext`/`mlmarkdown` | compiled admin | Journal YAML names ML types |
| Translator singleton | `towel.WithLocale` + backpack service | KERN-07 | No request globals |
@@ -753,7 +753,7 @@ No REQUIREMENTS.md IDs were mapped (TBD). Map locked decisions:
| D-07 | URL prefix wins; invalid code ignored; session/cookie flag; not header-only | unit | `go test ./classes -run TestTranslatorResolve -count=1` | ❌ Wave 0 |
| D-08 | Seed inserts `en` (default, enabled) and `pl` (enabled, not default); not `de` | integration | `go test ./updates -run TestSeedEnPl -count=1` | ❌ Wave 0 |
| D-09/D-12 | `Translatable()` + get/set + `WithLocale` | unit | `go test ./classes -run TestTranslatableGetSet -count=1` | ❌ Wave 0 |
| D-10 | Tables `winter_translate_locales\|attributes\|indexes\|messages` exist after migrate | integration | `go test ./updates -run TestTranslateTables -count=1` | ❌ Wave 0 |
| D-10 | Tables `golem15_translate_locales\|attributes\|indexes\|messages` exist after migrate | integration | `go test ./updates -run TestTranslateTables -count=1` | ❌ Wave 0 |
| D-11 | Missing `pl` falls back to default locale column | unit | `go test ./classes -run TestFallbackDefaultLocale -count=1` | ❌ Wave 0 |
| D-17 | Host/plugin Activate with user+translate | smoke | `go test ./... -run TestBootUserTranslate -count=1` | ❌ Wave 0 |
| T-SEC-01 | Unlisted locale never stored | unit | `go test ./classes -run TestInvalidLocaleRejected -count=1` | ❌ Wave 0 |

View File

@@ -45,7 +45,7 @@ created: "2026-10-06"
| 14.2.1-W0 | 04 | 0 | D-07 | T-14.2.1-02 | URL prefix wins; invalid code ignored; session/cookie flag; not header-only | unit | `go test ./classes -run TestTranslatorResolve -count=1` | ❌ W0 | ⬜ pending |
| 14.2.1-W0 | 04 | 0 | D-08 | — | Seed inserts `en` (default, enabled) and `pl` (enabled, not default); not `de` | integration | `go test ./updates -run TestSeedEnPl -count=1` | ❌ W0 | ⬜ pending |
| 14.2.1-W0 | 04 | 0 | D-09/D-12 | — | `Translatable()` + get/set + `WithLocale` | unit | `go test ./classes -run TestTranslatableGetSet -count=1` | ❌ W0 | ⬜ pending |
| 14.2.1-W0 | 04 | 0 | D-10 | — | Tables `winter_translate_locales\|attributes\|indexes\|messages` exist after migrate | integration | `go test ./updates -run TestTranslateTables -count=1` | ❌ W0 | ⬜ pending |
| 14.2.1-W0 | 04 | 0 | D-10 | — | Tables `golem15_translate_locales\|attributes\|indexes\|messages` exist after migrate | integration | `go test ./updates -run TestTranslateTables -count=1` | ❌ W0 | ⬜ pending |
| 14.2.1-W0 | 04 | 0 | D-11 | — | Missing `pl` falls back to default locale column | unit | `go test ./classes -run TestFallbackDefaultLocale -count=1` | ❌ W0 | ⬜ pending |
| 14.2.1-W0 | 04 | 0 | D-17 | — | Host/plugin Activate with user+translate | smoke | `go test ./... -run TestBootUserTranslate -count=1` | ❌ W0 | ⬜ pending |
| 14.2.1-W0 | 04 | 0 | T-SEC-01 | T-14.2.1-03 | Unlisted locale never stored | unit | `go test ./classes -run TestInvalidLocaleRejected -count=1` | ❌ W0 | ⬜ pending |