## Summary - Removes the legacy Tenant CRUD create page (`/admin/tenants/create`) so tenant creation is handled exclusively via the onboarding wizard. - Updates tenant selection flows and pages to prevent Livewire polling/notification-related 404s on workspace-scoped routes. - Aligns empty-state UX with enterprise patterns (avoid duplicate CTAs). ## Key changes - Tenant creation - Removed `CreateTenant` page + route from `TenantResource`. - `TenantResource::canCreate()` now returns `false` (CRUD creation disabled). - Tenants list now surfaces an **Add tenant** action that links to onboarding (`admin.onboarding`). - Onboarding wizard - Removed redundant legacy step-cards from the blade view (Wizard schema is the source of truth). - Disabled topbar on the onboarding page to avoid lazy-loaded notifications. - Choose tenant - Enterprise UI redesign + workspace context. - Uses Livewire `selectTenant()` instead of a form POST. - Disabled topbar and gated BODY_END hook to avoid background polling. - Baseline profiles - Hide header create action when table is empty to avoid duplicate CTAs. ## Tests - `vendor/bin/sail artisan test --compact --filter='Onboarding|ManagedTenantOnboarding'` - `vendor/bin/sail artisan test --compact --filter='ManagedTenantsLivewireUpdate'` - `vendor/bin/sail artisan test --compact --filter='TenantSetup|TenantResourceAuth|TenantAdminAuth|ListTenants'` - `vendor/bin/sail artisan test --compact --filter='BaselineProfile'` - `vendor/bin/sail artisan test --compact --filter='ChooseTenant|TenantMake|TenantScoping|AdminTenantScoped|AdminHomeRedirect|WorkspaceContext'` ## Notes - Filament v5 / Livewire v4 compatible. - No new assets introduced; no deploy pipeline changes required. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #131
6.9 KiB
Implementation Plan: Workspace Chooser v1 (Enterprise) + In-App Switch Entry Point
Branch: 107-workspace-chooser | Date: 2026-02-22 | Spec: spec.md
Input: Feature specification from /specs/107-workspace-chooser/spec.md
Summary
Refactor the workspace resolution flow to provide an enterprise-grade auto-resume, explicit switch/manage separation, enhanced metadata in the chooser, and audit events for all workspace selection transitions. The primary changes are:
- Refactor
EnsureWorkspaceSelectedmiddleware to implement the spec's 7-step auto-resume algorithm with stale-membership detection and flash warnings. - Upgrade the
ChooseWorkspacepage with role badges, tenant counts, "Manage workspaces" link (capability-gated), and cleaned-up empty state (no "Create workspace" header action). - Add audit events for workspace auto-selection and manual selection via new
AuditActionIdenum cases +WorkspaceAuditLoggercalls. - Add "Switch workspace" user menu entry visible only when user has >1 workspace membership.
- Support
?choose=1forced chooser bypass parameter in middleware.
No new tables, no new columns, no Microsoft Graph calls. All changes are DB-only, session-based, and synchronous.
Technical Context
Language/Version: PHP 8.4 / Laravel 12
Primary Dependencies: Filament v5, Livewire v4, Tailwind CSS v4
Storage: PostgreSQL (existing tables: workspaces, workspace_memberships, users, audit_logs)
Testing: Pest v4 (feature tests as Livewire component tests + HTTP tests)
Target Platform: Web (Sail/Docker locally, Dokploy for staging/production)
Project Type: Web application (Laravel monolith)
Performance Goals: Chooser page < 200ms DB time with 50 workspace memberships; no N+1 queries
Constraints: Session-based workspace context (all tabs share); no new tables/columns
Scale/Scope: Single Filament page refactor + 1 middleware refactor + 2 enum values + user menu entry + ~17 tests
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Inventory-first: N/A — this feature does not interact with Inventory. Workspace selection is a session context operation.
- Read/write separation: The only write is updating
users.last_workspace_id(convenience preference) and creating audit log entries. No destructive mutations — no preview/confirmation needed for preference persistence. Audit events fire on every selection. - Graph contract path: N/A — no Microsoft Graph calls in this feature. All data is local (workspaces, memberships, session).
- Deterministic capabilities:
Capabilities::WORKSPACE_MANAGEis referenced via the canonical registry constant. No new capabilities introduced. - RBAC-UX: Feature operates in the
/adminplane only. Non-member workspace selection returns 404 (deny-as-not-found) viaWorkspaceContext::isMember(). "Manage workspaces" link gated byworkspace.managecapability. No cross-plane access introduced. - Workspace isolation: Middleware ensures workspace membership on every
/admin/*request. Stale sessions are cleared and redirected. Non-members get 404. - Destructive actions: No destructive actions in this feature. The re-selection is a non-destructive context switch.
- Global search: No changes to global search behavior.
- Tenant isolation: Not directly affected. After workspace selection, the existing tenant-count branching routes to tenant-scoped flows.
- Run observability: N/A — workspace selection is a synchronous, DB-only, < 2s session operation. No
OperationRunneeded. Selection events are audit-logged. - Automation: N/A — no queued/scheduled operations.
- Data minimization: Audit log stores only
actor_id,workspace_id,method,reason,prev_workspace_id— no secrets/tokens/PII. - Badge semantics (BADGE-001): Role badge in chooser renders the workspace membership role. Simple color-mapped Filament badge (no status-like semantics, just a label). The workspace membership role is a tag/category, not a status — exempt from
BadgeCatalog. Verified: noBadgeDomainexists for workspace roles. - Filament UI Action Surface Contract: ChooseWorkspace is a custom context-selector page, not a CRUD Resource. Spec includes UI Action Matrix with explicit exemption documented. No header actions (v1), "Open" per workspace, empty state with specific title + CTA.
- Filament UI UX-001: This is a context-selector page, not a Create/Edit/View resource page. UX-001 Main/Aside layout does not apply. Exemption documented in spec.
Project Structure
Documentation (this feature)
specs/107-workspace-chooser/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
│ └── routes.md
└── tasks.md # Phase 2 output (created by /speckit.tasks)
Source Code (repository root)
app/
├── Http/
│ ├── Controllers/
│ │ └── SwitchWorkspaceController.php # MODIFY — WorkspaceRedirectResolver + audit (context_bar)
│ └── Middleware/
│ └── EnsureWorkspaceSelected.php # MODIFY — refactor to spec algorithm
├── Filament/
│ └── Pages/
│ └── ChooseWorkspace.php # MODIFY — metadata, remove Create action, audit
├── Providers/
│ └── Filament/
│ └── AdminPanelProvider.php # MODIFY — add user menu item
├── Support/
│ ├── Audit/
│ │ └── AuditActionId.php # MODIFY — add 2 enum cases
│ └── Workspaces/
│ └── WorkspaceRedirectResolver.php # NEW — tenant-count branching helper (R4)
resources/
└── views/
└── filament/
└── pages/
└── choose-workspace.blade.php # MODIFY — metadata cards, empty state, manage link
routes/
└── web.php # MODIFY — WorkspaceRedirectResolver integration
tests/
└── Feature/
└── Workspaces/
├── EnsureWorkspaceSelectedMiddlewareTest.php # NEW
├── ChooseWorkspacePageTest.php # NEW
├── WorkspaceSwitchUserMenuTest.php # NEW
├── WorkspaceRedirectResolverTest.php # NEW
└── WorkspaceAuditTrailTest.php # NEW
Structure Decision: Standard Laravel monolith structure. All changes are in existing directories. No new folders needed.
Complexity Tracking
No Constitution Check violations. No justifications needed.
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| — | — | — |