From 1e18143ef3cf5f7e00d322aee8cdcffdd7bc119a Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Wed, 14 Oct 2026 16:38:23 +0200 Subject: [PATCH] docs(18.1-01): complete taglist and flat reorder plan - release gate reply "hold": v0.1.26 held until plan 06, nothing pushed --- .../18.1-01-SUMMARY.md | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 .planning/phases/18.1-payment-gateway-subscriptions/18.1-01-SUMMARY.md diff --git a/.planning/phases/18.1-payment-gateway-subscriptions/18.1-01-SUMMARY.md b/.planning/phases/18.1-payment-gateway-subscriptions/18.1-01-SUMMARY.md new file mode 100644 index 0000000..3920555 --- /dev/null +++ b/.planning/phases/18.1-payment-gateway-subscriptions/18.1-01-SUMMARY.md @@ -0,0 +1,278 @@ +--- +phase: 18.1-payment-gateway-subscriptions +plan: 01 +subsystem: admin +tags: [cabana, taglist, reorder, sortable, vue, openapi, boardwalk] + +# Dependency graph +requires: + - phase: 18-payment-gateway + provides: the money field pattern (key gate, type check, save-site value check, phrase block) that taglist mirrors +provides: + - "cabana form field type `taglist` (mode string|array, separator comma|space, options, customTags) at the controller, relation and settings save sites" + - "flat (Sortable) reorder inferred from a config_reorder.yaml without parentColumn on a record without parent_id; `flat` flag on ListReorder and AdminReorderTree" + - "TaglistField.vue chip input, PreviewField taglist rendering, flat mode in ReorderView/ReorderBranch/tree.ts" + - "phrase keys backend::lang.taglist.{invalid,not_allowed,add,remove} and backend::lang.reorder.flat_parent (EN+PL)" + - "fixture plugin acme.catalog (badges controller, Badge model) for taglist and flat reorder smoke tests" +affects: [18.1-04, 18.1-06, cabana, boardwalk, admin] + +# Actuals (#2632) - chars/4 over the realized diff (dist excluded) +actuals: + tokens: 36070 + tasks: 5 + commits: 4 +plan_head_before: 43d2fdd54a74347b16650ac52658e0453a672518 +plan_head_after: c3834cb +commits: 4 + +tech-stack: + added: [] + patterns: + - "Value-bearing field type added the money way: compile*Keys gate, check*Type at boot, check*Values at every save site, hydrate* on record GET" + - "Mode inference from the record shape (WinterCMS Sortable): no parentColumn key and no parent_id column means flat" + - "New JSON flags are omitempty so existing payloads stay byte-identical" + +key-files: + created: + - modules/cabana/field_taglist.go + - modules/cabana/taglist_field_test.go + - modules/cabana/catalog_smoke_test.go + - modules/cabana/reorder_flat_test.go + - modules/cabana/testdata/catalog/controllers/badges/config_form.yaml + - modules/cabana/testdata/catalog/controllers/badges/config_list.yaml + - modules/cabana/testdata/catalog/controllers/badges/config_reorder.yaml + - modules/cabana/testdata/catalog/models/badge/columns.yaml + - modules/cabana/testdata/catalog/models/badge/fields.yaml + - admin/src/components/form/fields/TaglistField.vue + - admin/tests/form/TaglistField.test.ts + modified: + - modules/cabana/form_schema.go + - modules/cabana/schema_types.go + - modules/cabana/crud.go + - modules/cabana/relation_child.go + - modules/cabana/relation_form.go + - modules/cabana/settings.go + - modules/cabana/registry.go + - modules/cabana/field_file.go + - modules/cabana/reorder.go + - modules/cabana/admin_openapi.go + - modules/cabana/README.md + - modules/phrasebook/backend/lang/en/lang.yaml + - modules/phrasebook/backend/lang/pl/lang.yaml + - admin/src/components/form/registry.ts + - admin/src/components/form/PreviewField.vue + - admin/src/views/ReorderView.vue + - admin/src/components/reorder/ReorderBranch.vue + - admin/src/components/reorder/tree.ts + - admin/tests/views/ReorderView.test.ts + - admin/openapi/admin.json + - admin/src/api/schema.d.ts + - modules/boardwalk/dist + - docs/backend/forms.md + - docs/backend/lists-and-filters.md + +key-decisions: + - "Release gate (D-22): human replied \"hold\"; proposed tag v0.1.26 is held until plan 06, nothing pushed or tagged; 22 commits origin/master..master stay local" + - "A required taglist is checked on its normalised tags; the generic required rule is skipped for taglist in controller rules and settingRules, as for repeater" + - "Flat reorder is inferred (no parentColumn key and no parent_id column), no new YAML key; maxDepth on a flat reorder fails boot" + +patterns-established: + - "taglist save path: decode -> type check (422 taglist.invalid) -> normaliseTags (trim, drop empty, dedupe first) -> customTags allow-list (422 taglist.not_allowed) -> JSON text (array) or separator-joined string" + +requirements-completed: [SC-3, D-17, D-22] + +coverage: + - id: D1 + description: "taglist field compiles from fields.yaml with mode/separator/options/customTags and refuses bad keys, wrong column types and repeater nesting at boot" + requirement: D-17 + verification: + - kind: unit + ref: "modules/cabana/taglist_field_test.go#TestTaglistFieldCompile" + status: pass + human_judgment: false + - id: D2 + description: "taglist save normalises and validates in array and string modes, enforces customTags false, stores [] / empty string for null, and the record GET returns the stored value" + requirement: D-17 + verification: + - kind: integration + ref: "modules/cabana/catalog_smoke_test.go#TestTaglistFieldSmoke" + status: pass + human_judgment: false + - id: D3 + description: "flat reorder compiles from a WinterCMS Sortable config_reorder.yaml, answers flat true with null parents, saves only the sort column, 422 on a parent, 409 on a stale set; tree reorders unchanged" + requirement: D-17 + verification: + - kind: unit + ref: "modules/cabana/reorder_flat_test.go#TestReorderFlatCompile" + status: pass + - kind: integration + ref: "modules/cabana/catalog_smoke_test.go#TestReorderFlatSmoke" + status: pass + - kind: integration + ref: "modules/cabana/reorder_test.go#TestReorderPostgres" + status: pass + human_judgment: false + - id: D4 + description: "TaglistField chip input (array and string modes, customTags, read-only) and flat ReorderView (no indent/outdent/inside drop, parent_id null)" + requirement: D-17 + verification: + - kind: unit + ref: "admin/tests/form/TaglistField.test.ts" + status: pass + - kind: unit + ref: "admin/tests/views/ReorderView.test.ts" + status: pass + human_judgment: true + rationale: "Chip editing and drag-to-order feel in a real browser are not covered by vitest; a human should try both screens once in the admin" + - id: D5 + description: "Release gate: the human decided the framework tag (reply \"hold\"); nothing pushed or tagged" + requirement: D-22 + verification: + - kind: other + ref: "git ls-remote --tags origin refs/tags/v0.1.26 (empty); git rev-list --count origin/master..master = 22" + status: pass + human_judgment: false + +duration: 31min +completed: 2026-10-14 +status: complete +--- + +# Phase 18.1 Plan 01: Framework taglist field and flat reorder Summary + +**cabana gained a core `taglist` form field (array mode on a JSON column, WinterCMS string mode with comma/space separator, options with a customTags allow-list) and a flat Sortable reorder inferred from a parent-less record, with the chip-input and flat-reorder SPA, OpenAPI, dist and docs; the framework tag v0.1.26 is held until plan 06.** + +## Performance + +- **Duration:** 31 min of task execution (13:36:49Z to ~14:08Z), then the release checkpoint +- **Started:** 2026-10-14T13:36:49Z +- **Completed:** 2026-10-14T14:37Z (SUMMARY after the "hold" reply) +- **Tasks:** 5 of 5 (4 implementation tasks committed, Task 5 the release decision) +- **Files modified:** 37 (excluding the dist and generated OpenAPI/TS), +1720/-61 + +## Accomplishments + +- `taglist` field type: compile gate (mode, separator, options, customTags; refused keys nameFrom, emptyOption, dependsOn, preset and the money keys; wrong column type fails boot), save-time normalisation and validation at the controller, relation-form and settings save sites, hydration on record GET. +- Flat reorder: a `config_reorder.yaml` with only title, nameFrom and modelClass on a record without `parent_id` compiles flat; GET answers `flat: true` with null parents; POST writes only the sort column; a parent id is a 422 on `nodes`; tree reorders answer byte-identically. +- SPA: `TaglistField.vue` chip input (Enter or separator adds, datalist suggestions, remove buttons, read-only chips), PreviewField taglist rendering, ReorderView flat mode (no indent/outdent, no inside drop, arrows left/right ignored, parent_id null on save). +- OpenAPI and `schema.d.ts` regenerated, `modules/boardwalk/dist` rebuilt, `docs/backend/forms.md` `## Taglist`, `docs/backend/lists-and-filters.md` `## Flat reorder`, cabana README rows. + +## Task Commits + +1. **Task 1: Taglist tracer, array mode end to end** - `349bad5` (feat) +2. **Task 2: Taglist string mode, customTags, all save sites, preview** - `cf63fb3` (feat) +3. **Task 3: Flat reorder on the server** - `9edff88` (feat) +4. **Task 4: Flat reorder in the admin SPA** - `c3834cb` (feat) +5. **Task 5: Release gate (checkpoint:decision, blocking-human)** - no commit; human reply "hold" + +**Plan metadata:** recorded in the docs commit that adds this SUMMARY. + +## Identifiers Added + +- Unexported (cabana): `taglistType`, `compileTaglistKeys`, `checkTaglistType`, `jsonColumnType`, `compileTaglistFields`, `normaliseTags`, `taglistValue`, `taglistJoin`, `taglistOptionValues`, `disallowedTag`, `checkTaglistValues(ctx, tr, fields, body, allows, create)`, `hydrateTaglistFields`, `storedTags`, `compiledReorder.flat`, `msgReorderFlatParent`. +- Exported: `FormField.Separator` (json `separator,omitempty`), `FormField.CustomTags` (json `customTags,omitempty`), `ListReorder.Flat` and `AdminReorderTree.Flat` (json `flat,omitempty`). +- Test-only export: `cabana.CompiledListSchema` (export_test.go). +- SPA `admin/src/components/reorder/tree.ts`: `flatZone`, `flatMove`, `flatten(tree, flat)`. + +## Release Gate (Task 5) + +**Gate evidence:** the verify chain printed `release-gate-ok` (go vet, `go test -short ./...`, the four route/permission inventories, `TestDocsTree`/`TestDelegateCommandsHelp`, `docs:build --check`, admin typecheck, vitest 92 files / 1390 tests, `check-admin-dist.sh`, `check-admin-openapi.sh --check`). Docker suites ran green: `go test -count=1 ./modules/cabana ./modules/phrasebook`, including `TestTaglistFieldSmoke`, `TestReorderFlatSmoke` and `TestReorderPostgres`. No Docker smoke test is NOT RUN. + +**Proposed tag:** `v0.1.26` (highest local `v0.1.25` plus one; `git ls-remote --tags origin refs/tags/v0.1.26` prints nothing). + +**One-way surfaces the tag would publish:** +- cabana field type `taglist`: keys `mode` (`string`|`array`, default `string`), `separator` (`comma`|`space`, default `comma`), `options`, `customTags` (default true); array mode needs a Scanner/Valuer column such as `lagoon.Jsonable[[]string]`; FormField JSON keys `separator` and `customTags` (omitempty). +- Flat reorder: `config_reorder.yaml` without `parentColumn` on a record without `parent_id` compiles flat; `ListReorder.Flat` / `AdminReorderTree.Flat` as `flat` (omitempty); the flat save writes only the sort column; a node with a parent is a 422 on `nodes`. +- Phrase keys `backend::lang.taglist.invalid`, `taglist.not_allowed`, `taglist.add`, `taglist.remove`, `backend::lang.reorder.flat_parent` (EN and PL). + +**Commits to push:** 22 commits in `origin/master..master` (this plan's four task commits plus earlier unpushed work), left unpushed. + +**Human reply (verbatim):** "hold" + +**Outcome:** nothing pushed, nothing tagged. `v0.1.26` is held until the phase gate in plan 06. Plans 02 to 05 build against this checkout through `replace ../summercms.go`, so holding costs only a later bump in plan 06. + +## Decisions Made + +- The framework tag is held until plan 06, the same choice as Phase 18. +- Flat mode is inferred the WinterCMS Sortable way, without a new YAML key; an explicit `parentColumn` still means a tree. +- A required taglist is validated on its normalised tags rather than on the raw value (see deviation 2). + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] The `mode` boot error lists taglist** +- **Found during:** Task 1 +- **Issue:** `mode` became valid on taglist, so the existing boot error text for an invalid `mode` was incomplete. +- **Fix:** the message now lists "..., permissioneditor or taglist"; four tests that pin the message were updated (datepicker_test.go, datepicker_smoke_test.go, phase121_form_test.go, phase121_schema_boot_test.go). +- **Committed in:** `349bad5` + +**2. [Rule 2 - Missing critical] Required taglist checked on its tags** +- **Found during:** Task 2 +- **Issue:** the generic required rule would accept `[""]` or `" , "`, which normalise to no tags. +- **Fix:** a required taglist is checked after normalisation; the generic required rule is skipped for taglist in controller rules and in settingRules, as it already is for repeater. +- **Committed in:** `cf63fb3` + +**3. [Rule 3 - Blocking] Settings save order and binding** +- **Found during:** Task 2 +- **Issue:** settings Put ran `requireSettingInputs` before any taglist check, and settings did not bind taglist columns. +- **Fix:** settings Put runs `checkTaglistValues` before `requireSettingInputs`; settings bind taglist columns. +- **Committed in:** `cf63fb3` + +**4. Repeater refusal needed no code** +- **Found during:** Task 2 +- **Issue:** the plan listed `field_repeater.go` for the nested-taglist refusal, but `repeaterNestedTypes` already refuses any type not on its list. +- **Fix:** `field_repeater.go` is unchanged; a compile subtest asserts the refusal. +- **Committed in:** `cf63fb3` + +**5. [Rule 2 - Missing critical] Accessible preview separators** +- **Found during:** Task 2 +- **Issue:** the preview's chips needed a text separator for screen readers and copy-paste. +- **Fix:** PreviewField reuses the TaglistField read-only chips with sr-only ", " separators (text interpolation only, no raw HTML). +- **Committed in:** `cf63fb3` + +**6. Flat admin docs placed in the Task 4 commit** +- **Found during:** Task 4 +- **Issue:** the SPA-side flat behaviour (no indent/outdent, no inside drop) belonged in `docs/backend/lists-and-filters.md`. +- **Fix:** the text went in with the Task 4 commit next to the Task 3 section. +- **Committed in:** `c3834cb` + +**7. Badge customTags:false fixture (traceability note, not a deviation)** +- The `customTags: false` change on the badge `tags` field landed with Task 2 together with the string-mode `keywords` field, as planned. +- **Committed in:** `cf63fb3` + +Supporting edits outside the plan's file list: `modules/cabana/registry.go` and `relation_form.go` call `compileTaglistFields` next to `compileMoneyFields` (controller and relation forms); `field_file.go` holds the shared `mode` key gate (deviation 1); `modules/cabana/export_test.go` adds the test-only `CompiledListSchema`. + +--- + +**Total deviations:** 6 (1 Rule 1, 2 Rule 2, 1 Rule 3, 2 placement notes) plus 1 traceability note +**Impact on plan:** all needed for correctness or to keep existing tests honest; no scope creep, no new dependency. + +## Issues Encountered + +- Vitest flaked under host load (load average around 105): some runs had 6 to 13 unrelated timing-dependent tests fail. Clean runs and the final gate run were 1390/1390. +- Pre-existing gofmt issues in `account_tokens*.go` and `token_*.go` were left alone as out of scope. + +## Threat Flags + +None. No new route: the reorder and form saves keep the existing controller permission gate and CSRF checks. T-18.1-01 to T-18.1-03 are mitigated as planned (type and allow-list 422s, text-only rendering with no `v-html`, flat save writes only the sort column). No npm or Go package was installed. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- Plan 04 can declare `features` as `type: taglist, mode: array` on a `lagoon.Jsonable[[]string]` column and a flat SubscriptionPlans reorder with the PHP `config_reorder.yaml` unchanged. +- Plans 02 to 05 build through `replace ../summercms.go`; plan 06 tags the framework (v0.1.26 or later) after its unit tests and pushes with the human's go-ahead. +- Plan 06 owes a vitest that renders a tag containing markup as text (T-18.1-02). + +--- +*Phase: 18.1-payment-gateway-subscriptions* +*Completed: 2026-10-14* + +## Self-Check: PASSED + +- Files: field_taglist.go, taglist_field_test.go, catalog_smoke_test.go, reorder_flat_test.go, TaglistField.vue, TaglistField.test.ts, catalog config_reorder.yaml all present. +- Commits: 349bad5, cf63fb3, 9edff88, c3834cb all present. +- No tag: v0.1.26 exists neither locally nor on origin; nothing pushed.