Files
summercms/.planning/phases/12.1-user-plugin-admin-screens/12.1-02-SUMMARY.md
Jakub Zych bd4960401d docs(12.1-02): complete framework preview and form seams plan
- summary with the v0.1.3 tag, gate times and contract names
- deferred item for two application inventory tests
- WINDOWS entry 9 fixed: RecordActions.vue is mounted
2026-10-05 12:43:23 +02:00

28 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plan_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before plan_head_after tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
12.1-user-plugin-admin-screens 02 admin
cabana
pact
admin-spa
preview
permissioneditor
password
virtual-fields
relation-lock
vue
openapi
release
phase provides
12.1-user-plugin-admin-screens plan 01: record actions with meta.actions, cabana.ForbiddenError, RecordActions.vue, the acme.roster fixture
phase provides
12.2-admin-form-fields lagoon.Transaction write paths, datepicker and fileupload read-only modes, tags v0.1.1 and v0.1.2
config_form.yaml preview block, context: preview, the SPA preview route and PreviewView.vue
pact.FormVirtualFields, pact.FormRules, cabana.VirtualFieldsFromContext
fields.yaml types password and permissioneditor, key preset
cabana.PermissionOption, cabana.PermissionEditorProvider
cabana.FieldRelationContract.WritableForeignKey, cabana.RelationLock, cabana.RelationLockProvider, RelationOption.Locked
columns.yaml invisible; pact.FilterOptions asked on the controller before the model
annotated tag v0.1.3 on df5cace (local; push pending)
12.1-03
12.1-04
12.1-05
sm-user-plugin admin controllers
fonoteka.go admin inventory tests
tokens tasks commits
362238 6 4
1c99de5013 df5cace852
added patterns
One file per field type (field_permission.go beside field_date.go and field_file.go)
Per-request schema parts (permission options) are set on the localized copy, never on the cached schema
Form-only values travel to hooks on the transaction context, never through Fill or the projection
Locks are enforced inside the save transaction before any row write; the flag on options is display only
created modified
modules/cabana/field_permission.go
modules/cabana/phase121_form_test.go
modules/cabana/example_form_seams_test.go
modules/cabana/testdata/roster/controllers/people/_status.htm
modules/cabana/testdata/roster/controllers/people/config_filter.yaml
admin/src/views/PreviewView.vue
admin/src/components/form/PreviewField.vue
admin/src/components/form/fields/PasswordField.vue
admin/src/components/form/fields/PermissionEditorField.vue
admin/tests/smoke/preview.smoke.test.ts
admin/tests/smoke/seams.smoke.test.ts
admin/tests/fixtures/roster.form-schema.json
modules/pact/capabilities.go
modules/cabana/form_schema.go
modules/cabana/schema_types.go
modules/cabana/crud.go
modules/cabana/tx_context.go
modules/cabana/extension.go
modules/cabana/relation_field.go
modules/cabana/list_schema.go
modules/cabana/filter_schema.go
modules/cabana/http.go
modules/cabana/messages.go
modules/cabana/settings.go
modules/cabana/relation_form.go
admin/src/app/router.ts
admin/src/app/winterUrl.ts
admin/src/views/FormView.vue
admin/src/components/form/formState.ts
admin/src/components/form/control.ts
admin/src/components/form/fields/RelationField.vue
admin/src/components/list/DataTable.vue
admin/src/styles/main.css
Task 5 answer: tag-local. v0.1.3 is an annotated local tag on df5cace; master and the tag are not pushed
password, permissioneditor and preset are refused on settings forms and on relation (manage and pivot) forms
The SPA sends a permissioneditor field on every update, reduced to the offered codes
Relation locks are enforced on form create and update only; relation-manager link and unlink routes do not ask the lock provider
pact.FilterOptions keeps its signature (no context parameter); the controller is asked before the model
A null preview: is detected by checking the document's root keys, because goccy does not call a custom unmarshaler for null
A form that declares recordActions must declare preview:; write preview: {} for a screen without a status hint
A type: password field must be listed in the controller's FormVirtualFields
A new required message key needs the typed SPA fixtures and the conformance fixture updated in the same commit
SC-1
SC-2
SC-3
SC-4
id description requirement verification human_judgment
D1 Preview screen: preview block, context preview, status hint partial, record actions and the edit button in the footer, preview redirects SC-1
kind ref status
integration modules/cabana/phase121_form_test.go#TestPreviewSmoke pass
kind ref status
automated_ui admin/tests/smoke/preview.smoke.test.ts#preview screen (UI-SPEC S3, D-11) pass
kind ref status
automated_ui admin/tests/smoke/preview.smoke.test.ts#preview footer (UI-SPEC S2, D-10) pass
kind ref status
automated_ui admin/tests/smoke/preview.smoke.test.ts#preview URLs (T-12.1-16) pass
kind ref status
manual_procedural Task 1 tracer checkpoint: the user opened the preview on the demo server and approved it pass
false
id description requirement verification human_judgment
D2 Password field with confirmation and form-only (virtual) fields that reach hooks and are never bound, filled or returned SC-3
kind ref status
integration modules/cabana/phase121_form_test.go#TestPasswordFieldSmoke pass
kind ref status
integration modules/cabana/phase121_form_test.go#TestVirtualFieldsSmoke pass
kind ref status
automated_ui admin/tests/smoke/seams.smoke.test.ts#password field (UI-SPEC S7, D-19) pass
false
id description requirement verification human_judgment
D3 Rules per operation from the controller (pact.FormRules) and the preset key on text fields SC-3
kind ref status
integration modules/cabana/phase121_form_test.go#TestFormRulesSmoke pass
kind ref status
unit modules/cabana/phase121_form_test.go#TestPresetSchema pass
kind ref status
automated_ui admin/tests/smoke/seams.smoke.test.ts#preset (D-27 G7) pass
false
id description requirement verification human_judgment rationale
D4 permissioneditor field in radio or checkbox mode: per-request options, 422 for unknown codes and values, 403 for a changed locked code, kept unknown stored codes SC-2
kind ref status
integration modules/cabana/phase121_form_test.go#TestPermissionEditorSmoke pass
kind ref status
automated_ui admin/tests/smoke/seams.smoke.test.ts#permission editor (UI-SPEC S5, D-16) pass
true Section layout, the three-segment control, wrapping below 640px and dark mode are visual; the checkbox-mode value set has no HTTP test yet (plan 05).
id description requirement verification human_judgment rationale
D5 Writable protected foreign key through a relation field, and locked relation options enforced with 403 on create and update SC-3
kind ref status
integration modules/cabana/phase121_form_test.go#TestWritableForeignKeySmoke pass
kind ref status
integration modules/cabana/phase121_form_test.go#TestRelationLockSmoke pass
kind ref status
automated_ui admin/tests/smoke/seams.smoke.test.ts#locked relation options (UI-SPEC S6, D-07) pass
true Relation-manager link and unlink routes do not ask the lock provider; whether that is acceptable for T-12-18 is a review decision for plans 04 and 05.
id description requirement verification human_judgment
D6 Invisible list columns (searched, not sent, not rendered) and filter choices served by the controller SC-1
kind ref status
integration modules/cabana/phase121_form_test.go#TestInvisibleColumnSmoke pass
kind ref status
integration modules/cabana/phase121_form_test.go#TestFilterOptionsController pass
kind ref status
automated_ui admin/tests/smoke/seams.smoke.test.ts#invisible list column (D-27 G6) pass
false
id description requirement verification human_judgment rationale
D7 Framework released as the annotated tag v0.1.3 on a head where every plan gate passed; push pending by the user's choice SC-4
kind ref status
other git cat-file -t v0.1.3 = tag; git rev-parse 'v0.1.3^{commit}' = df5cace852 pass
kind ref status
other Task 6 gate table in this summary pass
true The push of master and v0.1.3 is a pending release step, and two application inventory tests outside the plan's gate fail against this tree (see Issues Encountered).
12h 43m 2026-10-05 complete

