docs(18.1-01): complete taglist and flat reorder plan
- release gate reply "hold": v0.1.26 held until plan 06, nothing pushed
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user