From 6bf1943f8a3f750f5c3ae0b396c7a0503fddb642 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Sun, 4 Oct 2026 14:52:44 +0200 Subject: [PATCH] docs(12.1): capture phase context --- .../12.1-CONTEXT.md | 159 +++++++++++++++ .../12.1-DISCUSSION-LOG.md | 193 ++++++++++++++++++ .../todos/pending/mailblocker-with-mailing.md | 12 ++ 3 files changed, 364 insertions(+) create mode 100644 .planning/phases/12.1-user-plugin-admin-screens/12.1-CONTEXT.md create mode 100644 .planning/phases/12.1-user-plugin-admin-screens/12.1-DISCUSSION-LOG.md create mode 100644 .planning/todos/pending/mailblocker-with-mailing.md diff --git a/.planning/phases/12.1-user-plugin-admin-screens/12.1-CONTEXT.md b/.planning/phases/12.1-user-plugin-admin-screens/12.1-CONTEXT.md new file mode 100644 index 0000000..68a0a0e --- /dev/null +++ b/.planning/phases/12.1-user-plugin-admin-screens/12.1-CONTEXT.md @@ -0,0 +1,159 @@ +# Phase 12.1: User plugin admin screens - Context + +**Gathered:** 2026-10-04 +**Status:** Ready for planning + + +## Phase Boundary + +Backend admins manage frontend users, user groups and organisations in the admin SPA, so the PHP backend is not needed for user administration after cutover. The PHP user plugin's `Users`, `UserGroups` and `Organisations` screens are ported to `golem15.user` (`sm-user-plugin`, mounted in fonoteka.go at `plugins/golem15/user`), driven by its `fields.yaml` / `columns.yaml`. + +The discussion widened the framework half of the phase. `summercms.go` (`modules/cabana`, `modules/pact`, the admin SPA, READMEs and `docs/`) gains five generic admin features the screens need: declared bulk actions, record actions, a preview context, row state, and a `permissioneditor` field type. That work lands first and is tagged **v0.1.3**; the plugin screens build on the tag. + +Out of scope: impersonating a user, the guest concept and convert-guest, MailBlocker / block-mail, a `username` column, the user plugin's Settings screen, and any change to the user API payloads (the Nuxt app's contract stays byte-identical). + + + + +## Implementation Decisions + +### Impersonate and guests +- **D-01:** Impersonate is not ported: no action, no button. PHP impersonation is session-based and never reaches the JWT-driven frontend, so nothing is lost at cutover. +- **D-02:** The permission `golem15.users.impersonate_user` is still registered by the Go plugin, so backend roles imported at cutover that reference it stay valid and a later phase can use it. The plugin registers all four PHP codes: `golem15.users.access_users`, `access_groups`, `access_settings`, `impersonate_user`. +- **D-03:** The guest concept is dropped entirely: no convert-guest action, no `is_guest` column or list column, no guest hint. The seeded Guest group row stays as data. + +### Group membership and privileged groups (T-12-18) +- **D-04:** `golem15.users.access_users` is enough to change a user's membership of ordinary groups, as in PHP. Adding or removing a **privileged** group additionally requires a new backend permission. The check is on the server and covers every path that writes `users_groups` (the user form's groups field, any relation manager, bulk or record actions). — **Reversibility:** costly — the permission code is stored in backend roles; renaming it later means rewriting role rows in every host application. +- **D-05:** Privileged groups are a config list of group codes under the `golem15.user` config namespace, default `[admin]`. The plugin is shared across projects, so an application adds a code without a plugin change. +- **D-06:** The User Groups screen is gated by `golem15.users.access_groups`. Creating a group with a privileged code, changing a code to or from a privileged one, and deleting a privileged group all require the same extra permission as D-04. Ordinary groups are edited freely. +- **D-07:** For an admin without the extra permission, privileged groups are shown in the user form's groups field but locked (disabled), so the admin can see that a user is a site admin. A save that tries to change a privileged membership anyway is refused with a forbidden error and changes nothing (no partial save). +- **D-08:** `User.Groups` stays out of every user API payload (P12 D-25). Only the admin API reads or writes it. + +### Actions, delete semantics and preview (framework + plugin) +- **D-09:** cabana gains **declared bulk actions**, generic for every plugin: a controller registers named bulk actions, the list declares which ones it offers, the SPA sends the selected ids, and the framework resolves them through the controller's list scope inside one transaction before calling the plugin. Ids outside the scope never reach the plugin. Bulk delete keeps working as today. — **Reversibility:** costly — list YAML and the `pact` action contract grow; every plugin's list config may come to depend on the shape. +- **D-10:** cabana gains **record actions**: named actions shown as buttons for one record, run against the record loaded through the controller's form scope, each with its own permissions on top of the controller's, and each able to say whether it applies to the record's current state. — **Reversibility:** costly — same contract growth as D-09. +- **D-11:** cabana gains Winter's **preview context**: a read-only record screen with its own toolbar, fields with `context: preview`, and a `recordUrl` that may point at it. The Users list opens preview first, then Update, as in PHP. Status hints (not activated, banned, suspended, deleted) and the record actions live on the preview screen. — **Reversibility:** costly — a new screen and route in the SPA and a new context value in the form schema. +- **D-12:** cabana gains **row state**: a controller returns a state per list row from a fixed framework-defined set (Winter's `listInjectRowClass`, limited to known states such as deleted, negative, disabled, not free CSS classes). The SPA styles states with design tokens. Users: deleted when trashed, negative when banned, disabled when not activated. +- **D-13:** Delete semantics follow PHP `Users.php` exactly. The Users list and form include trashed users (`withTrashed`). `deactivate` is the soft delete, `restore` brings a user back, and `delete` (form button and bulk) is a permanent force delete. No extra typed confirmation beyond the standard confirm. +- **D-14:** Bulk actions on Users, as in PHP `index_onBulkAction`: delete, activate, deactivate, restore, ban, unban. Record actions: activate, unban, unsuspend. Ban and suspend state lives in the existing `user_throttle` table. + +### Fields and columns +- **D-15:** Frontend permissions are ported in full: the `golem15_user_frontend_permissions` table (code, label, tab, comment), the `users.permissions` column, the existing `user_groups.permissions` column, the Permissions tab on the user form (radio mode: allow / deny / inherit) and the group form (checkbox mode), and a Go resolver equivalent to PHP `User::getMergedPermissions()` that a plugin can call to ask whether a user holds a permission. Nothing in the application calls the resolver yet; unit tests prove it. No plugin-declared registry: rows come from migrations or seed, as in PHP. — **Reversibility:** costly — additive migrations in the shared user plugin; the stored JSON shape must match PHP for the cutover import. +- **D-16:** The editor is a built-in framework field type, `permissioneditor`, in cabana and the SPA: tabbed, with a radio or checkbox `mode`, and an option list supplied by the controller. It replaces the PHP YAML's `Golem15\User\FormWidgets\FrontendPermissionEditor` class name. It is built to be reusable for backend role permissions later. The 10.1 widget contract (scalar fill keys only) is not widened. — **Reversibility:** costly — a new field type in the typed schema and the generated TS types. +- **D-17:** `last_seen` is added to `users` as an additive column, updated by the Go auth path on login or token refresh, and shown in the Users list as in PHP. It must not appear in any user API payload unless PHP already returns it. +- **D-18:** `username` is dropped from the ported YAML and no column is added (login is by email). +- **D-19:** The user form carries the PHP create/update behaviour: password with confirmation on create, password reset on update, `send_invite` (default on, create only) that sends the invitation mail, and `created_ip_address` / `last_ip_address` on preview. +- **D-20:** Avatars use the Phase 12.2 `fileupload` field (`mode: image`) on both the user form (260x260) and the organisation form (120x120). They must bind to the same attachment the user API already serves, so an avatar set in the admin shows in the app and the other way round. +- **D-21:** `block_mail` and MailBlocker are left out. A todo records them for the mailing work (mail settings, templates). + +### Screens, as ported +- **D-22:** Organisations: list plus form (name, slug with preset from name, description, avatar) gated by `golem15.users.access_users`, and a `members` relation manager that assigns existing users (PHP `add|remove` on the hasMany, which sets or clears the user's `organisation_id`). The user form's `organisation` field is a belongsTo `relation` picker. +- **D-23:** Users filters follow PHP `config_filter.yaml`: groups (scope `filterByGroup`), created date (daterange) and activated (switch). The PHP `conditions:` strings are re-expressed as model scopes, since a `conditions` key is a boot error (P9). +- **D-24:** User Groups list keeps the `users_count` column. + +### Release and ordering +- **D-25:** Framework first. The cabana and SPA work (D-09 to D-12, D-16) lands in the early plans with module READMEs, `docs/` pages, the admin OpenAPI document, generated TS types and the rebuilt `dist/`, and is tagged **v0.1.3** (`v0.1.2` already exists). The plugin screens build on that tag. Framework fixtures use neutral names and never name the application. + +### Claude's Discretion +- The code of the extra permission in D-04 (for example `golem15.users.manage_privileged_groups`), its label and tab, and the config key name for the privileged list. +- YAML keys and Go interface names for bulk actions, record actions, preview and row state, provided they follow the existing fail-loud rules (unknown keys and unregistered actions are boot errors), sit under the `{prefix}/api/v1/{vendor}/{plugin}/{controller}/...` scheme, use `requireAjax` on writes and carry swag annotations. +- The exact set of row states and their token styling; whether a state is also announced in text for accessibility. +- What the preview screen shows beyond status hints, preview-context fields and actions (PHP's scoreboard is optional). +- Whether `last_seen` is written on login only or also on refresh, and any throttling of that write. +- How force delete cleans up what hangs off a user (attachments, throttle, groups pivot), following PHP `User` delete behaviour. +- Plan count and split, subject to the plan-count checkpoint and "unit tests are the last plan". Run the security-review agent: the phase touches authorization (T-12-18), the plugin API (`pact`) and destructive bulk operations. + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Roadmap and prior decisions +- `.planning/ROADMAP.md` § "Phase 12.1" — goal, five success criteria, open questions (now answered by D-01 to D-07) +- `.planning/phases/12-p-ytarium-api-collections-and-albums/12-01-PLAN.md` — user groups (D-25) and threat T-12-18, which this phase revisits +- `.planning/phases/12-p-ytarium-api-collections-and-albums/12-CONTEXT.md` — D-25 site-admin predicate (group code `admin`) +- `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md` — typed schema, fail-loud boot, permissions (D-03), hooks (D-13), filters and scopes +- `.planning/phases/10-admin-vue-spa/10-CONTEXT.md` — SPA stack, FieldRenderer registry, toolbar buttons (D-14), cookie auth and CSRF, OpenAPI to TS types (D-15), committed `dist/` +- `.planning/phases/10.1-runtime-admin-extension-point/10.1-CONTEXT.md` — `AdminAction` contract, why toolbar actions carry no record ids (D-12), widget fill limits (D-05 to D-07) +- `.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-CONTEXT.md` — `fileupload`, `attach.Relation`, relation child CRUD and parent scoping (D-15) + +### PHP reference (read-only): `/media/nvme/dev/golem15/fonoteka/plugins/golem15/user` +- `controllers/Users.php` — actions, bulk actions, `withTrashed`, `listInjectRowClass`, force delete +- `controllers/users/` — `config_list.yaml`, `config_form.yaml`, `config_filter.yaml`, `config_relation.yaml`, `_list_toolbar.htm`, `_preview_toolbar.htm`, `_hint_*.htm` +- `controllers/UserGroups.php`, `controllers/usergroups/`, `controllers/Organisations.php`, `controllers/organisations/` (incl. `config_relation.yaml`) +- `models/user/fields.yaml`, `models/user/columns.yaml`, `models/usergroup/*.yaml`, `models/organisation/*.yaml` +- `models/User.php` — `getMergedPermissions`, `attemptActivation`, `ban` / `unban`, `unsuspend`, `sendInvitation`, delete behaviour +- `models/FrontendPermission.php`, `formwidgets/FrontendPermissionEditor.php` and its partials, `updates/v2.8.0/` — frontend permissions (D-15, D-16) +- `Plugin.php` — `registerPermissions`, `registerNavigation` + +### Go code this phase touches +- `../fonoteka.go/plugins/golem15/user/` (repo `sm-user-plugin`) — `plugin.go`, `models/`, `classes/`, `updates/`, `lang/`, `README.md` +- `modules/cabana/` — `actions.go`, `list_schema.go`, `form_schema.go`, `filter_schema.go`, `crud.go`, `http.go`, `relation*.go`, `README.md` +- `modules/pact/capabilities.go` — `AdminAction`, `HasAdminActions`, `Permission`, `NavigationItem` +- `admin/src/` — form field registry, list view and toolbar, relation manager +- `../fonoteka.go/plugins/golem15/fonoteka/controllers/*_admin_controller.go` — working examples of admin controllers in an application plugin +- `docs/backend/` — `admin-controllers.md`, `forms.md`, `lists-and-filters.md`, `relation-manager.md`, `users-and-permissions.md`; `go test ./cmd/summer -run TestDocsTree` and `summer docs:build --check` must pass + + + + +## Existing Code Insights + +### Reusable Assets +- `sm-user-plugin` already has `models.User` (soft delete via `DeletedAt`, `Groups` many2many never serialized), `models.UserGroup`, `models.Organisation`, `models.Throttle` (`is_banned`, `is_suspended`), and `classes.UserGroupCodes` / `HasGroupCode`. It has no admin controllers, permissions or navigation yet. +- cabana: list, form, filters (`switch`, `daterange`, model scopes), `relation` and `relation-manager` fields with child CRUD and parent scoping, `fileupload`, `datepicker`, `widget`, `partial`, toolbar actions, bulk delete. +- `pact.AdminAction` / `HasAdminActions`: the existing action contract that bulk and record actions extend. +- The Go user API already stores user avatars; D-20 reuses that attachment. + +### Established Patterns +- Optional capabilities are interfaces the framework type-asserts (`Has*`, admin hooks). +- Unknown YAML keys, unknown types, unregistered actions and a `conditions:` filter fail boot. +- Every write route uses `requireAjax`, sits under the `backend` guard and enforces `RequiredPermissions`; an action's own permissions are checked on top. +- Toolbar actions deliberately take no record ids, so an id list can never become an unscoped lookup. Bulk actions (D-09) must keep that property by resolving ids through the list scope. +- Framework stays app-agnostic: contract tests use a nameless fixture plugin; the real screens live in the plugin repo. +- Framework changes update the module README, `docs/`, the admin OpenAPI document, generated TS types and the committed `dist/` in the same change. + +### Integration Points +- `sm-user-plugin` `plugin.go`: add `pact.HasAdminControllers`, `HasPermissions`, navigation, and embed the admin YAML. +- New additive migrations in `sm-user-plugin/updates`: `users.permissions`, `users.last_seen`, `golem15_user_frontend_permissions`. The fonoteka.go parity schema allow-list needs matching entries where the frozen PHP snapshot differs. +- The Go auth path (login / refresh) writes `last_seen`. +- fonoteka.go bumps the submodule pointer and the framework version (v0.1.3). + + + + +## Specific Ideas + +- "Framework first, but I believe it's v0.1.3, v0.1.2 already exists" — the tag for this phase's framework work is v0.1.3. +- The user chose the fuller option over the lean one in three places: a real preview screen in the framework (not status on the update form), the frontend permission editor ported now (not deferred), and `last_seen` added. Treat PHP parity of the admin experience as the bar, not minimum viable. +- Block-mail is wanted, but together with the mailing work (mail settings, templates), not here. + + + + +## Deferred Ideas + +- Impersonate a frontend user from the admin by minting a short-lived user JWT, with an audit record and a Nuxt entry point. The permission is already registered (D-02). +- MailBlocker and the block-mail checkbox: recorded as todo `mailblocker-with-mailing.md`, to be done with the mailing development. +- Guest users and convert-guest, for a project that uses guests. +- A `username` column and login by username. +- A plugin-declared frontend permission registry synced at boot. +- Using `permissioneditor` for backend role permissions. +- The user plugin's Settings screen (`access_settings`). + +### Reviewed Todos (not folded) +- `nest-framework-packages-under-modules.md`, `per-module-readmes-after-nest.md`, `readme-go-fences-src.md`, `rewrite-summercms-readme.md`, `refresh-fonoteka-readme.md`: repo and README housekeeping; keyword matches only. +- `2026-10-01-benchmark-the-application-on-wintercms-vs-summercms.md`: benchmarking, unrelated. +- `backend-admin-api-tokens.md`: backend admin auth, not frontend user administration. +- `orphan-pending-routes.md`: covered by Phase 14.1. +- `scaffold-admin-controller-incomplete.md`, `scaffold-same-second-migration-order.md`, `scaffold-generated-header-and-command-deps.md`, `bonfire-duplicate-command-names.md`: CLI scaffolding bugs; worth knowing if the planner uses `make:admin-controller`, but not this phase's scope. +- `lagoon-readme-after-commit-callback-order.md`, `sitemap-plugin-port.md`: unrelated. + + + +--- + +*Phase: 12.1-user-plugin-admin-screens* +*Context gathered: 2026-10-04* diff --git a/.planning/phases/12.1-user-plugin-admin-screens/12.1-DISCUSSION-LOG.md b/.planning/phases/12.1-user-plugin-admin-screens/12.1-DISCUSSION-LOG.md new file mode 100644 index 0000000..87366d1 --- /dev/null +++ b/.planning/phases/12.1-user-plugin-admin-screens/12.1-DISCUSSION-LOG.md @@ -0,0 +1,193 @@ +# 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. diff --git a/.planning/todos/pending/mailblocker-with-mailing.md b/.planning/todos/pending/mailblocker-with-mailing.md new file mode 100644 index 0000000..7da6860 --- /dev/null +++ b/.planning/todos/pending/mailblocker-with-mailing.md @@ -0,0 +1,12 @@ +--- +title: Port MailBlocker and the block-mail checkbox with the mailing work +date: 2026-10-04 +priority: medium +area: sm-user-plugin mail +--- + +The PHP user form has a "block all mail to this user" checkbox (`block_mail`, update context) backed by `Golem15\User\Models\MailBlocker`. Phase 12.1 left it out (12.1-CONTEXT D-21): it needs its own table and a check in every mail send path, which is a mail feature rather than an admin screen. + +Implement it together with the mailing development (mail settings, mail templates): the MailBlocker table and model, the mailer consulting it before sending to a user, and the `block_mail` checkbox on the user admin form. + +PHP source: `/media/nvme/dev/golem15/fonoteka/plugins/golem15/user/models/MailBlocker.php`, `controllers/Users.php` (`formAfterUpdate`, `formExtendModel`).