Phase 12.1 Plan 02: Framework preview and form seams Summary

Any plugin form can now have a read-only preview screen with a status hint and record actions, a password with confirmation, form-only fields that reach hooks, its own admin rules per operation, a permission editor, a writable foreign-key picker, locked relation options the server enforces, preset fields and hidden search columns. The framework is tagged v0.1.3 locally on df5cace; nothing was pushed.

Performance

  • Duration: 12h 43m wall clock, including two waits for the user (the Task 1 tracer checkpoint and the Task 5 decision) and an overnight pause. Task 6 itself took about 8 minutes.
  • Started: 2026-10-04T21:57:49Z
  • Completed: 2026-10-05T10:41:16Z
  • Tasks: 6 of 6 (four code tasks, the decision checkpoint, the release task)
  • Files modified: 77 source files, plus the rebuilt modules/boardwalk/dist (82 paths in total)

Accomplishments

  • A list row opens a preview screen: the record as a read-only dl grid, a status hint partial above the card, the record actions of plan 01 and one edit button in the footer. RecordActions.vue is now mounted, which closes WINDOWS entry 9.
  • A form collects a password with confirmation and other form-only values. The framework never binds, fills or returns them; hooks read them with cabana.VirtualFieldsFromContext.
  • A controller supplies the validation rules of an admin save per operation, replacing the model's Rules().
  • A permissioneditor field shows the permissions a controller offers, in radio or checkbox mode. The server accepts only offered codes and allowed values and refuses a change to a locked code with 403.
  • A relation field can write a protected foreign key when its contract says so, and a controller can lock related ids per admin; a save that changes the locked subset is refused with 403 before any row is written.
  • columns.yaml accepts invisible: true, text fields accept preset, and scope-filter choices can come from the controller.

