From 1005c7247fb730b211a818814999dfa763ed2083 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Sun, 8 Mar 2026 01:43:37 +0100 Subject: [PATCH] fix: route workspace switch to chooser --- .github/agents/copilot-instructions.md | 4 +- .../filament/partials/context-bar.blade.php | 2 +- .../checklists/requirements.md | 36 ++++ .../contracts/routes.md | 50 +++++ specs/121-workspace-switch-fix/data-model.md | 85 ++++++++ specs/121-workspace-switch-fix/plan.md | 142 +++++++++++++ specs/121-workspace-switch-fix/quickstart.md | 42 ++++ specs/121-workspace-switch-fix/research.md | 43 ++++ specs/121-workspace-switch-fix/spec.md | 112 +++++++++++ specs/121-workspace-switch-fix/tasks.md | 190 ++++++++++++++++++ .../Monitoring/HeaderContextBarTest.php | 12 +- .../Workspaces/ChooseWorkspacePageTest.php | 6 +- ...seWorkspaceRedirectsToChooseTenantTest.php | 19 ++ .../EnsureWorkspaceSelectedMiddlewareTest.php | 24 +++ .../Workspaces/WorkspaceNavigationHubTest.php | 23 ++- .../WorkspaceSwitchUserMenuTest.php | 11 +- 16 files changed, 791 insertions(+), 10 deletions(-) create mode 100644 specs/121-workspace-switch-fix/checklists/requirements.md create mode 100644 specs/121-workspace-switch-fix/contracts/routes.md create mode 100644 specs/121-workspace-switch-fix/data-model.md create mode 100644 specs/121-workspace-switch-fix/plan.md create mode 100644 specs/121-workspace-switch-fix/quickstart.md create mode 100644 specs/121-workspace-switch-fix/research.md create mode 100644 specs/121-workspace-switch-fix/spec.md create mode 100644 specs/121-workspace-switch-fix/tasks.md diff --git a/.github/agents/copilot-instructions.md b/.github/agents/copilot-instructions.md index 05e89e8..c20dcf5 100644 --- a/.github/agents/copilot-instructions.md +++ b/.github/agents/copilot-instructions.md @@ -42,6 +42,8 @@ ## Active Technologies - PHP 8.4 + Laravel 12, Filament v5, Livewire v4 (116-baseline-drift-engine) - PHP 8.4, Laravel 12, Filament v5, Livewire v4 + Laravel framework, Filament admin panels, Livewire, PostgreSQL JSONB persistence, Laravel Sail (120-secret-redaction-integrity) - PostgreSQL (`policy_versions`, `operation_runs`, `audit_logs`, related evidence tables) (120-secret-redaction-integrity) +- PHP 8.4.15 / Laravel 12 + Filament v5 + Livewire v4.0+ + Tailwind CSS v4 (121-workspace-switch-fix) +- PostgreSQL + session-backed workspace context; no schema changes (121-workspace-switch-fix) - PHP 8.4.15 (feat/005-bulk-operations) @@ -61,8 +63,8 @@ ## Code Style PHP 8.4.15: Follow standard conventions ## Recent Changes +- 121-workspace-switch-fix: Added PHP 8.4.15 / Laravel 12 + Filament v5 + Livewire v4.0+ + Tailwind CSS v4 - 120-secret-redaction-integrity: Added PHP 8.4, Laravel 12, Filament v5, Livewire v4 + Laravel framework, Filament admin panels, Livewire, PostgreSQL JSONB persistence, Laravel Sail - 116-baseline-drift-engine-session-1772451227: Added PHP 8.4 + Laravel 12, Filament v5, Livewire v4 -- 116-baseline-drift-engine: Added PHP 8.4 + Laravel 12, Filament v5, Livewire v4 diff --git a/resources/views/filament/partials/context-bar.blade.php b/resources/views/filament/partials/context-bar.blade.php index 0de77c9..f1a0377 100644 --- a/resources/views/filament/partials/context-bar.blade.php +++ b/resources/views/filament/partials/context-bar.blade.php @@ -85,7 +85,7 @@ Switch workspace diff --git a/specs/121-workspace-switch-fix/checklists/requirements.md b/specs/121-workspace-switch-fix/checklists/requirements.md new file mode 100644 index 0000000..cda145e --- /dev/null +++ b/specs/121-workspace-switch-fix/checklists/requirements.md @@ -0,0 +1,36 @@ +# Specification Quality Checklist: Workspace Switch Semantic Fix + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-03-07 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Validation passed on the first review. +- The specification keeps the focus on user intent, navigation semantics, and separation of context switching from workspace administration. +- No clarification round is required before `/speckit.plan`. diff --git a/specs/121-workspace-switch-fix/contracts/routes.md b/specs/121-workspace-switch-fix/contracts/routes.md new file mode 100644 index 0000000..90559b5 --- /dev/null +++ b/specs/121-workspace-switch-fix/contracts/routes.md @@ -0,0 +1,50 @@ +# Route Contract: Workspace Switch Semantic Fix + +**Feature**: 121-workspace-switch-fix | **Date**: 2026-03-07 + +## Intent-level contract + +### Switch workspace + +- **Surface**: admin context bar +- **Label**: `Switch workspace` +- **Canonical target**: `ChooseWorkspace::getUrl(panel: 'admin').'?choose=1'` +- **Meaning**: enter the explicit workspace chooser flow + +### Manage workspaces + +- **Surface**: existing administrative navigation / chooser management affordance +- **Canonical target**: workspace management resource index +- **Meaning**: create, view, and edit workspace records + +## Before / After + +### Before + +```text +Context bar "Switch workspace" + -> /admin/workspaces + -> lands on workspace management CRUD index +``` + +### After + +```text +Context bar "Switch workspace" + -> /admin/choose-workspace?choose=1 + -> lands on chooser flow +``` + +## Invariants + +1. Workspace chooser remains the canonical manual-switch surface. +2. `?choose=1` remains the explicit forced-chooser signal for intentional switching. +3. Workspace management remains separately reachable and semantically distinct. +4. Choosing a workspace continues to use existing `WorkspaceIntendedUrl` / `WorkspaceRedirectResolver` behavior. +5. No new routes, middleware rules, or authorization rules are introduced. + +## Verification expectations + +- Response rendering for a real admin page containing the context bar must show the chooser target. +- Existing chooser behavior tests remain green. +- Workspace management navigation remains reachable through its existing destination. diff --git a/specs/121-workspace-switch-fix/data-model.md b/specs/121-workspace-switch-fix/data-model.md new file mode 100644 index 0000000..6969cc3 --- /dev/null +++ b/specs/121-workspace-switch-fix/data-model.md @@ -0,0 +1,85 @@ +# Data Model: Workspace Switch Semantic Fix + +**Feature**: 121-workspace-switch-fix | **Date**: 2026-03-07 + +## Overview + +This feature does not add or modify database tables. It corrects the navigation contract between three existing workspace-related concepts: + +1. the topbar `Switch workspace` entry, +2. the canonical workspace chooser, +3. the separate workspace management destination. + +## Existing Entities / Concepts + +### Workspace switch entry (UI affordance) + +| Attribute | Value | +|-----------|-------| +| Surface | Admin context bar / topbar partial | +| Current problem | Routes to workspace management instead of chooser | +| New contract | Routes to the chooser using the existing forced-switch convention | +| Mutation | None | + +### Workspace chooser + +| Attribute | Value | +|-----------|-------| +| Backing page | `App\Filament\Pages\ChooseWorkspace` | +| Route slug | `/admin/choose-workspace` | +| Purpose | Explicit manual workspace selection | +| Existing behavior | After selection, consumes intended URL or resolves redirect via `WorkspaceRedirectResolver` | + +### Workspace management destination + +| Attribute | Value | +|-----------|-------| +| Backing resource | `App\Filament\Resources\Workspaces\WorkspaceResource` | +| Purpose | Workspace CRUD / administrative management | +| Access model | Existing capability-aware workspace management rules | +| Change in this feature | None | + +## Relevant Existing State + +### Session-backed workspace context + +| Key | Type | Notes | +|-----|------|-------| +| `current_workspace_id` | int | Active workspace context used across the admin plane | +| intended workspace URL | string/null | Existing post-selection return path managed by `WorkspaceIntendedUrl` | + +### Existing redirect semantics + +```text +Switch intent + -> ChooseWorkspace (`?choose=1` forces chooser) + -> User selects workspace + -> WorkspaceIntendedUrl::consume() if present + -> else WorkspaceRedirectResolver::resolve() +``` + +## Validation / Rules + +| Rule | Result | +|------|--------| +| Intentional workspace switching must target chooser | Required | +| Intentional switching must not target workspace CRUD | Required | +| Workspace management remains separately reachable | Required | +| Existing membership/capability enforcement remains unchanged | Required | +| Existing post-selection redirect behavior remains unchanged | Required | + +## State Transition + +```text +[User on admin page] + | + | click "Switch workspace" + v +[Chooser page displayed] + | + | select workspace + v +[Existing intended URL or resolved workspace landing] +``` + +No new persistence state is introduced. diff --git a/specs/121-workspace-switch-fix/plan.md b/specs/121-workspace-switch-fix/plan.md new file mode 100644 index 0000000..552547c --- /dev/null +++ b/specs/121-workspace-switch-fix/plan.md @@ -0,0 +1,142 @@ +# Implementation Plan: Workspace Switch Semantic Fix + +**Branch**: `121-workspace-switch-fix` | **Date**: 2026-03-07 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification from `/specs/121-workspace-switch-fix/spec.md` + +## Summary + +Correct the admin context-bar `Switch workspace` affordance so it uses the same canonical chooser flow as the existing user-menu switch action. The implementation will update the topbar partial to target `ChooseWorkspace::getUrl(panel: 'admin').'?choose=1'`, preserving the current chooser redirect behavior (`WorkspaceIntendedUrl` / `WorkspaceRedirectResolver`) and leaving workspace management reachable only through its separate administrative navigation path. Validation stays focused on response-level topbar rendering plus regression coverage that management navigation and chooser semantics remain distinct. + +## Technical Context + +**Language/Version**: PHP 8.4.15 / Laravel 12 +**Primary Dependencies**: Filament v5 + Livewire v4.0+ + Tailwind CSS v4 +**Storage**: PostgreSQL + session-backed workspace context; no schema changes +**Testing**: Pest v4 feature tests (HTTP response assertions and existing workspace flow regression tests) +**Target Platform**: Web admin panel (`/admin`) running in Laravel Sail / container deployment +**Project Type**: Laravel monolith / Filament web application +**Performance Goals**: No additional remote calls or new query paths; topbar render remains effectively unchanged except for link destination +**Constraints**: Preserve existing RBAC and workspace isolation rules; preserve chooser post-selection behavior; avoid hardcoded management URLs for switch intent; no CRUD behavior changes +**Scale/Scope**: One context-bar Blade partial plus targeted workspace/navigation tests; no data migration, no new services, no new routes + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +- Inventory-first: PASS (feature only changes navigation semantics; no inventory or backup semantics affected). +- Read/write separation: PASS (no new writes or mutations introduced; chooser selection behavior remains existing behavior). +- Graph contract path: PASS (no Microsoft Graph calls; all affected surfaces are DB/session-only). +- Deterministic capabilities: PASS (no new capabilities; existing workspace membership and management capability rules remain canonical). +- RBAC-UX: PASS (feature stays in `/admin`; non-member and capability behavior are unchanged because only the destination link changes). +- Workspace isolation: PASS (`?choose=1` intentionally forces the chooser while preserving existing workspace membership enforcement and post-selection resolution). +- Destructive confirmation: PASS / N/A (no destructive actions are added or changed). +- Global search: PASS / N/A (no global search behavior changes). +- Tenant isolation: PASS (chooser and management separation remains explicit; no tenant data exposure changes). +- Run observability: PASS / N/A (no long-running or operational work; no `OperationRun` usage). +- Ops-UX 3-surface feedback: PASS / N/A (no operation lifecycle involved). +- Automation: PASS / N/A (no scheduled or queued work). +- Data minimization: PASS (no new persisted data or logging payloads). +- Badge semantics (BADGE-001): PASS / N/A (no new status-like badges). +- Filament UI Action Surface Contract: PASS with exemption note. The affected surface is a topbar/context-bar navigation link, not a CRUD Resource/RelationManager/Page action surface. +- Filament UI UX-001: PASS with exemption note. The feature preserves the existing chooser page and does not alter Create/Edit/View layouts. + +## Project Structure + +### Documentation (this feature) + +```text +specs/121-workspace-switch-fix/ +├── plan.md +├── research.md +├── data-model.md +├── quickstart.md +├── contracts/ +│ └── routes.md +└── tasks.md +``` + +### Source Code (repository root) + +```text +app/ +├── Filament/ +│ └── Pages/ +│ └── ChooseWorkspace.php # reference-only canonical chooser +├── Http/ +│ └── Middleware/ +│ └── EnsureWorkspaceSelected.php # reference-only forced chooser semantics +├── Providers/ +│ └── Filament/ +│ └── AdminPanelProvider.php # reference-only existing canonical switch pattern +└── Support/ + └── Workspaces/ + ├── WorkspaceIntendedUrl.php # reference-only existing post-selection flow + └── WorkspaceRedirectResolver.php # reference-only existing post-selection flow + +resources/ +└── views/ + └── filament/ + └── partials/ + └── context-bar.blade.php # MODIFY — switch link target + +tests/ +└── Feature/ + ├── Monitoring/ + │ └── HeaderContextBarTest.php # MODIFY — assert chooser target from context bar + └── Workspaces/ + ├── WorkspaceSwitchUserMenuTest.php # reference or extend — existing canonical chooser target pattern + ├── ChooseWorkspacePageTest.php # reference or extend — management remains reachable + └── WorkspaceNavigationHubTest.php # reference-only IA separation guard +``` + +**Structure Decision**: Keep the change inside the existing Laravel/Filament monolith. Implementation is a narrowly scoped view-level routing correction backed by targeted Pest feature tests and existing workspace chooser/navigation helpers. + +## Complexity Tracking + +> No Constitution Check violations. No justifications needed. + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| — | — | — | + +## Phase 0 — Research (output: `research.md`) + +See: [research.md](./research.md) + +Research goals: +- Confirm the canonical workspace-switch destination and whether forced chooser semantics require `?choose=1`. +- Confirm whether chooser post-selection redirect behavior should remain unchanged. +- Confirm the smallest reliable test surface for a topbar link semantic fix plus management-navigation regression coverage. + +## Phase 1 — Design & Contracts (outputs: `data-model.md`, `contracts/`, `quickstart.md`) + +See: +- [data-model.md](./data-model.md) +- [contracts/routes.md](./contracts/routes.md) +- [quickstart.md](./quickstart.md) + +Design focus: +- Treat the context-bar link as a pure navigation contract update, not a behavior rewrite. +- Reuse the existing chooser page and `?choose=1` forced-chooser convention to preserve manual switching semantics. +- Preserve existing management navigation, chooser redirect resolution, and capability boundaries unchanged. +- Validate the semantic split with response-level topbar assertions plus regression checks around management reachability. + +## Phase 2 — Implementation Outline (tasks created in `/speckit.tasks`) + +### UI routing correction +- Replace the context-bar `Switch workspace` link target in `context-bar.blade.php`. +- Use the canonical chooser helper (`ChooseWorkspace::getUrl(panel: 'admin')`) with the forced chooser query parameter. +- Keep the link as navigation-only; no action/mutation flow is added. + +### Regression protection +- Extend the real rendered topbar test to assert the context-bar switch link points at the chooser URL rather than workspace CRUD. +- Reuse or mirror existing workspace chooser tests to prove management navigation remains separate and accessible through its dedicated path. +- Keep existing middleware/chooser tests as the source of truth for post-selection branching. + +### Verification +- Run focused Pest coverage for context bar + workspace navigation semantics. +- Run Pint on dirty files through Sail. + +## Constitution Check (Post-Design) + +Re-check result: PASS. The design keeps workspace switching and workspace management semantically separate, reuses the existing chooser and middleware contract, introduces no new authorization rules or operational flows, and limits the implementation to a navigation target correction plus regression tests. diff --git a/specs/121-workspace-switch-fix/quickstart.md b/specs/121-workspace-switch-fix/quickstart.md new file mode 100644 index 0000000..c8ad0d3 --- /dev/null +++ b/specs/121-workspace-switch-fix/quickstart.md @@ -0,0 +1,42 @@ +# Quickstart: Workspace Switch Semantic Fix + +**Feature**: 121-workspace-switch-fix | **Date**: 2026-03-07 + +## Scope + +A small navigation-semantic correction: +- update the context-bar `Switch workspace` link, +- keep chooser behavior unchanged, +- keep workspace management navigation unchanged, +- add focused regression coverage. + +## Implementation order + +1. Update [resources/views/filament/partials/context-bar.blade.php](../../../resources/views/filament/partials/context-bar.blade.php) so `Switch workspace` targets the chooser helper with `?choose=1`. +2. Extend [tests/Feature/Monitoring/HeaderContextBarTest.php](../../../tests/Feature/Monitoring/HeaderContextBarTest.php) to assert the rendered context-bar link points to the chooser target. +3. Add or extend one workspace regression test to confirm management navigation remains separately reachable. +4. Run focused Sail-based tests. +5. Run Pint on dirty files. + +## Reference files + +- [app/Filament/Pages/ChooseWorkspace.php](../../../app/Filament/Pages/ChooseWorkspace.php) +- [app/Providers/Filament/AdminPanelProvider.php](../../../app/Providers/Filament/AdminPanelProvider.php) +- [app/Http/Middleware/EnsureWorkspaceSelected.php](../../../app/Http/Middleware/EnsureWorkspaceSelected.php) +- [tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php](../../../tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php) +- [tests/Feature/Workspaces/ChooseWorkspacePageTest.php](../../../tests/Feature/Workspaces/ChooseWorkspacePageTest.php) + +## Validation commands + +```bash +vendor/bin/sail artisan test --compact tests/Feature/Monitoring/HeaderContextBarTest.php +vendor/bin/sail artisan test --compact tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php +vendor/bin/sail artisan test --compact tests/Feature/Workspaces/ChooseWorkspacePageTest.php +vendor/bin/sail bin pint --dirty --format agent +``` + +## Expected outcome + +- Context-bar switching always opens the chooser. +- Workspace management remains a separate administrative path. +- Existing chooser branching behavior and middleware semantics remain unchanged. diff --git a/specs/121-workspace-switch-fix/research.md b/specs/121-workspace-switch-fix/research.md new file mode 100644 index 0000000..ce5775e --- /dev/null +++ b/specs/121-workspace-switch-fix/research.md @@ -0,0 +1,43 @@ +# Research: Workspace Switch Semantic Fix + +**Feature**: 121-workspace-switch-fix | **Date**: 2026-03-07 + +## R1: Canonical destination for intentional workspace switching + +**Decision**: Use `ChooseWorkspace::getUrl(panel: 'admin').'?choose=1'` as the context-bar target for `Switch workspace`. + +**Rationale**: The codebase already treats the chooser page as the canonical manual-switch experience. The admin user menu uses this exact destination today, while the context bar still points to the workspace CRUD index. The `?choose=1` parameter is the established convention for bypassing workspace auto-resume and forcing the chooser when the user explicitly intends to switch. + +**Alternatives considered**: +- Link to `route('filament.admin.resources.workspaces.index')`: rejected because that is the management surface (`Manage workspaces`), not the switch flow. +- Link to `/admin/choose-workspace` without `?choose=1`: rejected because the codebase already documents `?choose=1` as the explicit forced-chooser contract for intentional switching. + +## R2: Post-selection redirect behavior after entering the chooser + +**Decision**: Preserve the chooser’s existing redirect behavior after a workspace is selected. + +**Rationale**: `ChooseWorkspace::selectWorkspace()` already resolves post-selection flow through `WorkspaceIntendedUrl::consume()` and `WorkspaceRedirectResolver::resolve()`. That behavior is covered by existing tests and is outside this spec’s scope. The fix is semantic routing into the chooser, not a redesign of what happens after a user selects a workspace. + +**Alternatives considered**: +- Always redirect back to the current page after selection: rejected because it would change established chooser behavior and broaden scope beyond a low-risk semantic correction. +- Always redirect to one fixed landing page: rejected because it would bypass the existing tenant-count branching contract. + +## R3: Separation between switching and management + +**Decision**: Keep workspace management reachable only through its existing dedicated administrative destinations, while the context bar becomes chooser-only. + +**Rationale**: The chooser page already exposes a capability-aware `Manage workspaces` link for eligible roles, and the workspace resource itself is explicitly labeled `Manage workspaces`. This preserves the intended split between operational context switching and workspace CRUD administration. + +**Alternatives considered**: +- Add both switch and management links to the same context-bar action surface: rejected because it would continue the semantic conflation the spec is correcting. +- Hide workspace management entirely from chooser-adjacent flows: rejected because the current dedicated management affordances are valid and already capability-aware. + +## R4: Smallest reliable test strategy + +**Decision**: Extend response-level topbar rendering coverage in `HeaderContextBarTest`, and rely on existing workspace chooser/navigation tests for regression protection around management and forced chooser behavior. + +**Rationale**: The bug lives in a rendered Blade partial in the admin topbar, so the most direct regression test is an HTTP response assertion on a real admin page that includes the context bar. Existing tests already cover chooser routing semantics, forced chooser behavior, chooser management-link visibility, and navigation hub separation. + +**Alternatives considered**: +- Test only the chooser Livewire page: rejected because it would not assert the actual broken context-bar link. +- Add browser-only coverage: rejected because existing response-based feature tests already cover the required semantic contract at lower cost. diff --git a/specs/121-workspace-switch-fix/spec.md b/specs/121-workspace-switch-fix/spec.md new file mode 100644 index 0000000..1e0e1f6 --- /dev/null +++ b/specs/121-workspace-switch-fix/spec.md @@ -0,0 +1,112 @@ +# Feature Specification: Workspace Switch Semantic Fix + +**Feature Branch**: `121-workspace-switch-fix` +**Created**: 2026-03-07 +**Status**: Draft +**Input**: User description: "Separate workspace switching from workspace management in the context bar so the Switch workspace action always routes to the canonical workspace chooser instead of the workspace CRUD index." + +## Spec Scope Fields *(mandatory)* + +- **Scope**: workspace +- **Primary Routes**: authenticated admin context bar workspace switch entry; workspace chooser flow at `/admin/choose-workspace`; workspace management page at `/admin/workspaces` +- **Data Ownership**: workspace-owned context selection only; no new workspace records or workspace management data changes +- **RBAC**: authenticated users continue to switch only among workspaces they already belong to; workspace management remains behind its existing administrative capabilities with no visibility or permission expansion + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Direct workspace switching (Priority: P1) + +As a user with access to multiple workspaces, I can choose “Switch workspace” from the context bar and land on the workspace chooser instead of a management table. + +**Why this priority**: This is the core product expectation and the highest-visibility confusion point for multi-workspace usage. + +**Independent Test**: Can be fully tested by opening the context bar, activating “Switch workspace,” and confirming the chooser experience appears without exposing CRUD-oriented management UI. + +**Acceptance Scenarios**: + +1. **Given** a signed-in user with access to multiple workspaces, **When** the user selects “Switch workspace” from the context bar, **Then** the user is taken directly to the workspace chooser flow. +2. **Given** a signed-in user who opens the chooser from the context bar, **When** the chooser loads, **Then** the user sees the existing workspace selection experience rather than a workspace management list. + +--- + +### User Story 2 - Preserve administrative separation (Priority: P2) + +As an administrator, I can still reach workspace management through its dedicated administrative destination without mixing it into the context-switching action. + +**Why this priority**: The product must preserve the distinction between operational context switching and administrative CRUD management. + +**Independent Test**: Can be fully tested by confirming the context bar action changes destination while the existing workspace management entry remains reachable and unchanged. + +**Acceptance Scenarios**: + +1. **Given** an administrator with workspace management access, **When** the administrator uses the dedicated management navigation path at `/admin/workspaces`, **Then** the administrator still reaches the workspace management experience. +2. **Given** an administrator using the context bar, **When** the administrator selects “Switch workspace,” **Then** the action does not open workspace management. + +--- + +### User Story 3 - Maintain navigational coherence (Priority: P3) + +As a user, I experience consistent navigation after switching intent is corrected, with no loops, broken breadcrumbs, or dead-end routes. + +**Why this priority**: The change is small but high-visibility, so surrounding navigation must remain coherent to avoid replacing one confusion point with another. + +**Independent Test**: Can be fully tested by following the switch path and the management path separately and confirming both destinations remain stable and understandable. + +**Acceptance Scenarios**: + +1. **Given** a user who arrives at the chooser from the context bar, **When** the user navigates within the chooser flow, **Then** page title, breadcrumbs, and back-navigation remain consistent with workspace selection intent. +2. **Given** a user who previously used workspace management, **When** the user accesses it through the existing administrative path, **Then** the management route continues to behave as before. + +### Edge Cases + +- What happens when a user has access to only one workspace but the switch entry is still shown? The action should still resolve to the chooser flow rather than rerouting to management. +- How does the system handle a user whose active workspace becomes unavailable between rendering the context bar and clicking the switch action? The user should still be routed to the chooser, where existing entitlement handling determines the next safe state. +- What happens if a user lacks workspace management capability? The context-switching entry must still work without revealing or implying management access. +- How does the system handle stale bookmarks to workspace management? Existing management links remain valid and separate from switching intent. + +## Requirements *(mandatory)* + +### Constitution Alignment Notes + +- This feature introduces no Microsoft Graph calls, no queued or scheduled work, and no write-oriented operational workflows, so contract registry, audit-run orchestration, and `OperationRun` requirements are unchanged. +- This feature does not introduce new authorization rules. It preserves the current workspace membership and capability model while correcting navigation semantics. +- No status badge semantics change. +- The Filament action surface change is a navigation-only context bar action. The Action Surface Contract is satisfied because the action is non-destructive, capability-aware behavior is preserved, and no mutation path is added. +- UX-001 remains satisfied for the affected surface because workspace switching continues to use a dedicated chooser experience, while workspace management remains a distinct administrative destination. + +### Functional Requirements + +- **FR-001**: The system MUST route the context-bar “Switch workspace” action to the canonical workspace chooser URL (`/admin/choose-workspace?choose=1`). +- **FR-002**: The system MUST preserve the workspace chooser as the primary and explicit experience for changing active workspace context, and MUST NOT route switching intent to workspace CRUD/management surfaces. +- **FR-003**: The system MUST keep workspace management available through its existing dedicated administrative destination at `/admin/workspaces`. +- **FR-004**: The system MUST preserve existing visibility and permission rules for workspace management. +- **FR-005**: The system MUST keep navigation and breadcrumb behavior coherent after the destination change. +- **FR-006**: The system MUST preserve current capability-aware handling for users who can switch workspaces but cannot manage workspaces. + +## UI Action Matrix *(mandatory when Filament is changed)* + +| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions | +|---|---|---|---|---|---|---|---|---|---|---| +| Context bar workspace switcher | Global admin context bar | None | Not applicable | `Switch workspace` (navigation only) | None | None | Not applicable | Not applicable | No | Navigation-only action. Destination changes to the chooser flow. No destructive behavior, no mutation, and no authorization expansion. | +| Workspace management navigation | Existing admin navigation destination | Existing actions unchanged | Existing management affordances unchanged | Existing actions unchanged | Existing actions unchanged | Existing empty-state behavior unchanged | Existing actions unchanged | Existing save/cancel behavior unchanged | Unchanged | Included for separation-of-concerns verification only; this feature does not change management behavior. | + +### Key Entities *(include if feature involves data)* + +- **Active Workspace Context**: The current workspace the user is operating in, which determines the semantic purpose of the switch action. +- **Workspace Chooser Experience**: The dedicated selection flow users rely on to change active workspace context safely and intentionally. +- **Workspace Management Experience**: The separate administrative surface for creating, editing, and reviewing workspaces. + +## Assumptions + +- The existing workspace chooser remains the canonical destination for context switching. +- The existing workspace management destination remains available at `/admin/workspaces` and does not need redesign as part of this feature. +- Current membership and capability checks already distinguish between switching accessible workspaces and managing workspace records. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: In validation, 100% of context-bar “Switch workspace” activations land on the workspace chooser rather than a management table. +- **SC-002**: A multi-workspace user can begin a workspace switch in one click from the context bar without encountering CRUD management controls. +- **SC-003**: Administrative users retain an unchanged path to workspace management during regression testing. +- **SC-004**: Manual QA confirms no broken links, redirect loops, or breadcrumb confusion across the switch and management paths. diff --git a/specs/121-workspace-switch-fix/tasks.md b/specs/121-workspace-switch-fix/tasks.md new file mode 100644 index 0000000..aa5d11a --- /dev/null +++ b/specs/121-workspace-switch-fix/tasks.md @@ -0,0 +1,190 @@ +# Tasks: Workspace Switch Semantic Fix + +**Input**: Design documents from `/specs/121-workspace-switch-fix/` +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/routes.md, quickstart.md + +**Tests**: Tests are REQUIRED for this runtime behavior change. Use Pest feature tests via Sail. +**Operations**: No `OperationRun` work is required for this feature. +**RBAC**: No authorization model changes are planned; tasks must preserve existing workspace membership and capability behavior. +**Filament UI Action Surfaces**: The affected surface is a topbar navigation link; no new CRUD action surface is introduced. +**Filament UI UX-001**: No Create/Edit/View layout changes are required; preserve existing chooser and management screens. + +**Organization**: Tasks are grouped by user story so each story remains independently testable. + +## Phase 1: Setup (Shared Context) + +**Purpose**: Align implementation with the existing chooser contract before touching code. + +- [X] T001 [P] Review the canonical switch-route contract in specs/121-workspace-switch-fix/contracts/routes.md and specs/121-workspace-switch-fix/research.md +- [X] T002 [P] Inspect the current broken target in resources/views/filament/partials/context-bar.blade.php and the existing canonical switch pattern in app/Providers/Filament/AdminPanelProvider.php + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Confirm shared workspace-switch semantics that all stories depend on. + +**⚠️ CRITICAL**: No user story work should begin until this phase is complete. + +- [X] T003 Confirm forced-chooser and post-selection redirect semantics in app/Http/Middleware/EnsureWorkspaceSelected.php, app/Filament/Pages/ChooseWorkspace.php, app/Support/Workspaces/WorkspaceIntendedUrl.php, and app/Support/Workspaces/WorkspaceRedirectResolver.php + +**Checkpoint**: Canonical chooser semantics are confirmed and user-story implementation can begin. + +--- + +## Phase 3: User Story 1 - Direct workspace switching (Priority: P1) 🎯 MVP + +**Goal**: Make the context-bar `Switch workspace` action always open the canonical chooser instead of workspace management. + +**Independent Test**: Open a real admin page with the context bar and verify that `Switch workspace` points to the chooser URL with forced-chooser semantics, not to `/admin/workspaces`. + +### Tests for User Story 1 ⚠️ + +> **NOTE**: Add or update these tests first and ensure they fail before implementation. + +- [X] T004 [P] [US1] Extend tests/Feature/Monitoring/HeaderContextBarTest.php to assert the rendered `Switch workspace` link targets `/admin/choose-workspace?choose=1` and does not target `/admin/workspaces` +- [X] T005 [P] [US1] Keep the canonical chooser target expectation aligned in tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php so topbar and user-menu switching semantics stay consistent + +### Implementation for User Story 1 + +- [X] T006 [US1] Update resources/views/filament/partials/context-bar.blade.php to route `Switch workspace` to `ChooseWorkspace::getUrl(panel: 'admin').'?choose=1'` + +**Checkpoint**: User Story 1 should now be independently functional and verifiable from the rendered admin topbar. + +--- + +## Phase 4: User Story 2 - Preserve administrative separation (Priority: P2) + +**Goal**: Keep workspace management reachable through its dedicated admin path without mixing it into the context-switching action. + +**Independent Test**: Verify the chooser and admin navigation still expose `Manage workspaces` through `/admin/workspaces` while `Switch workspace` remains chooser-only. + +### Tests for User Story 2 ⚠️ + +- [X] T007 [P] [US2] Extend tests/Feature/Workspaces/ChooseWorkspacePageTest.php to confirm authorized users still see `Manage workspaces`, unauthorized roles still do not, and the chooser management affordance continues to resolve to `/admin/workspaces` +- [X] T008 [P] [US2] Extend tests/Feature/Workspaces/WorkspaceNavigationHubTest.php to confirm navigation separation remains `Switch workspace` in the topbar only while `Manage workspaces` remains the dedicated admin navigation path to `/admin/workspaces` + +### Implementation for User Story 2 + +- [X] T009 [US2] If T007 or T008 expose drift, update resources/views/filament/pages/choose-workspace.blade.php and/or app/Filament/Resources/Workspaces/WorkspaceResource.php so every `Manage workspaces` affordance still resolves to `/admin/workspaces` and remains separate from chooser-only switching + +**Checkpoint**: User Story 2 should now prove that switching and management remain semantically distinct. + +--- + +## Phase 5: User Story 3 - Maintain navigational coherence (Priority: P3) + +**Goal**: Preserve existing chooser and redirect behavior so the semantic fix does not introduce loops or breadcrumb/navigation regressions. + +**Independent Test**: Enter the chooser through the context-switch path and confirm existing forced-chooser and post-selection routing behavior remains intact. + +### Tests for User Story 3 ⚠️ + +- [X] T010 [P] [US3] Extend tests/Feature/Workspaces/EnsureWorkspaceSelectedMiddlewareTest.php to keep `?choose=1` forced-chooser behavior covered for intentional workspace switching +- [X] T011 [P] [US3] Extend tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php to confirm chooser post-selection routing remains unchanged after the context-bar fix + +### Implementation for User Story 3 + +- [X] T012 [US3] If T010 or T011 expose a regression, update app/Filament/Pages/ChooseWorkspace.php and/or app/Support/Workspaces/WorkspaceRedirectResolver.php so forced-chooser semantics and post-selection branching remain unchanged + +**Checkpoint**: User Story 3 should confirm the fix changes only entry semantics, not downstream chooser behavior. + +--- + +## Phase 6: Polish & Cross-Cutting Concerns + +**Purpose**: Final verification and formatting for the complete change. + +- [X] T013 Perform manual QA for `/admin/choose-workspace?choose=1` and `/admin/workspaces`, confirming page title, breadcrumbs, back-navigation, and link targets remain coherent across switching and management flows +- [X] T014 Run focused Pest coverage with `vendor/bin/sail artisan test --compact` for tests/Feature/Monitoring/HeaderContextBarTest.php, tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php, tests/Feature/Workspaces/ChooseWorkspacePageTest.php, tests/Feature/Workspaces/WorkspaceNavigationHubTest.php, tests/Feature/Workspaces/EnsureWorkspaceSelectedMiddlewareTest.php, and tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php +- [X] T015 Run `vendor/bin/sail bin pint --dirty --format agent` + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies; can start immediately. +- **Foundational (Phase 2)**: Depends on Setup completion; blocks all user stories. +- **User Story 1 (Phase 3)**: Depends on Foundational completion; this is the MVP. +- **User Story 2 (Phase 4)**: Depends on Foundational completion; can run after or in parallel with User Story 3 once User Story 1 direction is clear. +- **User Story 3 (Phase 5)**: Depends on Foundational completion; can run after or in parallel with User Story 2. +- **Polish (Phase 6)**: Depends on completion of the desired user stories. + +### User Story Dependencies + +- **US1**: No dependency on other stories; delivers the primary fix. +- **US2**: Depends only on the shared chooser/management contract, not on US3. +- **US3**: Depends only on the shared chooser contract, not on US2. + +### Within Each User Story + +- Tests should be updated first and observed failing before implementation. +- Implementation follows only after the story-specific regression tests are in place. +- Each story should remain independently testable when complete. + +### Parallel Opportunities + +- T001 and T002 can run in parallel. +- T004 and T005 can run in parallel. +- T007 and T008 can run in parallel. +- T010 and T011 can run in parallel. +- After Phase 2, US2 and US3 can proceed in parallel if staffed separately. + +--- + +## Parallel Example: User Story 1 + +```bash +# Launch both regression updates for the switching contract together: +Task: "Extend tests/Feature/Monitoring/HeaderContextBarTest.php to assert the rendered Switch workspace link targets /admin/choose-workspace?choose=1 and does not target /admin/workspaces" +Task: "Keep the canonical chooser target expectation aligned in tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php so topbar and user-menu switching semantics stay consistent" +``` + +--- + +## Parallel Example: User Story 2 + +```bash +# Launch management-separation regression coverage together: +Task: "Extend tests/Feature/Workspaces/ChooseWorkspacePageTest.php to confirm authorized users still see Manage workspaces on the chooser and unauthorized roles still do not" +Task: "Extend tests/Feature/Workspaces/WorkspaceNavigationHubTest.php to confirm navigation separation remains Switch workspace in the topbar only and Manage workspaces in admin navigation" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup +2. Complete Phase 2: Foundational +3. Complete Phase 3: User Story 1 +4. Validate the rendered topbar switch target +5. Stop and review before broader regression work if needed + +### Incremental Delivery + +1. Ship the chooser-target fix in US1 +2. Add management-separation regression protection in US2 +3. Add chooser-flow coherence regression protection in US3 +4. Finish with focused tests and formatting + +### Parallel Team Strategy + +With multiple contributors: + +1. One contributor updates the context-bar and topbar regression tests (US1) +2. One contributor hardens management-separation regressions (US2) +3. One contributor hardens chooser-flow regressions (US3) +4. Recombine for focused Sail test execution and Pint + +--- + +## Notes + +- [P] tasks touch different files and can be executed in parallel. +- User story labels map each task to the corresponding spec story. +- No migrations, no new routes, and no new dependencies are expected. +- Preserve existing chooser redirect behavior unless a failing regression test proves otherwise. diff --git a/tests/Feature/Monitoring/HeaderContextBarTest.php b/tests/Feature/Monitoring/HeaderContextBarTest.php index 251fc22..c1c1103 100644 --- a/tests/Feature/Monitoring/HeaderContextBarTest.php +++ b/tests/Feature/Monitoring/HeaderContextBarTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Filament\Pages\ChooseWorkspace; use App\Models\OperationRun; use App\Models\Tenant; use App\Support\Workspaces\WorkspaceContext; @@ -15,7 +16,7 @@ Filament::setTenant(null, true); - $this->actingAs($user) + $response = $this->actingAs($user) ->withSession([ WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id, WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [ @@ -31,6 +32,15 @@ ->assertSee('Clear tenant context') ->assertSee($tenant->getFilamentName()); + $content = $response->getContent(); + + expect($content)->not->toBeFalse(); + expect( + preg_match('/]+href="([^"]+)"[^>]*>\s*Switch workspace\s*<\/a>/i', (string) $content, $matches) + )->toBe(1); + expect($matches[1] ?? null)->toBe(ChooseWorkspace::getUrl(panel: 'admin').'?choose=1'); + expect($matches[1] ?? null)->not->toContain('/admin/workspaces'); + $this->actingAs($user) ->withSession([ WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id, diff --git a/tests/Feature/Workspaces/ChooseWorkspacePageTest.php b/tests/Feature/Workspaces/ChooseWorkspacePageTest.php index 4cd44a7..fad51bc 100644 --- a/tests/Feature/Workspaces/ChooseWorkspacePageTest.php +++ b/tests/Feature/Workspaces/ChooseWorkspacePageTest.php @@ -163,7 +163,8 @@ $this->actingAs($user) ->get(route('filament.admin.pages.choose-workspace')) ->assertOk() - ->assertSee('Manage workspaces'); + ->assertSee('Manage workspaces') + ->assertSee(route('filament.admin.resources.workspaces.index', absolute: false), false); }); it('hides manage link for non-owner roles', function (): void { @@ -181,7 +182,8 @@ $this->actingAs($user) ->get(route('filament.admin.pages.choose-workspace')) ->assertOk() - ->assertDontSee('Manage workspaces'); + ->assertDontSee('Manage workspaces') + ->assertDontSee(route('filament.admin.resources.workspaces.index', absolute: false), false); }); // --- T020: it_has_no_n_plus_1_queries_in_chooser --- diff --git a/tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php b/tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php index 149ff6f..302b4d1 100644 --- a/tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php +++ b/tests/Feature/Workspaces/ChooseWorkspaceRedirectsToChooseTenantTest.php @@ -9,6 +9,7 @@ use App\Models\User; use App\Models\Workspace; use App\Models\WorkspaceMembership; +use App\Support\Workspaces\WorkspaceContext; use Illuminate\Foundation\Testing\RefreshDatabase; use Livewire\Livewire; @@ -91,3 +92,21 @@ ->call('selectWorkspace', $workspace->getKey()) ->assertRedirect('/admin/choose-tenant'); }); + +it('prefers the stored intended url after selecting a workspace', function (): void { + $user = User::factory()->create(); + + $workspace = Workspace::factory()->create(); + WorkspaceMembership::factory()->create([ + 'workspace_id' => $workspace->getKey(), + 'user_id' => $user->getKey(), + 'role' => 'owner', + ]); + + session()->put(WorkspaceContext::INTENDED_URL_SESSION_KEY, '/admin/operations'); + + Livewire::actingAs($user) + ->test(ChooseWorkspace::class) + ->call('selectWorkspace', $workspace->getKey()) + ->assertRedirect('/admin/operations'); +}); diff --git a/tests/Feature/Workspaces/EnsureWorkspaceSelectedMiddlewareTest.php b/tests/Feature/Workspaces/EnsureWorkspaceSelectedMiddlewareTest.php index 80adedf..683f4ae 100644 --- a/tests/Feature/Workspaces/EnsureWorkspaceSelectedMiddlewareTest.php +++ b/tests/Feature/Workspaces/EnsureWorkspaceSelectedMiddlewareTest.php @@ -220,6 +220,30 @@ $response->assertRedirect('/admin/choose-workspace'); }); +it('redirects to the chooser when switching is explicitly requested with choose=1', function (): void { + $user = User::factory()->create(); + $workspaceA = Workspace::factory()->create(); + $workspaceB = Workspace::factory()->create(); + + WorkspaceMembership::factory()->create([ + 'workspace_id' => $workspaceA->getKey(), + 'user_id' => $user->getKey(), + 'role' => 'owner', + ]); + + WorkspaceMembership::factory()->create([ + 'workspace_id' => $workspaceB->getKey(), + 'user_id' => $user->getKey(), + 'role' => 'operator', + ]); + + $response = $this->actingAs($user) + ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspaceA->getKey()]) + ->get('/admin/_test/workspace-context?choose=1'); + + $response->assertRedirect('/admin/choose-workspace'); +}); + // --- T023: it_clears_session_when_active_workspace_membership_revoked --- it('clears session when active workspace membership revoked', function (): void { diff --git a/tests/Feature/Workspaces/WorkspaceNavigationHubTest.php b/tests/Feature/Workspaces/WorkspaceNavigationHubTest.php index 71b9588..601cd50 100644 --- a/tests/Feature/Workspaces/WorkspaceNavigationHubTest.php +++ b/tests/Feature/Workspaces/WorkspaceNavigationHubTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Filament\Pages\ChooseWorkspace; use App\Models\User; use App\Models\Workspace; use App\Models\WorkspaceMembership; @@ -11,30 +12,46 @@ uses(RefreshDatabase::class); -it('does not show "Switch workspace" in sidebar navigation (topbar-only)', function (): void { +it('keeps workspace switching out of sidebar navigation while management stays on its dedicated admin path', function (): void { $user = User::factory()->create(); $workspace = Workspace::factory()->create(); + $secondaryWorkspace = Workspace::factory()->create(); + WorkspaceMembership::factory()->create([ 'workspace_id' => $workspace->getKey(), 'user_id' => $user->getKey(), 'role' => 'owner', ]); + WorkspaceMembership::factory()->create([ + 'workspace_id' => $secondaryWorkspace->getKey(), + 'user_id' => $user->getKey(), + 'role' => 'operator', + ]); + Filament::setTenant(null, true); - $this->actingAs($user) + $response = $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()]) ->get('/admin/operations') ->assertOk(); + $response->assertSee('Switch workspace') + ->assertSee(ChooseWorkspace::getUrl(panel: 'admin').'?choose=1', false); + $panel = Filament::getCurrentOrDefaultPanel(); - $labels = collect($panel->getNavigationItems()) + $navigationItems = collect($panel->getNavigationItems()); + $labels = $navigationItems ->map(static fn ($item): string => $item->getLabel()) ->all(); + $manageWorkspaces = $navigationItems + ->first(static fn ($item): bool => $item->getLabel() === 'Manage workspaces'); expect($labels)->not->toContain('Switch workspace'); expect($labels)->toContain('Manage workspaces'); expect($labels)->not->toContain('Workspaces'); + expect($manageWorkspaces)->not->toBeNull(); + expect($manageWorkspaces->getUrl())->toBe(route('filament.admin.resources.workspaces.index')); }); diff --git a/tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php b/tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php index 32ac538..b109bdf 100644 --- a/tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php +++ b/tests/Feature/Workspaces/WorkspaceSwitchUserMenuTest.php @@ -2,10 +2,12 @@ declare(strict_types=1); +use App\Filament\Pages\ChooseWorkspace; use App\Models\User; use App\Models\Workspace; use App\Models\WorkspaceMembership; use App\Support\Workspaces\WorkspaceContext; +use Filament\Facades\Filament; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Http; @@ -44,7 +46,11 @@ ->get('/admin/workspaces'); $response->assertOk(); - $response->assertSee('choose-workspace?choose=1', false); + + $switchWorkspace = Filament::getUserMenuItems()['switch-workspace'] ?? null; + + expect($switchWorkspace)->not->toBeNull(); + expect($switchWorkspace->getUrl())->toBe(ChooseWorkspace::getUrl(panel: 'admin').'?choose=1'); }); // --- T032: it_hides_switch_workspace_menu_when_single_workspace --- @@ -69,5 +75,6 @@ ->get('/admin/workspaces'); $response->assertOk(); - $response->assertDontSee('choose-workspace?choose=1', false); + + expect(Filament::getUserMenuItems())->not->toHaveKey('switch-workspace'); });