From 7bc1433f77f4fb33e2c5c859779cd39daf25be93 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Mon, 12 Oct 2026 16:36:45 +0200 Subject: [PATCH] docs(17-02): summary of the admin SPA companions plan and the v0.1.23 release --- .../17-blog-companions/17-02-SUMMARY.md | 304 ++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 .planning/phases/17-blog-companions/17-02-SUMMARY.md diff --git a/.planning/phases/17-blog-companions/17-02-SUMMARY.md b/.planning/phases/17-blog-companions/17-02-SUMMARY.md new file mode 100644 index 0000000..4aa0222 --- /dev/null +++ b/.planning/phases/17-blog-companions/17-02-SUMMARY.md @@ -0,0 +1,304 @@ +--- +phase: 17-blog-companions +plan: 02 +subsystem: ui +tags: [vue, admin, tiptap, richeditor, repeater, dependent-dropdown, reorder, singleton, boardwalk, release] + +requires: + - phase: 17-01 + provides: cabana richeditor and repeater field types, dropdown dependsOn with the dependent-options route, config_reorder.yaml tree reorder routes, singleton controllers, regenerated OpenAPI and schema.d.ts + - phase: 16.2 + provides: admin form framework (FormView, FormGrid, field registry, formState), meta.notices plumbing +provides: + - RichEditorField (TipTap 3.31.4, lazy editor chunk) with the untouched rule, link popover allowlist, lossy note and HTML view + - RepeaterField (nested schema rows, add, reorder, remove, row-path errors) + - DropdownField dependsOn states (loading, empty, stale-value clear) through the dependent-options route + - ReorderView tree page with keyboard moves and one debounced POST of the full node set, list toolbar Reorder button + - Singleton form mode in FormView and settings index entries + - Phrase keys (en, pl), rebuilt modules/boardwalk/dist, docs updates + - Framework release v0.1.23 (annotated tag on origin, master pushed) +affects: [17-03 journal types and sitemap, 17-04 menu and faq plugins, 17-05 site integration (bumps requires to v0.1.23), 17-06 unit tests] + +actuals: + tokens: 35300 + tasks: 6 + commits: 9 +plan_head_before: 2181c2b5f68c8cac56e6ea1eec79dee92a22e12d +plan_head_after: e2a95298106a084a06032b63222f5965ebd93247 + +tech-stack: + added: ["@tiptap/core 3.31.4", "@tiptap/pm 3.31.4", "@tiptap/starter-kit 3.31.4 (admin/, exact pins, lockfile committed)"] + patterns: + - "Untouched rule: a rich editor emits nothing until the user edits, so imported HTML is submitted byte-identical" + - "Read-only HTML display through a client allowlist (displayHtml); the server sanitizer stays authoritative" + - "Form-wide services passed by injection keys (FORM_FEEDBACK, FIELD_SCOPE, DEPENDENT_CACHE) with one polite live region per FormView" + - "Tree writes send the full node set once, debounced; 409/403 shown, never retried silently" + +key-files: + created: + - admin/src/components/form/fields/RichEditorField.vue + - admin/src/components/form/richeditor/editor.ts + - admin/src/components/form/richeditor/LinkPopover.vue + - admin/src/components/form/richeditor/lossy.ts + - admin/src/components/form/richeditor/html.ts + - admin/src/components/form/fields/RepeaterField.vue + - admin/src/components/reorder/tree.ts + - admin/src/components/reorder/context.ts + - admin/src/components/reorder/ReorderBranch.vue + - admin/src/views/ReorderView.vue + - admin/tests/form/RichEditorField.test.ts + - admin/tests/form/RepeaterField.test.ts + - admin/tests/form/DependentDropdown.test.ts + - admin/tests/views/ReorderView.test.ts + - admin/tests/fixtures/companions.form-schema.json + modified: + - admin/package.json + - admin/package-lock.json + - admin/src/components/form/fields/DropdownField.vue + - admin/src/components/form/registry.ts + - admin/src/components/form/formState.ts + - admin/src/components/form/formContext.ts + - admin/src/components/form/FormGrid.vue + - admin/src/components/form/FormField.vue + - admin/src/components/form/FormErrorBanner.vue + - admin/src/components/form/PreviewField.vue + - admin/src/views/FormView.vue + - admin/src/views/ListView.vue + - admin/src/views/SettingsIndexView.vue + - admin/src/app/router.ts + - admin/src/api/types.ts + - admin/tests/fixtures/lang.json + - modules/phrasebook/backend/lang/en/lang.yaml + - modules/phrasebook/backend/lang/pl/lang.yaml + - modules/boardwalk/dist + - docs/backend/forms.md + - docs/backend/admin-controllers.md + - docs/backend/lists-and-filters.md + +key-decisions: + - "Released the framework as v0.1.23 (human reply: release v0.1.23) after the Docker smoke suites passed at 01bcd3e" + - "Read-only richeditor display goes through a client allowlist (displayHtml) because stored values are not sanitized on read (T-17-10)" + - "Reka Collapsible replaced by a plain aria-expanded/aria-controls disclosure so a 422 can focus fields inside collapsed repeater rows" + - "RepeaterField registered statically in the field registry (it renders FormGrid recursively; the import cycle is safe and commented)" + +patterns-established: + - "Untouched rule for HTML editors: no emit until edit" + - "Row-path errors (field.0.sub) resolved in FormErrorBanner, tab counts and focusError" + +requirements-completed: [SC-1, D-06, D-07, D-08, D-09, D-10, D-11] + +coverage: + - id: D1 + description: "Richeditor field: untouched imported HTML is submitted byte-identical; editing emits editor HTML" + requirement: "D-07" + verification: + - kind: unit + ref: "admin/tests/form/RichEditorField.test.ts#RichEditorField tracer: untouched value is submitted byte-identical" + status: pass + - kind: unit + ref: "admin/tests/form/RichEditorField.test.ts#RichEditorField edit emits editor HTML" + status: pass + human_judgment: true + rationale: "Toolbar, link popover, lossy note and HTML view look and feel per UI-SPEC need a visual check in a browser" + - id: D2 + description: "Repeater field: add, edit nested, reorder and remove rows change form state only" + requirement: "D-08" + verification: + - kind: unit + ref: "admin/tests/form/RepeaterField.test.ts#RepeaterField tracer: add, edit nested, reorder and remove change form state only" + status: pass + human_judgment: false + - id: D3 + description: "Dependent dropdown loads options for the sibling value and clears a stale value" + requirement: "D-09" + verification: + - kind: unit + ref: "admin/tests/form/DependentDropdown.test.ts#DependentDropdown loads options for the sibling value and clears a stale value" + status: pass + human_judgment: false + - id: D4 + description: "Tree reorder page: a keyboard move sends one debounced POST with the full node set; list toolbar Reorder button; singleton form mode" + requirement: "D-10" + verification: + - kind: unit + ref: "admin/tests/views/ReorderView.test.ts#ReorderView tracer: a keyboard move sends one debounced POST with the full node set" + status: pass + human_judgment: true + rationale: "Drag and keyboard reorder ergonomics and the singleton settings entry need a browser walk-through" + - id: D5 + description: "Dist, types and phrase keys ship with the features; framework tagged v0.1.23 after human approval" + requirement: "D-06" + verification: + - kind: other + ref: "Task 5 gate: release-gate-ok (vet, short tests, cabana inventories, docs check, admin typecheck and tests, check-admin-dist.sh, check-admin-openapi.sh --check)" + status: pass + - kind: other + ref: "Task 6 verify: release-ok (tag on origin, master == origin/master, tag annotated)" + status: pass + human_judgment: false + +duration: 50min +completed: 2026-10-12 +status: complete +--- + +# Phase 17 Plan 02: Admin SPA companions and framework release Summary + +**TipTap 3.31.4 richeditor with the untouched rule, repeater rows, dependent dropdown states, tree reorder page and singleton form in the admin SPA, shipped in the annotated framework tag v0.1.23** + +## Performance + +- **Duration:** about 50 min (with a human checkpoint wait) +- **Started:** 2026-10-12T13:45:12Z +- **Checkpoint (Task 5):** 2026-10-12T14:22:08Z +- **Completed:** 2026-10-12T14:35:20Z +- **Tasks:** 6 of 6 +- **Files modified:** 44 (including modules/boardwalk/dist assets and planning docs) + +## Accomplishments + +- RichEditorField on TipTap (lazy `editor-*` chunk): untouched imported HTML is submitted byte-identical, link popover with an http/https/mailto/tel/relative allowlist, lossy-markup note and HTML view. +- RepeaterField rendering nested schema rows with add, reorder (buttons and keyboard), remove and row-path validation errors. +- DropdownField `dependsOn`: loads options from the dependent-options route for the sibling value, shows loading and empty states, clears a stale value. +- ReorderView: tree page with keyboard moves and one debounced POST carrying the full node set, 403 and 409 states shown without silent retries; a Reorder button in the list toolbar. +- Singleton form mode in FormView and its settings index entry. +- en and pl phrase keys, rebuilt `modules/boardwalk/dist`, docs updates in forms, admin-controllers and lists-and-filters. +- Framework release v0.1.23 on origin. + +## Task Commits + +1. **Task 1: Package legitimacy (TipTap 3.31.4)** - checkpoint, no commit. Human reply verbatim: "packages approved" +2. **Task 2: Richeditor tracer** - `cb2987c` (feat), `4a5e896` (build, dist) +3. **Task 3: Repeater and dependent dropdown** - `d3bf5d8` (feat), `204149c` (build, dist) +4. **Task 4: ReorderView, list button, singleton form** - `872fd65` (feat), `01bcd3e` (build, dist) +5. **Deferred items log** - `4ef5c05` (docs) +6. **Task 5: Release gate** - checkpoint:decision (blocking-human), no commit. Human reply verbatim: "release v0.1.23" +7. **Task 6: Release** - annotated tag `v0.1.23`, no commit + +**Measured commit count:** `git rev-list --count 2181c2b..e2a9529` = 9. Seven are this plan's; `b519c9c` and `e2a9529` (`docs(phase-16.3): … security threat verification`) were committed by another session on master during the plan and are planning docs only. + +## Release Sheet (Task 5) + +| Item | Value | +|------|-------| +| Gate | release-gate-ok | +| Docker smoke suites | all re-run and passed at `01bcd3e` (plans 01 and 02 companions smoke tests, OpenAPI conformance) | +| 17-REVIEW.md / 17-SECURITY.md | absent, no open high findings | +| origin/master at checkpoint | `4a5e896` (pushed earlier by an outside push, not by the executor) | +| Proposed tag | v0.1.23 (highest existing v0.1.22, absent on origin) | +| One-way surfaces | pact HasMenuItemTypes, MenuItemType (PatternFrom), MenuItemReference, MenuItemResolve, ResolvedMenuItem, MenuItemAlternate, DependentOptionsProvider, DependentOptionSet, AdminSingleton, Option.Depth; signpost Activate, Registry, ReferenceOptions, config key menu_items.patterns; seaglass; cabana field types richeditor and repeater, the dependsOn key, config_reorder.yaml keys, toolbar button reorder; routes dependent-options, reorder GET and POST, singleton | +| Wave 2 note | plans 03 and 04 build against this checkout through go.work; plan 05 bumps their go.mod requires to this tag | +| Human reply | "release v0.1.23" (release-now) | + +## Release (Task 6) + +| Item | Value | +|------|-------| +| RELEASE_TAG | **v0.1.23** | +| Tag object | `ae75a925ee6a07ddfb7a1e89957052304d5fd1ac` (annotated) | +| Tagged commit | `e2a95298106a084a06032b63222f5965ebd93247` | +| Pushed master | `4a5e896..e2a9529` | +| origin/master after push | `e2a95298106a084a06032b63222f5965ebd93247` | +| Tag message | v0.1.23: menu item type registry (signpost), seaglass sanitizer, cabana repeater, richeditor, dependent dropdown, tree reorder and singleton form | +| Verify | release-ok | + +Precondition re-checked before tagging: `git fetch origin --tags`, `git ls-remote --tags origin refs/tags/v0.1.23` empty, `git log HEAD..origin/master` empty, tracked tree clean apart from other sessions' `.planning/state.json` and `.msd/`. The commits between the Docker-tested `01bcd3e` and the tagged head (`4ef5c05`, `b519c9c`, `e2a9529`) touch only `.planning/` docs, so the tagged code is the tested code. The push of master also published the other session's two 16.3 docs commits. No plugin or host was bumped (plan 05 does that). + +## Files Created/Modified + +- `admin/src/components/form/fields/RichEditorField.vue` - richeditor field, untouched rule, read-only display +- `admin/src/components/form/richeditor/{editor.ts,LinkPopover.vue,lossy.ts,html.ts}` - TipTap setup, link allowlist, lossy detection, display allowlist +- `admin/src/components/form/fields/RepeaterField.vue` - repeater rows +- `admin/src/components/form/fields/DropdownField.vue` - dependsOn states +- `admin/src/components/form/{registry.ts,formState.ts,formContext.ts,FormGrid.vue,FormField.vue,FormErrorBanner.vue,PreviewField.vue}` - registration, row-path state and errors, injection keys +- `admin/src/views/ReorderView.vue`, `admin/src/components/reorder/{tree.ts,context.ts,ReorderBranch.vue}` - tree reorder page +- `admin/src/views/{FormView.vue,ListView.vue,SettingsIndexView.vue}`, `admin/src/app/router.ts`, `admin/src/api/types.ts` - singleton mode, Reorder button, routes, types +- `admin/tests/...` - four spec files and fixtures +- `modules/phrasebook/backend/lang/{en,pl}/lang.yaml` - phrase keys +- `modules/boardwalk/dist` - rebuilt admin +- `docs/backend/{forms,admin-controllers,lists-and-filters}.md` - field and controller docs + +## Decisions Made + +- v0.1.23 released on the human's "release v0.1.23" reply. +- Read-only richeditor HTML passes through a client allowlist because stored values are not sanitized on read. +- Plain disclosure instead of Reka Collapsible so error focus reaches collapsed rows. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 2 - Missing Critical] Read-only richeditor client display allowlist (`displayHtml`)** +- **Found during:** Task 2 +- **Issue:** Stored values are not sanitized on read, so rendering them raw in the read-only view would allow admin XSS (T-17-10). +- **Fix:** `richeditor/html.ts` filters display HTML through an allowlist before `v-html`. +- **Committed in:** `cb2987c` + +**2. [Rule 2 - Missing Critical] FormField label id `${controlId}-label`** +- **Found during:** Task 2 +- **Issue:** The editor and repeater need `aria-labelledby`; the label had no id. +- **Fix:** label id added in FormField. +- **Committed in:** `cb2987c` + +**3. [Rule 2 - Missing Critical] PreviewField renders richeditor and repeater through read-only controls** +- **Found during:** Tasks 2-3 +- **Issue:** Preview mode would have shown raw HTML or JSON. +- **Committed in:** `cb2987c`, `d3bf5d8` + +**4. [Rule 1 - Bug] Toolbar labels use literal phrase keys** +- **Found during:** Task 2 +- **Issue:** `TestPhase10SPAKeysResolve` only resolves literal phrase keys in the SPA source, so non-literal toolbar label keys would escape the parity check. +- **Fix:** literal `t('…')` keys per toolbar button. +- **Committed in:** `cb2987c` + +**5. [Rule 3 - Blocking] Reka Collapsible replaced by a plain aria-expanded/aria-controls disclosure** +- **Found during:** Task 3 +- **Issue:** A 422 could not focus a field inside a collapsed repeater row. +- **Committed in:** `d3bf5d8` + +**6. [Rule 2 - Missing Critical] Row-path errors in FormErrorBanner, tab counts and focusError** +- **Found during:** Task 3 +- **Issue:** Errors keyed `field.0.sub` were dropped from the banner and tab counts. +- **Committed in:** `d3bf5d8` + +**7. [Rule 3 - Blocking] New formContext injection keys FORM_FEEDBACK, FIELD_SCOPE, DEPENDENT_CACHE; one polite live region in FormView** +- **Found during:** Task 3 +- **Issue:** Nested rows and dependent dropdowns needed form-wide services without prop drilling. +- **Committed in:** `d3bf5d8` + +**8. [Rule 3 - Blocking] Extra files outside `files_modified`** +- `richeditor/html.ts`, `components/reorder/{tree.ts,context.ts,ReorderBranch.vue}`, plus FormField, FormErrorBanner, PreviewField, formContext, tests/fixtures/typed.ts and docs/backend/{admin-controllers,lists-and-filters}.md. +- **Committed in:** `cb2987c`, `d3bf5d8`, `872fd65` + +**9. [Rule 3 - Blocking] RepeaterField registered statically** +- **Issue:** RepeaterField renders FormGrid, which resolves fields through the registry, so it sits on an import cycle with the registry. It is registered statically (not through the relation manager's lazy map) because only the registry imports it, so the registry always evaluates first; the reasoning is commented in `RepeaterField.vue`. +- **Committed in:** `d3bf5d8` + +--- + +**Total deviations:** 9 auto-fixed (5 missing critical, 1 bug, 3 blocking) +**Impact on plan:** Security and accessibility fixes required for correctness; no scope beyond the UI-SPEC. + +## Issues Encountered + +- A `git checkout --` without a pathspec ran during Task 3. It changed no files (verified with `git status`). +- origin/master was at `4a5e896` at the checkpoint, pushed by an outside push, not by the executor. +- Pushing master in Task 6 also published another session's two 16.3 planning-doc commits (`b519c9c`, `e2a9529`), which sat on master above this plan's commits. +- Out-of-scope findings logged in `deferred-items.md` (`4ef5c05`): singleton subtitle link key, per-controller reorder label, OpenAPI query and nullability gaps, npm audit advisories in existing dev dependencies, main bundle size above Vite's 500 kB warning. + +## User Setup Required + +None. + +## Next Phase Readiness + +- Plans 03 and 04 can build against this checkout; plan 05 bumps plugin and host requires to **v0.1.23**. +- Plan 06 (unit tests) runs after this release; its tests ship with the next tag. + +--- +*Phase: 17-blog-companions* +*Completed: 2026-10-12* + +## Self-Check: PASSED + +Created files present (RichEditorField.vue, RepeaterField.vue, ReorderView.vue, richeditor/html.ts, reorder/tree.ts, the four spec files); commits cb2987c, 4a5e896, d3bf5d8, 204149c, 872fd65, 01bcd3e, 4ef5c05 found; tag v0.1.23 on origin (annotated, ae75a92 -> e2a9529).