114 lines
6.9 KiB
Markdown
114 lines
6.9 KiB
Markdown
# Implementation Plan: Workspace Chooser v1 (Enterprise) + In-App Switch Entry Point
|
|
|
|
**Branch**: `107-workspace-chooser` | **Date**: 2026-02-22 | **Spec**: [spec.md](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:
|
|
|
|
1. **Refactor `EnsureWorkspaceSelected` middleware** to implement the spec's 7-step auto-resume algorithm with stale-membership detection and flash warnings.
|
|
2. **Upgrade the `ChooseWorkspace` page** with role badges, tenant counts, "Manage workspaces" link (capability-gated), and cleaned-up empty state (no "Create workspace" header action).
|
|
3. **Add audit events** for workspace auto-selection and manual selection via new `AuditActionId` enum cases + `WorkspaceAuditLogger` calls.
|
|
4. **Add "Switch workspace" user menu entry** visible only when user has >1 workspace membership.
|
|
5. **Support `?choose=1` forced 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.*
|
|
|
|
- [x] **Inventory-first**: N/A — this feature does not interact with Inventory. Workspace selection is a session context operation.
|
|
- [x] **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.
|
|
- [x] **Graph contract path**: N/A — no Microsoft Graph calls in this feature. All data is local (workspaces, memberships, session).
|
|
- [x] **Deterministic capabilities**: `Capabilities::WORKSPACE_MANAGE` is referenced via the canonical registry constant. No new capabilities introduced.
|
|
- [x] **RBAC-UX**: Feature operates in the `/admin` plane only. Non-member workspace selection returns 404 (deny-as-not-found) via `WorkspaceContext::isMember()`. "Manage workspaces" link gated by `workspace.manage` capability. No cross-plane access introduced.
|
|
- [x] **Workspace isolation**: Middleware ensures workspace membership on every `/admin/*` request. Stale sessions are cleared and redirected. Non-members get 404.
|
|
- [x] **Destructive actions**: No destructive actions in this feature. The re-selection is a non-destructive context switch.
|
|
- [x] **Global search**: No changes to global search behavior.
|
|
- [x] **Tenant isolation**: Not directly affected. After workspace selection, the existing tenant-count branching routes to tenant-scoped flows.
|
|
- [x] **Run observability**: N/A — workspace selection is a synchronous, DB-only, < 2s session operation. No `OperationRun` needed. Selection events are audit-logged.
|
|
- [x] **Automation**: N/A — no queued/scheduled operations.
|
|
- [x] **Data minimization**: Audit log stores only `actor_id`, `workspace_id`, `method`, `reason`, `prev_workspace_id` — no secrets/tokens/PII.
|
|
- [x] **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: no `BadgeDomain` exists for workspace roles.
|
|
- [x] **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.
|
|
- [x] **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)
|
|
|
|
```text
|
|
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)
|
|
|
|
```text
|
|
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 |
|
|
|-----------|------------|-------------------------------------|
|
|
| — | — | — |
|