Release: v0.1.3

Item Value
Option chosen at Task 5 tag-local
Tag v0.1.3, annotated, message "SummerCMS v0.1.3: bulk and record actions, preview screen, row state, permission editor, form seams"
Tagged commit df5cace8525bc10fa40b25552137290793c08bb3
On origin No. git ls-remote --tags origin v0.1.3 lists nothing
Earlier tags v0.1.1 (5c38d96) and v0.1.2 (6525d96) unchanged

Pending release steps (not done, by the user's choice):

  1. Push framework master and the tag: git push origin master v0.1.3. master was 10 commits ahead of origin/master at the tag, plus the planning commits made after it.
  2. The sm-user-plugin push of plan 05 Task 3 waits for step 1. It runs only when git ls-remote --tags origin v0.1.3 lists the tag, because a published plugin commit must not depend on an unpublished framework contract.

Until the push, the local tag can still be moved if a name has to change.

Gates on the tagged head (measured, for VALIDATION.md)

All ran on df5cace with a clean tree (only the three untracked files that predate this plan).

Gate Result Time
go vet ./... pass under 1 s (build cache warm)
go test ./... -count=1 pass, no FAIL line 52 s
go test ./modules/cabana -run '^(TestPhase09ContractInventory|TestPhase09PermissionMatrix|TestPhase10OpenAPIConformance)$' -count=1 -v 3 of 3 pass 16 s
npm --prefix admin run typecheck pass 10 s
npm --prefix admin test 63 files, 848 tests pass 26 s
scripts/check-admin-openapi.sh --check clean 2 s
scripts/check-admin-dist.sh "modules/boardwalk/dist matches a fresh build" 16 s
go test ./cmd/summer -run TestDocsTree -count=1 pass 4 s
go run ./cmd/summer docs:build --check "no problems found" 2 s
go -C ../fonoteka.go build ./... pass 3 s
go -C ../fonoteka.go vet ./... pass under 1 s
go -C ../fonoteka.go test ./... -count=1 pass (root module: fonoteka, fonoteka/parity) 69 s

Earlier rough numbers from Tasks 1 to 4: go test ./modules/cabana/... -count=1 about 33 s, npm --prefix admin test about 22 s.

Extra runs, not part of the plan's gate (the application's go.work plugin modules, which ./... in the root module does not match):

Module vet test
plugins/golem15/user pass pass, 18 s
plugins/golem15/golem pass pass, 8 s
plugins/golem15/feedback pass pass, 8 s
plugins/golem15/fonoteka pass 2 tests fail, 98 s (see Issues Encountered)

Contract names (inputs to plans 03 to 05)

Every name below was checked with go doc at the tagged commit.

