Phase 12.1: User plugin admin screens - Discussion Log
Audit trail only. Do not use as input to planning, research, or execution agents.
Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
Date: 2026-10-04
Phase: 12.1-user-plugin-admin-screens
Areas discussed: Impersonate and convert-guest, Group membership and the admin group, User actions and trashed users, Fields the Go model lacks, Release
Impersonate and convert-guest
Should 'impersonate user' be part of this phase?
| Option |
Description |
Selected |
| Out of scope (Recommended) |
PHP impersonation is session-based and never reaches the JWT-driven Nuxt app; permission not registered |
|
| Out, but register the permission |
No action, but golem15.users.impersonate_user is registered so imported roles stay valid |
✓ |
| In scope: mint a user JWT |
Admin gets a short-lived frontend token for the user, with audit; needs security review and a Nuxt entry point |
|
User's choice: Out, but register the permission
What happens to convert-guest and the guest concept?
| Option |
Description |
Selected |
| Drop it entirely (Recommended) |
No action, no is_guest column or list column, no guest hint |
✓ |
| Add is_guest, skip the action |
Column and invisible list column for schema fidelity, no popup |
|
| Port convert-guest fully |
Column, popup with group choice, optional registration mail |
|
User's choice: Drop it entirely
Notes: The application never creates guests and the Go users table has no is_guest.
Group membership and the admin group
Which backend permission is required to change a user's group membership?
| Option |
Description |
Selected |
| Extra permission for privileged groups (Recommended) |
access_users for ordinary groups; privileged groups need a new permission, server-side |
✓ |
| access_users only (PHP parity) |
Any user editor can put a user in any group, including admin |
|
| New permission for all membership changes |
Any users_groups change needs a dedicated permission |
|
How does the plugin know which groups are privileged?
| Option |
Description |
Selected |
| Config list, default [admin] (Recommended) |
golem15.user config key listing privileged codes |
✓ |
Hard-coded code admin |
Only admin is privileged, no config |
|
| A flag column on user_groups |
New boolean on the group row |
|
How should the User Groups screen guard privileged codes?
| Option |
Description |
Selected |
| Privileged codes need the extra permission (Recommended) |
Create with, rename to/from, and delete of a privileged code need the extra permission |
✓ |
| Code is read-only after create |
Code never changes once saved |
|
| No guard (PHP parity) |
access_groups edits any group including its code |
|
How do privileged groups behave for an admin without the permission?
| Option |
Description |
Selected |
| Shown but locked, 403 on tamper (Recommended) |
Visible and disabled; a tampered save is refused and changes nothing |
✓ |
| Hidden from the options |
Left out of the field; existing memberships kept |
|
| Whole save refused, no UI hint |
Field looks normal; validation error if a privileged group changed |
|
User actions and trashed users
How should the Go admin carry the six bulk actions and three record actions?
| Option |
Description |
Selected |
| Extend cabana generically (Recommended) |
Framework gains declared bulk actions and record actions |
✓ |
| Plugin-side with existing pieces |
Widget buttons on the form; bulk actions dropped except delete |
|
| Record actions only, bulk later |
Record buttons now, bulk to the backlog |
|
Delete semantics and trashed users
| Option |
Description |
Selected |
| PHP parity (Recommended) |
Trashed users listed; deactivate = soft delete, restore, delete = permanent |
✓ |
| Parity, but force delete needs confirmation by email |
Same, plus typing the user's email before a permanent delete |
|
| Soft delete only |
No permanent delete in the admin |
|
Where do the status and the actions live (PHP has a preview screen)?
| Option |
Description |
Selected |
| On the update form (Recommended) |
Status notice and conditional action buttons on the update form; no preview |
|
| Add a preview screen to the framework |
cabana gains Winter's preview context with its own toolbar |
✓ |
| Actions always visible, no status notice |
All buttons shown regardless of state |
|
User's choice: Add a preview screen to the framework
Notes: Chosen over the recommended lighter option; PHP parity of the admin experience is the bar.
How should the list show user state?
| Option |
Description |
Selected |
| Generic row state in the framework (Recommended) |
Controller returns a state per row from a fixed set; SPA styles with tokens |
✓ |
| A status column |
One computed Status column, no row styling |
|
| Both |
Row state and a status column |
|
Fields the Go model lacks
Port the frontend Permissions tab (FrontendPermissionEditor)?
| Option |
Description |
Selected |
| Leave out, defer (Recommended) |
No Permissions tab; its own later phase |
|
| Port the editor |
Registry table, users.permissions, editor on both forms |
✓ |
User's choice: Port the editor
How far does the frontend-permissions port go?
| Option |
Description |
Selected |
| Storage, editor and resolver (Recommended) |
Table, column, editor, and a Go getMergedPermissions equivalent |
✓ |
| Storage and editor only |
No resolver until a project needs it |
|
| Also a plugin-declared registry |
Plugins declare frontend permissions in Go, synced at boot |
|
How should the editor be built?
| Option |
Description |
Selected |
| Built-in framework field type (Recommended) |
permissioneditor in cabana and the SPA, reusable for backend roles later |
✓ |
| Extend the widget contract |
type: widget carries a structured value; plugin ships a custom element |
|
Block mail (MailBlocker)
| Option |
Description |
Selected |
| Leave out, defer (Recommended) |
No checkbox; a mail feature, not an admin screen |
|
| Port MailBlocker now |
Table, form flag, mailer check |
|
User's choice: (free text) "Leave out, write in todos to implement together with mailing development (so together with mail settings, templates etc)"
Notes: Todo written: .planning/todos/pending/mailblocker-with-mailing.md.
username and last_seen
| Option |
Description |
Selected |
| Drop both from the YAML (Recommended) |
Only columns the Go model has |
|
| Add last_seen, drop username |
Additive last_seen updated by the auth path |
✓ |
| Add both columns |
Full schema fidelity |
|
Create form: password, invite, avatar
| Option |
Description |
Selected |
| All of it (Recommended) |
Password + confirmation, reset on update, send_invite mail, avatars on users and organisations |
✓ |
| Password and avatar, no invite |
No invitation mail |
|
| Password only |
No invite, no avatar fields |
|
Release
| Option |
Description |
Selected |
| Framework first, tagged v0.1.2 (Recommended) |
Framework work lands first and is tagged; plugin builds on the tag |
✓ (as v0.1.3) |
| One phase, no tag |
Ship together through the local replace |
|
| Split the framework work into its own phase |
Insert 12.3 for the cabana features |
|
User's choice: (free text) "Framework first, but i belive it's v0.1.3, v0.1.2 already exists"
Notes: Verified with git tag: v0.1.2 exists, so the tag is v0.1.3.
Claude's Discretion
- The extra permission's code, label and tab; the privileged-list config key name.
- YAML keys and Go interface names for bulk actions, record actions, preview and row state.
- The row state set and styling; preview screen contents beyond hints, fields and actions.
- When
last_seen is written; force-delete cleanup details.
- Plan count and split (subject to the plan-count checkpoint).
Deferred Ideas
- Impersonation by minting a short-lived user JWT.
- MailBlocker with the mailing work (todo written).
- Guests and convert-guest;
username and login by username.
- Plugin-declared frontend permission registry;
permissioneditor for backend roles.
- The user plugin's Settings screen.