TenantAtlas/docs/ui/action-surface-contract.md
ahmido 37c6d0622c feat: implement spec 169 action surface contract v1.1 (#200)
## Summary
- implement the Action Surface Contract v1.1 runtime changes for Spec 169
- add the new explicit ActionSurfaceType contract, validator/discovery updates, and enrolled surface declarations
- update Filament action-surface documentation, focused guard tests, and spec artifacts for the completed feature

## Included
- clickable-row vs explicit-inspect enforcement across monitoring, reporting, CRUD, and system reference surfaces
- helper-first, workflow-next, destructive-last overflow ordering checks
- system panel list discovery in the primary action-surface validator
- Spec 169 artifacts: spec, plan, tasks, research, data model, quickstart, and logical contract

## Verification
- focused Pest verification pack completed for:
  - tests/Feature/Guards/ActionSurfaceValidatorTest.php
  - tests/Feature/Guards/ActionSurfaceContractTest.php
  - tests/Feature/Rbac/TenantActionSurfaceConsistencyTest.php
- integrated browser smoke test completed for admin-side reference surfaces:
  - /admin/operations
  - /admin/audit-log
  - /admin/finding-exceptions/queue
  - /admin/reviews
  - /admin/tenants

## Notes
- system panel browser smoke coverage could not be exercised in the same session because /system routes require platform authentication in the integrated browser
- Livewire target remains v4-compliant and no provider registration or asset strategy changes are introduced by this PR

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #200
2026-03-30 09:21:39 +00:00

79 lines
3.8 KiB
Markdown

# Action surface contract
This project enforces a small “action surface contract” for Filament Resources / Pages / RelationManagers to keep table UIs consistent, quiet, and safe.
## Inspect affordance (required)
Any list-style surface that exposes records must provide an **inspect affordance** so an admin can open a record.
### Surface-type decision tree
The inspect model is driven by the declaration `surfaceType`:
- **CRUD / List-first Resource**
Use one-click open by default, normally `recordUrl()`. `PrimaryLinkColumn` is allowed only as an explicit exception with a concrete reason.
- **Read-only Registry / Report**
Use one-click open by default, normally `recordUrl()`. This includes scan-first reporting surfaces such as Monitoring Operations, Review Register, Evidence Overview, and read-only registry resources.
- **Queue / Review**
Use explicit inspect (`Inspect` row action or equivalent same-page selected detail). Do not make the full row clickable.
- **History / Audit**
Use explicit inspect (`Inspect` row action or equivalent same-page selected detail). Do not make the full row clickable.
- **Config-lite**
Edit-as-inspect is allowed, but it still uses one obvious open path and must not add a competing `View` action.
### Accepted implementations
- **Clickable rows** (preferred): set `recordUrl()` for the table.
- **Inspect action**: a row action used only on queue / review or history / audit surfaces where context must stay on the same page.
- **Primary link column**: a column that is clearly the primary affordance to open the record, with an explicit `PrimaryLinkColumn` reason in the declaration.
### Rule: no lone “View” button
Avoid rendering a table that only has a single inspect-style row action on a clickable-row surface. This creates visual noise and adds an unnecessary Actions column.
Preferred approach:
- Make the row clickable via `recordUrl()` and set `actions([])` so no Actions column is rendered.
### PrimaryLinkColumn exception rule
Use `PrimaryLinkColumn` only when full-row click is the wrong interaction model for that specific surface.
- The declaration must use a clickable surface type (`CrudListFirstResource`, `ReadOnlyRegistryReport`, or `ConfigLite`).
- The declaration must include a non-empty `primaryLinkColumnReason`.
- Queue / review and history / audit surfaces may not use `PrimaryLinkColumn` as a shortcut around explicit inspect.
### Reporting / evidence register rule
Review and evidence registers are governed as **ReadOnlyRegistryReport** surfaces.
- `ReviewRegister` and `EvidenceOverview` keep clickable-row inspection as the primary open path.
- Do not add a duplicate `View review` or equivalent open action beside the row click.
- Safe non-inspect shortcuts may remain when they are clearly secondary.
## More-menu ordering
Governed `ActionGroup` and `BulkActionGroup` menus use one stable order:
- Navigation or inspect helpers first
- Non-destructive workflow or lifecycle actions next
- Destructive actions last
Examples:
- `Policies`: export before sync, sync before ignore/delete
- `Backup schedules`: run/retry before archive or force delete
- `Tenants`: related onboarding and safe navigation shortcuts before sync or verification, with archive/force delete trailing
## Placeholder groups are forbidden
`ActionGroup` and `BulkActionGroup` exist to hold real secondary actions, not to reserve layout space.
- Do not render an empty `More` menu after visibility, record-state, or RBAC filtering removes every effective action.
- On clickable-row surfaces with only one safe shortcut, that shortcut may still live under `More` when it preserves a cleaner scan-first list.
## RBAC / safety
- If the current user cannot inspect a record, `recordUrl()` must return `null` for that record.
- UI visibility is not authorization; always enforce permissions at the policy / resource level.