Name Shape
pact.FormVirtualFields FormVirtualFields() []string
pact.FormRules FormRules(ctx context.Context, op string) map[string]string; op is create or update
pact.FilterOptions unchanged: FilterOptions(scope string) []Option; the admin controller is asked before the model
cabana.VirtualFieldsFromContext (ctx context.Context) (map[string]any, bool); a copy; false outside a create or update save
cabana.FormPreview HeaderPartial string (json headerPartial, omitempty)
cabana.FormView.Preview *FormPreview (json preview, omitempty)
cabana.FormMessages.Preview, .Edit YAML and JSON keys preview, edit
cabana.FieldPreset Field (json field), Type (json type: slug or exact)
cabana.FormField.Preset *FieldPreset
cabana.PermissionOption Code, Label, Tab, Comment string; Locked bool (json code, label, tab, comment, locked; the last three omitempty)
cabana.FormField.PermissionOptions []PermissionOption (json permissionOptions, omitempty)
cabana.PermissionEditorProvider AdminPermissionOptions(ctx, field string) ([]PermissionOption, error); AdminPermissionValues(ctx, field string, record any) (map[string]int, error); AdminSetPermissionValues(ctx, field string, record any, values map[string]int) error
cabana.FieldRelationContract.WritableForeignKey bool; belongsTo only
cabana.RelationLock IDs []uint; Message string
cabana.RelationLockProvider AdminRelationLocks(ctx context.Context, field string) (RelationLock, error)
cabana.RelationOption.Locked bool (json locked, omitempty)
cabana.ListColumn.Invisible bool (json invisible, omitempty)
TS aliases FormPreview, FieldPreset, PermissionOption

The tag also publishes the plan 01 names (pact.AdminBulkAction, HasAdminBulkActions, AdminRecordAction, HasAdminRecordActions, RowState, ListRowStates; cabana.ForbiddenError, BulkActionResult, RecordAction); see 12.1-01-SUMMARY.md.

YAML: config_form.yaml preview: (a mapping with the optional key headerPartial; preview: {} enables the screen without a hint; a null value stops boot) and messages.preview, messages.edit; fields.yaml types password and permissioneditor, mode: radio|checkbox on permissioneditor, preset on text fields (a field name, or field plus type); columns.yaml invisible.

SPA: route preview at /:vendor/:plugin/:controller/:id/preview; PreviewView.vue, PreviewField.vue, PasswordField.vue, PermissionEditorField.vue; FormMode has preview; mapWinterUrl maps preview/:id; PartialHost.vue has a hint prop; style kit classes .summer-callout, .summer-callout--warning, .summer-callout--danger, .summer-callout__title, .summer-callout__text.

Phrase keys added in en and pl: backend::lang.form.{return_to_preview, locked_item, locked_note, show_password, hide_password}, backend::lang.permissioneditor.{allow, inherit, deny, locked, empty, other}, backend::lang.messages.form.{preview, edit}.

Fixture: acme.roster gained the preview block with the status partial, the fields joined_ip, password, password_confirmation, notify, slug, permissions, team, tags, an invisible email column, a visible slug column and the tagged filter. Helpers: newRosterEnvWith, rosterBootWith; rosterPlugin has db and relations.

Known contract properties

The user was shown these six before choosing tag-local and accepted them.

  1. Settings forms and relation (manage and pivot) forms refuse password, permissioneditor and preset at boot.
  2. Virtual values reach hooks as decoded from JSON: a string, a bool, a json.Number or nil. A number is a json.Number, not an int or float.
  3. The SPA sends a permissioneditor field on every update, reduced to the offered codes.
  4. Relation locks are enforced on form create and update only. The relation-manager link and unlink routes do not ask RelationLockProvider.
  5. pact.FilterOptions has no context parameter, so a controller serving choices uses the database handle it holds, without the request's principal.
  6. A locked permission code whose stored value is outside the mode's set makes every save of that record a 403, because the SPA cannot send the stored value back.

Task Commits

  1. Task 1: read-only preview screen (tracer) - a65c670 (feat). The user approved the screen at the tracer checkpoint.
  2. Task 2: password and form-only fields, rules per operation, preset - a1c6bb1 (feat)
  3. Task 3: permissioneditor field - f50d9b8 (feat)
  4. Task 4: writable foreign keys, locked relation options, invisible columns, controller filter choices - df5cace (feat)
  5. Task 5: checkpoint:decision - answered tag-local; no commit
  6. Task 6: gates and tag - no source change; annotated tag v0.1.3 on df5cace

