docs(12.1): UI design contract
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
phase: "12.1"
|
phase: "12.1"
|
||||||
slug: "user-plugin-admin-screens"
|
slug: "user-plugin-admin-screens"
|
||||||
status: draft
|
status: approved
|
||||||
|
reviewed_at: "2026-10-04"
|
||||||
shadcn_initialized: false
|
shadcn_initialized: false
|
||||||
preset: none
|
preset: none
|
||||||
created: "2026-10-04"
|
created: "2026-10-04"
|
||||||
@@ -479,57 +480,117 @@ No preview: rows open the update form. The relation manager's visuals are the 12
|
|||||||
|
|
||||||
## UI Considerations
|
## UI Considerations
|
||||||
|
|
||||||
Applicable state considerations resolved: 37 covered, 5 backstop, 2 dismissed, 0 unresolved.
|
State coverage from the UI-consideration probe (run 2026-10-04 after checker approval; element kinds confirmed by the user). 99 applicable considerations, plus 4 extra backstop rows: 66 resolved explicit, 5 resolved backstop, 32 dismissed, 0 unresolved.
|
||||||
|
|
||||||
| Category | Element(s) | Status | Resolution / Reason |
|
How the planner lifts a row: `resolved` / `explicit` → the statement is a `must_haves.truths` string; `resolved` / `backstop` → the flat scalar shown; `dismissed` → not lifted. Empty-state and error copy stays in the Copywriting Contract and Plugin screens sections; rows here reference the keys.
|
||||||
|----------|------------|--------|---------------------|
|
|
||||||
| empty | S1 bulk menu | ✅ covered | With zero permitted bulk actions the menu is not rendered; with nothing selected the trigger is disabled and keeps its label |
|
Elements: S1 bulk actions menu · S2 record actions · S3 preview screen · S4 row state · S5 `permissioneditor` · S6a locked relation options · S6b forbidden banner · S7a `password` field · S7b `preset` · P1 Users list · P2 Users form · P3 Users preview · P4 User Groups · P5 Organisations · P6 admin navigation.
|
||||||
| loading | S1 bulk action in flight | ✅ covered | The confirm dialog stays open with both buttons disabled and a spinner on the confirm button until the POST settles; the trigger is disabled |
|
|
||||||
| error | S1 bulk action | ✅ covered | 409: danger toast `list.bulk_stale`, selection cleared, list reloaded; 403: danger toast (server message or `list.action_forbidden`); other: danger toast (server message or `extension.action_failed`), selection kept |
|
| Element | Category | Status | Verification | Statement / Reason |
|
||||||
| populated | S1 bulk menu | ✅ covered | Items in declared order, 14/400, 40px minimum height, no icons or separators |
|
|---------|----------|--------|--------------|--------------------|
|
||||||
| zero-one-many | S1 bulk action count | ✅ covered | The default confirm and the `list.bulk_done` toast use `:count` with CLDR plural forms; one selected row and many share one flow |
|
| S1 | empty | resolved | explicit | With zero permitted bulk actions the bulk menu is not rendered; with nothing selected the trigger is disabled and keeps its label |
|
||||||
| overflow | S1 bulk menu | ✅ covered | Many actions extend the menu downward; Reka keeps it inside the viewport with collision handling; the toolbar cluster wraps with the existing `flex-wrap` |
|
| S1 | loading | resolved | explicit | While a bulk action is in flight the confirm dialog stays open with both buttons disabled and a spinner on the confirm button until the POST settles, and the menu trigger is disabled |
|
||||||
| long-text | S1 menu item and confirm | ✅ covered | A long label wraps inside the 240–320px menu and is never truncated; the confirm message wraps inside the 440px dialog |
|
| S1 | error | resolved | explicit | A bulk action failure shows a danger toast: 409 uses `list.bulk_stale`, clears the selection and reloads the list; 403 uses the server message or `list.action_forbidden` and keeps the selection; any other status uses the server message or `extension.action_failed`, keeps the selection and reloads nothing |
|
||||||
| partial | S1 bulk result | ✅ covered | The whole batch succeeds or rolls back (one transaction); the toast shows the server's affected count, which may be lower than the selection when the plugin skips rows |
|
| S1 | error | resolved | backstop | `{ statement: "Focus returns to the bulk menu trigger after the confirm dialog closes, by confirm and by cancel", verification: backstop }` |
|
||||||
| error | S1 focus return | 🧪 backstop | An SPA unit test asserts focus returns to the bulk menu trigger after the confirm dialog closes by confirm and by cancel |
|
| S1 | populated | resolved | explicit | Bulk menu items render in declared order at 14/400 with a 40px minimum height and no icons, groups or separators |
|
||||||
| empty | S2 record actions | ✅ covered | With no applicable action the footer shows only the edit button |
|
| S1 | partial | resolved | explicit | A bulk action succeeds or rolls back as a whole; the success toast shows the server's affected count, which may be lower than the selection when the plugin skips rows |
|
||||||
| loading | S2 record action in flight | ✅ covered | Confirm dialog busy; every footer button disabled |
|
| S1 | overflow | resolved | explicit | Many bulk actions extend the menu downward inside the viewport (Reka collision handling) and the toolbar cluster wraps |
|
||||||
| error | S2 record action | ✅ covered | 409: toast `form.action_stale` and reload; 404: the form load-failure alert; 403 and other: danger toast |
|
| S1 | zero-one-many | resolved | explicit | The default bulk confirm and the `list.bulk_done` toast use `:count` with CLDR plural forms; one selected row and many share one flow |
|
||||||
| populated | S2 after success | ✅ covered | Toast, then the record and hint reload in place; previous content stays visible during the reload |
|
| S1 | long-text | resolved | explicit | A long bulk action label wraps inside the 240–320px menu and is never truncated; the confirm message wraps inside the dialog |
|
||||||
| overflow / long-text | S2 footer buttons | ✅ covered | The footer and its right cluster use `flex-wrap`; buttons are `whitespace-nowrap` and wrap to a second row instead of shrinking |
|
| S2 | empty | resolved | explicit | With no applicable record action the preview footer shows only the edit button |
|
||||||
| loading | S3 preview | ✅ covered | As `FormView`: blank title, no card until schema and record arrive, footer buttons disabled |
|
| S2 | loading | resolved | explicit | While a record action runs the confirm dialog is busy and every footer button is disabled |
|
||||||
| error | S3 preview load / 404 | ✅ covered | The existing alert block with `form.load_failed`; the back button works |
|
| S2 | error | resolved | explicit | A record action failure: 409 shows the `form.action_stale` toast and reloads the record and hint; 404 shows the form load-failure alert; 403 and other statuses show a danger toast with the server message or the framework fallback |
|
||||||
| empty | S3 preview values | ✅ covered | An empty value shows the muted "—"; an empty multiple relation shows "—" |
|
| S2 | populated | resolved | explicit | After a record action succeeds a success toast shows and the record and status hint reload in place, with the previous content visible during the reload |
|
||||||
| populated | S3 preview | ✅ covered | `dl` grid in the form card with read-only boxes per the PreviewField table |
|
| S2 | partial | dismissed | — | Reason: A record action is either applicable and rendered or not rendered; there is no partial state |
|
||||||
| long-text | S3 preview values | ✅ covered | Values wrap with `overflow-wrap: anywhere`; textarea values keep line breaks and the box grows; the title truncates as on the form |
|
| S2 | overflow | resolved | explicit | The preview footer and its right cluster wrap; record action buttons move to a second row instead of shrinking |
|
||||||
| partial | S3 preview with tabs | ✅ covered | Fields group into the same tabs as the form; a tab with no preview-visible field is not rendered |
|
| S2 | zero-one-many | resolved | explicit | Zero applicable record actions leave only the edit button; one or many render in declared order before it |
|
||||||
| zero-one-many | S3 multiple relation | ✅ covered | None: "—"; one or many: chips wrap inside the box |
|
| S2 | long-text | resolved | explicit | Record action buttons are `whitespace-nowrap` and wrap as whole buttons; a label is never truncated |
|
||||||
| loading | S3 status hint | ✅ covered | First fetch: one 68px `bg-skel` block; refetch keeps the previous hint visible |
|
| S3 | empty | resolved | explicit | On preview an empty value shows the muted empty-value dash, and an empty multiple relation shows the same dash |
|
||||||
| empty | S3 status hint | ✅ covered | Zero nodes render nothing and take no gap |
|
| S3 | loading | resolved | explicit | While the preview loads the title is blank, the card is not rendered and the footer buttons are disabled; the status hint's first fetch shows one 68px skeleton block and a refetch keeps the previous hint visible |
|
||||||
| error | S3 status hint | ✅ covered | The extension failure box with `extension.partial_failed`; the rest of the screen works |
|
| S3 | error | resolved | explicit | A preview load failure or 404 shows the existing alert with `form.load_failed` and the back button still works; a failed status hint shows the extension failure box and the rest of the screen works |
|
||||||
| long-text | S3 callout | ✅ covered | `.summer-callout` wraps with `overflow-wrap: anywhere` and grows; never truncated |
|
| S3 | error | resolved | backstop | `{ statement: "`mapWinterUrl` maps `preview/:id` to the preview route, and opening the preview route of a form without a preview replaces it with the record route", verification: backstop }` |
|
||||||
| error | S3 route without preview | 🧪 backstop | An SPA unit test asserts `mapWinterUrl` maps `preview/:id` to the preview route and that opening the preview route of a form without preview replaces it with the record route |
|
| S3 | populated | resolved | explicit | The preview renders fields as a `dl` grid in the form card with read-only boxes per the PreviewField table |
|
||||||
| populated | S4 row state | ✅ covered | Text badge per state in the first cell plus the row text style from the state table |
|
| S3 | partial | resolved | explicit | Preview fields group into the same tabs as the form, and a tab with no preview-visible field is not rendered; a status hint with zero nodes renders nothing and takes no gap |
|
||||||
| zero-one-many | S4 row states | ✅ covered | No state: the row is unchanged; one or several: badges in the fixed order and combined text styles |
|
| S3 | overflow | resolved | explicit | Preview values wrap with `overflow-wrap: anywhere`, textarea boxes grow with their text, and the page scrolls under the fixed footer |
|
||||||
| long-text / overflow | S4 first cell | ✅ covered | The first-cell text truncates (`min-w-0 truncate`), badges never shrink (`shrink-0 whitespace-nowrap`); the table keeps its horizontal scroll |
|
| S3 | zero-one-many | resolved | explicit | A multiple relation on preview shows the dash for none and wrapping chips for one or many |
|
||||||
| error | S4 unknown state | 🧪 backstop | An SPA unit test asserts a state outside the fixed set renders no badge and no class, and that each known state renders its text badge |
|
| S3 | long-text | resolved | explicit | Long preview values and callout text wrap and are never truncated; the title truncates as on the form |
|
||||||
| loading / empty / error | S4 row state | ➖ dismissed | States arrive with the list rows; list loading, empty and error states belong to the unchanged list view |
|
| S4 | empty | dismissed | — | Reason: Row states arrive with the list rows; list loading, error and paging states belong to the unchanged list view (Phase 10 contract) |
|
||||||
| empty | S5 permission editor | ✅ covered | No options: the read-only box with `permissioneditor.empty` |
|
| S4 | loading | dismissed | — | Reason: Row states arrive with the list rows; list loading, error and paging states belong to the unchanged list view (Phase 10 contract) |
|
||||||
| populated | S5 permission editor | ✅ covered | Sections by tab; rows with label, comment and a radio segment group or a checkbox |
|
| S4 | error | dismissed | — | Reason: Row states arrive with the list rows; list loading, error and paging states belong to the unchanged list view (Phase 10 contract) |
|
||||||
| error | S5 permission editor | ✅ covered | A 422 renders on the `FormField` error line and the container border becomes `danger` |
|
| S4 | error | resolved | backstop | `{ statement: "A row state outside the fixed set renders no badge and no class, and each known state renders its text badge", verification: backstop }` |
|
||||||
| partial | S5 locked options | ✅ covered | A locked row shows its stored value, a disabled control, the lock icon and the `permissioneditor.locked` text; other rows stay editable |
|
| S4 | populated | resolved | explicit | Each row state renders a text badge in the first cell plus the row text style from the state table; the row background is never changed |
|
||||||
| overflow | S5 many permissions | ✅ covered | No inner scroll; the page scrolls; section headers are not sticky |
|
| S4 | partial | resolved | explicit | A row may carry any subset of the states; each renders independently and their text styles combine |
|
||||||
| long-text | S5 labels and comments | ✅ covered | Label and comment wrap in the left column (`min-w-0`); the control never shrinks; below 640px the control moves under the label |
|
| S4 | overflow | resolved | explicit | In a first cell with states the text truncates (`min-w-0 truncate`) and badges never shrink; the table keeps its horizontal scroll |
|
||||||
| zero-one-many | S5 sections | ✅ covered | One tab: one section with its header; permissions without a tab form a last "Other" section |
|
| S4 | zero-one-many | resolved | explicit | A row with no state is unchanged; one or several states render badges in the fixed order deleted, negative, disabled |
|
||||||
| loading | S5 permission editor | ➖ dismissed | Options arrive with the form schema; there is no separate fetch to show |
|
| S4 | long-text | resolved | explicit | Row state badges are `whitespace-nowrap` and never struck through; a long first-cell value truncates before the badges |
|
||||||
| error | S5 keyboard and value | 🧪 backstop | An SPA unit test asserts radio mode emits `1` / `-1` and omits inherit, checkbox mode emits `1` and omits unchecked, a locked row cannot change, and codes outside the options are never sent |
|
| S5 | empty | resolved | explicit | A permission editor with no options renders the read-only box with `permissioneditor.empty` |
|
||||||
| partial | S6 locked chips | ✅ covered | Locked chips have no remove button and are skipped by Backspace; unlocked chips work as before; the note shows under the field |
|
| S5 | loading | dismissed | — | Reason: Options arrive with the form schema; there is no separate fetch to show |
|
||||||
| error | S6 forbidden save | ✅ covered | Banner with the server message or `form.forbidden`; values kept; fields named in `details` are marked and focused |
|
| S5 | error | resolved | explicit | A 422 on a permission editor renders on the `FormField` error line and the container border becomes `danger` |
|
||||||
| error | S6 locked option behaviour | 🧪 backstop | An SPA unit test asserts a locked option cannot be chosen by click, Enter or arrow keys, and a locked chip cannot be removed by click or Backspace |
|
| S5 | error | resolved | backstop | `{ statement: "Radio mode emits `1` / `-1` and omits inherit, checkbox mode emits `1` and omits unchecked, a locked row cannot change, and codes outside the options are never sent", verification: backstop }` |
|
||||||
| empty / error / partial | S7 password | ✅ covered | Always empty on load; empty on update is not sent; a 422 shows on the field; the field is cleared after a successful save |
|
| S5 | populated | resolved | explicit | Permissions render as sections grouped by tab in one list (no inner tablist; confirmed by the user as the meaning of D-16 "tabbed"), each row with label, comment and a three-segment radio group or a checkbox |
|
||||||
| long-text | S7 password | ✅ covered | The input scrolls horizontally like any text input; `pr-12` keeps text clear of the toggle |
|
| S5 | partial | resolved | explicit | A locked permission row shows its stored value, a disabled control, the lock icon and the `permissioneditor.locked` text while other rows stay editable |
|
||||||
| empty | Plugin lists | ✅ covered | Each list sets its own empty copy (`users.list_empty`, `groups.list_empty`, `organisation.list_empty`); the empty-search state is the existing one |
|
| S5 | overflow | resolved | explicit | A permission editor with many permissions has no inner scroll; the page scrolls and section headers are not sticky |
|
||||||
| zero-one-many | Users hint | ✅ covered | At most one callout by precedence; none when no state applies |
|
| S5 | zero-one-many | resolved | explicit | One tab gives one section with its header; permissions without a tab form a last section labelled `permissioneditor.other` |
|
||||||
|
| S5 | long-text | resolved | explicit | Permission labels and comments wrap in the left column, the control never shrinks, and below 640px the control moves under the label |
|
||||||
|
| S6a | empty | dismissed | — | Reason: Locking adds no empty state; an empty option list is the unchanged RelationField contract (Phase 12.2) |
|
||||||
|
| S6a | loading | dismissed | — | Reason: Locking adds no loading state; option loading is the unchanged RelationField contract (Phase 12.2) |
|
||||||
|
| S6a | error | resolved | backstop | `{ statement: "A locked option cannot be chosen by click, Enter or arrow keys, and a locked chip cannot be removed by click or Backspace", verification: backstop }` |
|
||||||
|
| S6a | partial | resolved | explicit | With some options locked, locked chips have no remove button and are skipped by Backspace, unlocked chips work as before, and the locked note shows under the field |
|
||||||
|
| S6a | long-text | dismissed | — | Reason: Chip and option text overflow is the unchanged RelationField contract (Phase 12.2); the locked note is a fixed framework string |
|
||||||
|
| S6b | empty | resolved | explicit | A 403 on save with no server message shows the banner with `form.forbidden` |
|
||||||
|
| S6b | loading | dismissed | — | Reason: The banner is the result of a save; form loading and save-busy states belong to the unchanged FormView (Phase 10 contract) |
|
||||||
|
| S6b | error | resolved | explicit | A 403 on create or update saves nothing, keeps every entered value and the dirty state, and shows a persistent `role="alert"` banner (not a toast) that clears on the next save attempt |
|
||||||
|
| S6b | partial | resolved | explicit | When the 403 `error.details` names fields, each is marked through its error line and the first is focused; without details only the banner shows |
|
||||||
|
| S6b | overflow | resolved | explicit | A long forbidden message wraps inside the banner and is never truncated |
|
||||||
|
| S6b | long-text | resolved | explicit | A long forbidden message wraps inside the banner and is never truncated |
|
||||||
|
| S7a | empty | resolved | explicit | A password field is always empty on load, and an empty password on update is not sent |
|
||||||
|
| S7a | loading | dismissed | — | Reason: The server never sends a password value, so there is nothing to load |
|
||||||
|
| S7a | error | resolved | explicit | A password 422, including a confirmation mismatch, renders on the field's error line; the SPA does not compare the two fields |
|
||||||
|
| S7a | partial | resolved | explicit | With only one of password and confirmation filled the save is sent as entered and the server's 422 marks the field |
|
||||||
|
| S7a | long-text | resolved | explicit | A long password scrolls inside the input and `pr-12` keeps it clear of the show/hide toggle; every password field is cleared and hidden again after a successful save |
|
||||||
|
| S7b | empty | resolved | explicit | With an empty preset source the target stays empty |
|
||||||
|
| S7b | loading | dismissed | — | Reason: Preset is client-side only; it makes no request |
|
||||||
|
| S7b | error | dismissed | — | Reason: Preset produces no error of its own; an invalid target value is the server's 422 on that field, as for any text field |
|
||||||
|
| S7b | partial | resolved | explicit | A preset target follows its source until the first manual edit, then stops for the session; on update nothing is filled |
|
||||||
|
| S7b | long-text | resolved | explicit | A long preset source is slugged in full with no length cut in the SPA; the server's validation decides |
|
||||||
|
| P1 | empty | resolved | explicit | The Users list with no rows shows `users.list_empty`; the empty-search state is the existing one |
|
||||||
|
| P1 | loading | dismissed | — | Reason: YAML-driven screen; list loading, error and paging states belong to the unchanged list view (Phase 10 contract) |
|
||||||
|
| P1 | error | dismissed | — | Reason: YAML-driven screen; list loading, error and paging states belong to the unchanged list view (Phase 10 contract) |
|
||||||
|
| P1 | populated | resolved | explicit | The Users list shows Name, Email, Registered and Last seen, with row-state badges Deactivated, Banned and Not activated, and rows open the preview |
|
||||||
|
| P1 | partial | dismissed | — | Reason: A missing cell value (for example a user never seen) uses the unchanged list empty-value rule |
|
||||||
|
| P1 | overflow | dismissed | — | Reason: Column overflow and pagination are the unchanged list view (Phase 10 contract) |
|
||||||
|
| P1 | zero-one-many | resolved | explicit | Users bulk confirms and success toasts address the selected users; counts come from the S1 `:count` rule |
|
||||||
|
| P1 | long-text | dismissed | — | Reason: Cell truncation is the unchanged list view; the first cell with badges is covered by S4 |
|
||||||
|
| P2 | empty | resolved | explicit | The Users create form opens with `send_invite` on and every other field empty; a user without an avatar shows the Phase 12.2 `fileupload` empty state |
|
||||||
|
| P2 | loading | dismissed | — | Reason: YAML-driven screen; form loading and save-busy states belong to the unchanged FormView (Phase 10 contract) |
|
||||||
|
| P2 | error | resolved | explicit | On the Users form a validation failure marks the field (422) and a refused privileged-group change shows the forbidden banner with `users.privileged_group_forbidden` and marks `groups` |
|
||||||
|
| P2 | populated | dismissed | — | Reason: A present avatar is the unchanged Phase 12.2 `fileupload` image mode |
|
||||||
|
| P2 | partial | resolved | explicit | On the Users form `send_invite` shows on create only and the Permissions tab on update only |
|
||||||
|
| P2 | long-text | dismissed | — | Reason: Text inputs are the unchanged form field contract |
|
||||||
|
| P3 | loading | resolved | explicit | The Users preview loads as S3: skeleton hint on first fetch, previous hint kept on refetch |
|
||||||
|
| P3 | error | resolved | explicit | The Users preview fails as S3, and its record actions fail as S2 |
|
||||||
|
| P3 | overflow | resolved | explicit | The Users preview shows at most one status callout, by precedence banned, deactivated, not activated, suspended; action buttons wrap in the footer |
|
||||||
|
| P3 | long-text | resolved | explicit | Status callout text wraps and is never truncated |
|
||||||
|
| P4 | empty | resolved | explicit | The User Groups list with no rows shows `groups.list_empty` |
|
||||||
|
| P4 | loading | dismissed | — | Reason: YAML-driven screen; list loading, error and paging states belong to the unchanged list view (Phase 10 contract) |
|
||||||
|
| P4 | error | resolved | explicit | A refused privileged-group create, re-code or delete shows `groups.privileged_forbidden`: a banner on save and a toast on delete |
|
||||||
|
| P4 | populated | resolved | explicit | The User Groups list shows Name, Code, Created and Users, with no checkboxes, bulk actions or preview; rows open the update form |
|
||||||
|
| P4 | partial | dismissed | — | Reason: A missing cell value uses the unchanged list empty-value rule |
|
||||||
|
| P4 | overflow | dismissed | — | Reason: Column overflow and pagination are the unchanged list view (Phase 10 contract) |
|
||||||
|
| P4 | zero-one-many | resolved | explicit | The User Groups `Users` column shows 0 for a group with no members, not the empty-value dash |
|
||||||
|
| P4 | long-text | dismissed | — | Reason: Cell truncation and text inputs are the unchanged list and form contracts |
|
||||||
|
| P5 | empty | resolved | explicit | The Organisations list with no rows shows `organisation.list_empty`, and an organisation with no members shows `organisation.members_empty` |
|
||||||
|
| P5 | loading | dismissed | — | Reason: YAML-driven screen; list loading, error and paging states belong to the unchanged list view (Phase 10 contract); the relation manager is the unchanged Phase 12.2 contract |
|
||||||
|
| P5 | error | dismissed | — | Reason: List, form and relation manager error states are the unchanged Phase 10 and 12.2 contracts; this screen adds no forbidden case |
|
||||||
|
| P5 | populated | resolved | explicit | The Organisations list shows Name, Slug and Created; rows open the update form, which has no preview |
|
||||||
|
| P5 | partial | resolved | explicit | The Organisations Members tab shows on update only |
|
||||||
|
| P5 | overflow | dismissed | — | Reason: Column overflow, pagination and the members table are the unchanged list and Phase 12.2 relation manager contracts |
|
||||||
|
| P5 | zero-one-many | resolved | explicit | The members unlink confirm uses `:count` and states that the users are not deleted |
|
||||||
|
| P5 | long-text | dismissed | — | Reason: Cell truncation and text inputs are the unchanged list and form contracts |
|
||||||
|
| P6 | loading | dismissed | — | Reason: The three items join the existing shell navigation; its states are the unchanged Phase 10 contract |
|
||||||
|
| P6 | error | dismissed | — | Reason: The three items join the existing shell navigation; its states are the unchanged Phase 10 contract |
|
||||||
|
| P6 | overflow | dismissed | — | Reason: The three items join the existing shell navigation; its overflow is the unchanged Phase 10 contract |
|
||||||
|
| P6 | long-text | dismissed | — | Reason: The labels are short fixed strings; shell navigation text handling is unchanged |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -557,12 +618,14 @@ Applicable state considerations resolved: 37 covered, 5 backstop, 2 dismissed, 0
|
|||||||
|
|
||||||
## Checker Sign-Off
|
## Checker Sign-Off
|
||||||
|
|
||||||
- [ ] Dimension 1 Copywriting: PASS
|
- [x] Dimension 1 Copywriting: PASS
|
||||||
- [ ] Dimension 2 Visuals: PASS
|
- [x] Dimension 2 Visuals: PASS
|
||||||
- [ ] Dimension 3 Color: PASS
|
- [x] Dimension 3 Color: PASS
|
||||||
- [ ] Dimension 4 Typography: PASS
|
- [x] Dimension 4 Typography: FLAG (non-blocking): new roles use 12 / 13 / 14px, so hierarchy rests on weight and colour; the preview screen also renders the inherited 24/700 page title and the 20/700 confirm title
|
||||||
- [ ] Dimension 5 Spacing: PASS
|
- [x] Dimension 5 Spacing: FLAG (non-blocking): off-grid values are copied into new code (`.summer-callout` `14px 18px` must stay in step with `FormErrorBanner`); `px-2.5`, `right-1.5`, `p-1.5` / `gap-1.5`, `gap-3` and `p-7` are used but not listed under exceptions
|
||||||
- [ ] Dimension 6 Registry Safety: PASS
|
- [x] Dimension 6 Registry Safety: PASS
|
||||||
- [ ] Dimension 7 Inventory Provenance: PASS
|
- [x] Dimension 7 Inventory Provenance: PASS
|
||||||
|
|
||||||
**Approval:** pending
|
Checker notes for the planner: write out `users.trashed_hint_desc`; consider a next step in `form.forbidden` / `list.action_forbidden`; plugin bulk success messages are fixed strings and can overstate when rows are skipped; state the user avatar's 260×260 (D-20); S6 and S7 carry locked decisions D-07 and D-19, so their seams (G4, G1/G2) are not freely droppable.
|
||||||
|
|
||||||
|
**Approval:** approved 2026-10-04 (gsd-ui-checker; D-16 "tabbed" as grouped sections confirmed by the user)
|
||||||
|
|||||||
Reference in New Issue
Block a user