TenantAtlas/specs/106-required-permissions-sidebar-context/data-model.md
ahmido 33a2b1a242 feat(106): Required Permissions sidebar stays on workspace nav (#129)
## Summary

Fixes the sidebar context bug where navigating to the **Required Permissions** page (`/admin/tenants/{id}/required-permissions`) would switch the sidebar from workspace navigation to tenant-scoped navigation, confusing users.

## Problem

The `EnsureFilamentTenantSelected` middleware detected a tenant ID in the URL and called `setTenant()`, which switched the entire sidebar to tenant-scoped navigation. The Required Permissions page is logically a **workspace-level** page that happens to reference a tenant — it should keep showing workspace nav.

## Changes

### Middleware (`EnsureFilamentTenantSelected.php`)
- **`isWorkspaceScopedPageWithTenant()`** — new private helper that detects workspace-scoped pages containing a tenant parameter via regex
- **Livewire referer bypass** — checks if a Livewire request originates from a workspace-scoped page and preserves workspace nav
- **`setTenant()` bypass** — skips tenant activation and `rememberLastTenantId()` for workspace-scoped pages

### Tests
- **`RequiredPermissionsSidebarTest.php`** (NEW) — 7 tests covering:
  - Workspace nav visible on required-permissions page
  - Tenant nav absent on required-permissions page
  - Direct URL access preserves workspace nav
  - 404 for non-member tenants
  - 404 for tenants without entitlement
  - Tenant pages still show tenant sidebar (regression guard)
  - Scoped tenant resolves correctly on tenant pages

### Pre-existing test fixes
- **`RequiredPermissionsEmptyStateTest`** — fixed URL assertion (dynamic `TenantResource::getUrl()` instead of hardcoded `/admin/onboarding`)
- **`RequiredPermissionsLinksTest`** — fixed URL assertion + multiline HTML `data-testid` assertion
- **`RequiredPermissionsFiltersTest`** — fixed `entra_permissions` config leak from branch 105

## Test Results

| Suite | Result |
|-------|--------|
| RequiredPermissions (26 tests) | **26 pass** (73 assertions) |
| Full regression (1571 tests) | **1562 pass**, 2 fail (pre-existing OpsUx), 7 skipped |

The 2 failures are pre-existing in `OpsUx/OperationCatalogCoverageTest` and `OpsUx/OperationSummaryKeysSpecTest` — unrelated to this feature.

## Spec Artifacts

- `specs/106-required-permissions-sidebar-context/plan.md`
- `specs/106-required-permissions-sidebar-context/tasks.md`
- `specs/106-required-permissions-sidebar-context/research.md`
- `specs/106-required-permissions-sidebar-context/data-model.md`
- `specs/106-required-permissions-sidebar-context/quickstart.md`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #129
2026-02-22 02:42:44 +00:00

1.9 KiB

Data Model: 106 — Required Permissions Sidebar Context Fix

Date: 2026-02-22 | Branch: 106-required-permissions-sidebar-context

Summary

No data model changes. This feature modifies middleware control flow only.

Entities Affected

None. No database tables, models, or relationships are created or modified.

State Transitions

Component Before After
EnsureFilamentTenantSelected middleware Always calls Filament::setTenant() when {tenant} route param present Checks workspace-scoped page allowlist first; skips setTenant() for matched pages
Filament::getTenant() on Required Permissions page Returns resolved Tenant instance (triggers tenant sidebar) Returns null (triggers workspace sidebar)
configureNavigationForRequest() on Required Permissions page Renders tenant-scoped sidebar Renders workspace-scoped sidebar
rememberLastTenantId() on Required Permissions page Called (updates session) Skipped (no session side-effect)

Middleware Decision Flow (After Fix)

Request arrives
  ├── /livewire/update?
  │   └── Check referer against:
  │       ├── /admin/operations/{run} (existing)
  │       └── /admin/tenants/{tenant}/required-permissions (NEW)
  │           └── Match → workspace nav, return
  │
  ├── /admin/operations/{run} → workspace nav (existing)
  ├── /admin/operations → workspace nav (existing)
  │
  ├── Route has {tenant} param?
  │   ├── Authorization checks (all 8 — unchanged)
  │   ├── Is workspace-scoped page? (NEW check)
  │   │   ├── YES → configureNavigationForRequest() WITHOUT setTenant()
  │   │   └── NO  → Filament::setTenant() + rememberLastTenantId() + configureNavigation (existing)
  │   └── return next
  │
  └── ... existing flow continues