Decisions Made

  • tag-local at Task 5 (the user's decision): the tag exists locally and the push is a pending release step.
  • password, permissioneditor and preset are refused outside ordinary controller forms, because settings and relation forms have no hook path that could consume them safely.
  • The null preview: check reads the document's root keys, since the YAML library does not call a custom unmarshaler for a null value.
  • PreviewView.vue provides a read-only FORM_SESSION, so a fileupload field can list its files on the preview.
  • editablePayload reduces a permissioneditor value to the offered codes, so codes the server no longer offers are never sent back.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] A null preview: was not refused by the decoder alone

  • Found during: Task 1
  • Issue: goccy does not call a custom unmarshaler for null, so preview: with no value compiled as "no preview".
  • Fix: CompileForm checks the root keys (topLevelKey, using goccy/go-yaml/parser, which is already in the module; go.mod unchanged).
  • Committed in: a65c670

2. [Rule 3 - Blocking] Fixtures and tests for the two new required form messages and the boot rule

  • Found during: Task 1
  • Issue: FormMessages gained two required keys, and recordActions now needs preview.
  • Fix: four typed SPA form fixtures got preview and edit; the conformance fixture and the unregistered case of TestFormSchemaRecordActionsBoot got preview: {}; winterUrl.test.ts, edit.smoke.test.ts and actions.smoke.test.ts were updated (outside files_modified).
  • Committed in: a65c670

3. [Rule 3 - Blocking] Existing creates needed a password; the fixture stamps the tenant

  • Found during: Task 2
  • Issue: the fixture's FormRules makes a password required on create, which broke two existing creates.
  • Fix: phase121_actions_test.go and TestPreviewSmoke send a password pair; the fixture's FormBeforeCreate stamps the tenant.
  • Committed in: a1c6bb1

4. [Rule 2 - Missing critical] password and preset refused on settings and relation forms

  • Found during: Task 2
  • Issue: these forms have no virtual-field path, so a password there would have been treated as a column.
  • Fix: boot errors in settings.go and relation_form.go (outside files_modified); mergedRules gained a ctx parameter (relation.go, relation_child.go).
  • Committed in: a1c6bb1

5. [Rule 3 - Blocking] Docs fences on methods need a whole example file

  • Found during: Task 2
  • Fix: example_form_seams_test.go has Example_formSeams, so the src= fences resolve.
  • Committed in: a1c6bb1

6. [Rule 3 - Blocking] Datepicker tests expected the old mode message

  • Found during: Task 3
  • Fix: datepicker_test.go and datepicker_smoke_test.go updated for "mode is only valid on type: fileupload, datepicker or permissioneditor".
  • Committed in: f50d9b8

