Implements Spec 077 refinements: workspace Global Mode and navigation/context-bar redundancy cleanup.
Summary
- Global Mode: `/admin/workspaces` is workspace-optional (lists only member workspaces); explicit allowlist in `EnsureWorkspaceSelected`.
- Navigation cleanup: workspace switching is topbar-only; no sidebar “Switch workspace”; removes redundant “Manage workspaces” entry from context-bar.
- Context bar: when no workspace selected, tenant picker is disabled with guidance; on tenant-scoped routes `/admin/t/{tenant}/…` the tenant indicator is read-only (Filament tenant menu remains primary).
- Authorization: workspace creation is policy-driven (`WorkspacePolicy::create()`), enforced in `ChooseWorkspace` via Gate.
Safety / Compliance
- Livewire v4.0+ compliant (Filament v5).
- Panel provider registration remains in `bootstrap/providers.php` (no changes required).
- Global search: no new globally searchable resources added; no behavior changes introduced.
- Destructive actions: none added/changed.
- Assets: no new assets registered; deploy process unchanged (if assets are registered elsewhere, ensure `php artisan filament:assets` runs in deploy as usual).
Tests
- `vendor/bin/sail bin pint --dirty`
- `vendor/bin/sail artisan test --compact tests/Feature/Workspaces tests/Feature/Monitoring tests/Feature/OpsUx tests/Feature/Filament/WorkspaceContextTopbarAndTenantSelectionTest.php`
Spec artifacts
- `specs/077-workspace-nav-monitoring-hub/{spec,plan,tasks}.md`
- `specs/077-workspace-nav-monitoring-hub/contracts/routes.md`
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #94
9.8 KiB
Implementation Plan: Workspace-first Navigation & Monitoring Hub
Branch: 077-workspace-nav-monitoring-hub | Date: 2026-02-06 | Spec: specs/077-workspace-nav-monitoring-hub/spec.md
Input: Feature specification from specs/077-workspace-nav-monitoring-hub/spec.md
Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.
Summary
Resolve workspace navigation ambiguity and formalize a workspace-first context model:
- Unambiguous labels: Switch workspace (
/admin/choose-workspace) vs Manage workspaces (/admin/workspaces). - Monitoring → Operations remains canonical and tenantless (
/admin/operations,/admin/operations/{run}). - Tenant context influences Operations only via server-side default filter state (removable), never via routing.
- Strict non-leaking security semantics:
- Non-member workspace scope → 404 (deny-as-not-found)
- Workspace member missing capability (protected actions/screens) → 403
- Accessing a workspace record outside membership → 404 (deny-as-not-found)
Supporting artifacts:
Technical Context
Language/Version: PHP 8.4.x
Primary Dependencies: Laravel 12, Filament v5, Livewire v4
Storage: PostgreSQL (Sail)
Testing: Pest v4
Target Platform: Web (Filament admin panels)
Project Type: Laravel monolith
Performance Goals: Operations pages remain DB-only at render; list/detail stay fast on large run tables (pagination + indexed filters)
Constraints: Filament-native patterns only; canonical URLs must not depend on tenant context; strict 404/403 non-leakage semantics
Scale/Scope: Multi-workspace MSP use; many tenants and many operation runs
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Inventory-first: N/A (no inventory semantics changes)
- Read/write separation: PASS (no write operations introduced)
- Graph contract path: N/A (no Graph calls)
- Deterministic capabilities: PASS (capability gating uses existing resolver/registry patterns)
- RBAC-UX: PASS (explicit 404 vs 403 rules)
- RBAC-UX destructive confirmation: N/A (no destructive actions introduced)
- RBAC-UX global search: N/A (no new searchable resources; no changes to global search)
- Tenant isolation: PASS (workspace membership is isolation boundary; tenant context auto-cleared when invalid)
- Run observability: N/A (no new operations/jobs)
- Automation: N/A
- Data minimization: N/A
- Badge semantics (BADGE-001): N/A
Project Structure
Documentation (this feature)
specs/077-workspace-nav-monitoring-hub/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── routes.md
└── checklists/
└── requirements.md
Source Code (repository root)
app/
├── Filament/
│ ├── Pages/
│ │ └── ChooseWorkspace.php
│ └── Resources/
│ ├── OperationRunResource.php
│ └── OperationRunResource/
│ └── Pages/
│ └── ListOperationRuns.php
├── Http/
│ └── Middleware/
│ └── EnsureWorkspaceSelected.php
├── Providers/
│ └── Filament/
│ └── AdminPanelProvider.php
└── Support/
└── Middleware/
└── EnsureFilamentTenantSelected.php
resources/
└── views/
└── filament/
└── partials/
└── workspace-switcher.blade.php
routes/
└── web.php
tests/
└── Feature/
└── (new tests for navigation labels + 404/403 + operations default filter)
Structure Decision: Laravel monolith using Filament resources/pages and Laravel middleware.
Complexity Tracking
No constitution violations.
Phase 0 — Outline & Research (complete)
All unknowns/decisions have been resolved and recorded:
- Repo reality + ambiguity sources + decisions D1–D4: research.md
- No remaining NEEDS CLARIFICATION items in the spec.
Phase 1 — Design & Contracts (complete)
- Data model: no new tables/columns required; behavior is implemented via middleware + Filament config: data-model.md
- Route/security contracts: contracts/routes.md
- Manual validation steps + suggested test filters: quickstart.md
Phase 2 — Implementation Plan (ready for tasks)
Step 1 — Navigation labels: “one label, one meaning”
- Update admin navigation to include:
- Switch workspace (topbar context switcher) →
/admin/choose-workspace - Manage workspaces (sidebar Settings) →
/admin/workspaces
- Switch workspace (topbar context switcher) →
- Remove/replace any navigation items labeled only “Workspaces”.
Implementation targets:
- Update app/Support/Middleware/EnsureFilamentTenantSelected.php navigation builder:
- Change the label from
WorkspacestoSwitch workspacefor the choose-workspace link. - Ensure this fallback navigation does not accidentally imply CRUD management.
- Change the label from
- Update app/Providers/Filament/AdminPanelProvider.php nav item label for workspace CRUD to
Manage workspaces. - Update resources/views/filament/partials/workspace-switcher.blade.php text/links to consistently say “Switch workspace”.
- Add reserved Monitoring navigation surfaces for Alerts and Audit Log as placeholder pages (non-functional “coming soon”) to satisfy FR-011.
Step 2 — Enforce workspace-scoped RBAC semantics for /admin/workspaces
/admin/workspacesstays tenantless and is Global Mode (workspace-optional).- Enforce strict non-leakage semantics:
- Non-member attempting to access a workspace record → 404 (deny-as-not-found)
- Member missing required capability for protected actions/screens → 403
Implementation targets:
- Scope the Workspaces query (index) to only workspaces the user is a member of.
- Ensure
WorkspacePolicyreturns 404 semantics for non-members (record access). - Workspace creation is self-serve (policy-driven). Gate edit/membership-management behind canonical workspace capabilities (no raw strings).
- Hide “Manage workspaces” navigation unless the user can manage something workspace-admin related (capability-based).
Step 3 — Workspace selection redirect + return-to-intended
Requirement: visiting any workspace-scoped page without a selected workspace MUST redirect to /admin/choose-workspace and then return to the originally requested URL.
Implementation targets:
- Update app/Http/Middleware/EnsureWorkspaceSelected.php:
- When redirecting to
/admin/choose-workspace, store the intended URL (path + query) in session. - Preserve the existing exemptions for auth routes and for
/admin/operations/{run}and Livewire update referers.
- When redirecting to
- Update both workspace-selection entrypoints to honor intended URLs:
- app/Filament/Pages/ChooseWorkspace.php
- app/Http/Controllers/SwitchWorkspaceController.php
- After setting the workspace, redirect to the stored intended URL (if present and safe), otherwise keep the existing behavior (onboarding / choose-tenant / tenant dashboard).
Step 4 — Auto-clear invalid tenant context on workspace change
Requirement: if tenant context is active but does not belong to the current workspace, auto-clear tenant context and continue on tenantless workspace pages.
Implementation targets:
- In app/Support/Middleware/EnsureFilamentTenantSelected.php (or a dedicated middleware used for tenantless pages):
- Detect a persisted Filament tenant that does not match
WorkspaceContext::currentWorkspaceId(). - Clear the persisted Filament tenant context (confirm the correct Filament v5 mechanism during implementation).
- Detect a persisted Filament tenant that does not match
Step 5 — Operations: move tenant scoping from query to removable default filter
Requirement: /admin/operations stays canonical; if tenant context is active, default to that tenant using server-side default filter state with a visible removable chip.
Implementation targets:
- Update app/Filament/Resources/OperationRunResource.php:
- Remove tenant-context filtering from
getEloquentQuery().
- Remove tenant-context filtering from
- Update app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php:
- Add a tenant filter (select) over available tenants in the current workspace.
- Default the filter state from the current tenant context when valid.
- Ensure the filter chip is visible and can be cleared to view workspace-wide operations.
Step 6 — Tests (Pest) + formatting
Add/adjust tests to cover the strict semantics:
- Navigation labels: “Switch workspace” vs “Manage workspaces” (no ambiguous “Workspaces”).
/admin/workspaces:- non-member record access → 404
- member missing capability for a protected action/screen → 403
- EnsureWorkspaceSelected:
- visiting
/admin/operationswithout workspace → redirects to choose-workspace - after selecting workspace → returns to intended URL
- visiting
- Operations default filter:
- with tenant context active → tenant filter default set
- clearing filter → shows workspace-wide results
Tooling:
- Run
./vendor/bin/sail bin pint --dirty. - Run focused tests via
./vendor/bin/sail artisan test --compact --filter=....