--- phase: quick-261006-t5r plan: 01 subsystem: cabana forms / admin SPA tags: [preset, mltext, slug, admin-spa, forms] status: complete requires: [] provides: - "fields.yaml preset on text and mltext targets with text or mltext sources" - "SPA preset follow per locale (formState.presetUpdates, changedLocales)" affects: - sm-journal-plugin (post and category slug presets; YAML snippet below, not applied here) tech-stack: added: [] patterns: - "Pure preset-follow rules in formState.ts; FormView only tracks edits and the active ML locale" key-files: created: - modules/cabana/preset_ml_test.go modified: - modules/cabana/form_schema.go - modules/cabana/phase121_form_test.go - modules/cabana/phase121_schema_boot_test.go - admin/src/components/form/formState.ts - admin/src/views/FormView.vue - admin/tests/form/formState.test.ts - admin/tests/form/FormView.test.ts - modules/boardwalk/dist/index.html - modules/boardwalk/dist/assets/index-DPCIeFlT.js - modules/cabana/schema_types.go - modules/cabana/admin_openapi.go - admin/openapi/admin.json - admin/src/api/schema.d.ts - modules/cabana/README.md - docs/backend/forms.md decisions: - "ML -> ML presets follow per locale: only locales whose source text changed are rewritten, each from its own locale" - "Hand edits of an ML preset target are tracked per locale; editing locale L stops only L" - "A preset never writes a locale the administrator neither typed in nor sees: text -> mltext writes only the active locale" - "mltext -> text follows the source's active-locale string (FormView tracks the active ML locale via ML_LOCALE_CHANGE)" - "mlmarkdown stays out: server admits only text/mltext targets and sources" metrics: duration: "~13 min" completed: 2026-10-06 estimate: tokens: 70000 tasks: 3 actuals: tokens: 13500 tasks: 3 commits: 3 plan_head_before: 7e2b1a909bb24eae22662a9edea23fec38cc0cd1 plan_head_after: 593217214809f8b8f45a7c2d2ea9ffd311ff2bfe --- # Quick 261006-t5r: Make SPA slug preset follow mltext source Summary fields.yaml `preset` now works on `mltext` fields: cabana accepts text/mltext targets and sources, and the admin SPA follows an mltext title into an mltext slug per locale, stopping only the locale the administrator edits by hand. ## What changed **Task 1 - cabana (7afad73, `feat(cabana): accept preset on mltext fields`)** - `compilePresetKey` admits `type: text` or `type: mltext`; anything else fails with `preset is only valid on type: text or mltext`. - `checkPresets` admits a text or mltext source; otherwise `field %s: preset field %s must be a text or mltext field`. - New DB-free `TestPresetML` (RED confirmed before the change) covers mltext->mltext (mapping, short form, exact), text->mltext, mltext->text, and the refusals (mlmarkdown and markdown targets, mlmarkdown source). The Journal-shaped field serializes with `"type":"mltext"` and `"preset":{"field":"title","type":"slug"}`. - `TestPresetSchema` / `TestPhase121BootErrors` rows updated to the new messages (testcontainers Postgres, green). Settings and relation forms still refuse `preset`. **Task 2 - admin SPA (b5d8506, `feat(admin): follow presets on mltext fields per locale`)** - `formState.ts`: `changedLocales(previous, next)` and `presetUpdates(fields, source, previous, next, values, activeLocale, isEdited)` (plus `PresetEdited` type) hold every preset-follow rule; all values still go through `presetValue`, so slug/exact remain one implementation. - `FormView.vue`: `update()` reads the previous value, records per-locale hand edits of ML fields in `editedLocales`, and on create merges `presetUpdates` into the next values. `mlLocale` follows the window `ML_LOCALE_CHANGE` event, starts on the first enabled locale after load, and resets to it when `activeTab` changes. Update mode unchanged. - Tests: 6 new formState unit cases, 4 new FormView cases (Journal ML->ML per-locale with POST body check, update mode, mltext->text, text->mltext). The existing text preset test is unchanged and green. Full Vitest: 1064 passed; vue-tsc clean. - `modules/boardwalk/dist` rebuilt (old `index-7UEAlsFO.js` removed, `index-DPCIeFlT.js` added; intentional hashed-bundle swap); `check-admin-dist.sh` reports a match. **Task 3 - docs (5932172, `docs(cabana): document preset on mltext fields`)** - `schema_types.go` Preset/FieldPreset comments and the form-schema swag description in `admin_openapi.go` name text or mltext; `admin.json` and `schema.d.ts` regenerated with `scripts/check-admin-openapi.sh` (the `--check` gate passes). - `modules/cabana/README.md` (Features bullet, `cabana.FieldPreset` row) and `docs/backend/forms.md` (Field options list, preset paragraph, new multilingual-rules paragraph, start-up refusal sentence, and a pointer from the Markdown and multilingual fields section to `#field-options`). - `TestDocsTree`, `go vet ./...` and `go test ./...` green. ## Discretion choices (from the plan) - ML -> ML follows per locale: only the locales whose source text changed in this update are rewritten, each from its own locale. - Edits of an ML target are tracked per locale: a hand edit of locale L stops L only. - A preset never writes a locale the administrator neither typed in (source side) nor is looking at (target side): text -> mltext writes only the active locale; untouched locales stay empty so the server's translation fallback applies, and no unseen duplicate translations get saved. - mltext -> text follows the source's active-locale string (FormView tracks the active ML locale). - mlmarkdown stays out: the server admits text/mltext targets and text/mltext sources only. ## Plugin follow-up (sm-journal-plugin, NOT edited in this task) sm-journal-plugin was not edited. Apply in that repo: models/post/fields.yaml, the slug field gains a preset: ```yaml slug: label: golem15.journal::lang.post.slug type: mltext span: right tab: golem15.journal::lang.post.tab_edit required: true preset: field: title type: slug ``` models/category/fields.yaml, the slug field gains `preset: { field: name, type: slug }` the same way (its source `name` is mltext). models/tag/fields.yaml already has a text -> text preset and needs nothing. ## Deviations from Plan None - plan executed exactly as written. (Process note: the formState helpers were written just before their tests; RED was then demonstrated by running the new tests against the committed `formState.ts`/`FormView.vue` — 9 failures — before restoring the implementation.) ## Known Stubs None. ## Threat Flags None. No new routes or trust-boundary surface; the server-side widening admits exactly text and mltext (pinned by TestPresetML), and preset output remains client convenience validated by the normal save path. ## Self-Check: PASSED - FOUND: modules/cabana/preset_ml_test.go - FOUND: commits 7afad73, b5d8506, 5932172 (no co-author trailers, no .planning files) - Gates: check-admin-dist, check-admin-openapi --check, TestDocsTree, go vet ./..., go test ./..., Vitest full suite, vue-tsc