7. [Rule 1 - Bug] The SPA could send permission codes that are no longer offered

  • Found during: Task 3
  • Fix: editablePayload reduces the value through permissionValues in admin/src/components/form/control.ts (outside the task's file list).
  • Committed in: f50d9b8

8. [Rule 2 - Missing critical] permissioneditor refused on settings and relation forms

  • Found during: Task 3
  • Committed in: f50d9b8

9. [Rule 3 - Blocking] An invisible email column left the roster list with one visible column

  • Found during: Task 4
  • Issue: a plan 01 row-state assertion needs a second visible column.
  • Fix: columns.yaml and the SPA list fixtures gained a visible slug column.
  • Committed in: df5cace

Other departures from the plan text

  • Task 1: PartialHost.vue gained a hint prop for the 68px skeleton and the keep-previous behaviour.
  • Task 2: preview.smoke.test.ts lists the new slug field in its dt check; editablePayload gained an optional mode argument.
  • Task 3: PreviewField.vue needed no change for the permission editor; the provider-missing boot test uses the conformance fixture.
  • Task 4: summer docs:sync rewrote the config_list.yaml fence in docs/backend/admin-controllers.md; query.go needed no change; the SPA list-schema fixture keeps filters: []; the new file testdata/roster/controllers/people/config_filter.yaml and the helpers newRosterEnvWith and rosterBootWith were added.
  • Task 6: no source change. The plugin-module runs in the gate section are an addition to the plan's gate.

Total deviations: 9 auto-fixed (2 bugs, 2 missing critical, 5 blocking) and the departures listed above. Impact on plan: No scope added. Every extra file is a test, fixture or example the new keys require, or a refusal that keeps a new field type off forms that cannot handle it.

Issues Encountered

Two application tests fail against the tagged framework. They are outside the plan's gate and were found by an extra run in Task 6.

  • go -C ../fonoteka.go/plugins/golem15/fonoteka test ./... -count=1 fails in TestPhase09SecurityRoutes and TestPhase10ControllerCopy.
  • Both compare against fixed lists in the application's tests. phase09AdminRoutes does not name the two plan 01 routes (bulk action, record action). phase10ListMessageKeys does not name the three plan 01 row-state messages. The form half of TestPhase10ControllerCopy was not reached and may need preview and edit in the same way.
  • No framework change is needed, so the tagged commit is unaffected. The fix is in fonoteka.go, which this plan does not write to. The same catch-up happened once before (549840d in fonoteka.go, for the Phase 12.2 routes).
  • The plan's application gate passed because go -C ../fonoteka.go test ./... covers the root module only; the four go.work plugin modules are separate modules.
  • Recorded in deferred-items.md in this phase directory, with plan 04 or the plan 05 gate script as the suggested owner.

The tag was still created: every gate the plan defines passed on df5cace, the failures need no change to that commit, and the tag is local and can be moved.

Not verified here

  • A form-level required: true on a virtual field has no test (Task 2).
  • The checkbox-mode value set of the permission editor has no HTTP test on the server; radio mode has (Task 3).
  • The model-only FilterOptions path relies on the existing TestPhase10FilterOptions; no new test was added for it.
  • Visual checks of the permission editor, the locked chips and the password toggle in a browser. Only the preview screen was looked at by the user.
  • scripts/check-phase10.sh and the other phase gates were not run; only the commands the plan names.

Open items for plan 05's review

  1. Relation locks and the relation manager (property 4). A locked id can still be linked or unlinked through the relation-manager routes, because only form saves call checkRelationLocks. Plan 04's privileged-group guard (T-12-18) must cover those routes in the plugin, or the framework needs a follow-up.
  2. Locked permission with a stored value outside the mode's set (property 6). Every save of such a record is a 403. Decide whether the server should compare a locked code only when it was submitted, or whether the plugin must normalize stored values.
  3. Application inventory tests (see Issues Encountered).
  4. The three untested paths under "Not verified here".

Known Stubs

None. RecordActions.vue is mounted in PreviewView.vue, and WINDOWS entry 9 is marked fixed.

The demo fixture used at the tracer checkpoint registers no navigation, so the SPA home shows "No sections are available." and the list was opened by direct URL. This is a property of the test fixture, not a defect.

User Setup Required

None - no external service configuration required.

Next Phase Readiness

  • Plan 12.1-03 has its precondition: v0.1.3 exists locally, and the application resolves the framework through its local replace.
  • No Go module and no npm package changed: git diff --stat 1c99de5..df5cace -- go.mod go.sum admin/package.json admin/package-lock.json is empty.
  • Pending: push master and v0.1.3; the sm-user-plugin push of plan 05 waits for it.
  • Pending: the two application inventory tests in fonoteka.go.
  • A demo server for the user is still running on 127.0.0.1:8431; this plan left it alone.

Self-Check: PASSED

  • Created files exist: field_permission.go, phase121_form_test.go, example_form_seams_test.go, the two new roster fixture files, the four SPA components, the two smoke tests, roster.form-schema.json.
  • Commits exist: a65c670, a1c6bb1, f50d9b8, df5cace; git rev-list --count 1c99de5..HEAD was 4 when this summary was written.
  • Tag: git tag --list v0.1.3 prints v0.1.3; git cat-file -t v0.1.3 prints tag; git rev-parse 'v0.1.3^{commit}' is df5cace8525bc10fa40b25552137290793c08bb3; git merge-base --is-ancestor v0.1.3 HEAD succeeds; v0.1.1 and v0.1.2 are unchanged.
  • Key links: name: 'preview' once in router.ts; RecordActions used in PreviewView.vue; the ten Test* functions named in the coverage block are in phase121_form_test.go.
  • Nothing was pushed: git ls-remote --tags origin v0.1.3 lists nothing.

Phase: 12.1-user-plugin-admin-screens Completed: 2026-10-05