User: 'workspace stays in URL only until tab change' + 'use random 25-char
string or UUID for workspace identifier'.
Migration:
- workspaces.public_id: varchar(25) unique nullable.
- Backfill existing rows with Str::random(25).
Workspace model:
- Auto-fill public_id on create via booted() with collision-check loop.
- Override getRouteKeyName() → 'public_id' so route-model-binding uses
the opaque public_id everywhere (not the guessable slug).
Routes:
- Authed routes wrapped in prefix 'w/{wsId}' → URL shape
/w/bPSyuQpMDI0bl0LBKlwVOLdsl/dashboard etc.
- Bare /dashboard /sites /servers etc. now redirect to current
workspace's prefixed URL (covers external bookmarks / first visit).
ResolveCurrentWorkspace middleware:
- Resolution priority: route param {wsId} → ?workspace query →
session sticky → user.current_workspace_id → first owned.
- On resolve: URL::defaults(['wsId' => public_id]) auto-injects param
into every route() call, so all Volt wire:navigate / @route()
helpers carry the workspace through the entire SPA session.
- forceFill + saveQuietly to avoid triggering observers on user.
Sidebar:
- Workspace-switch links now point to /w/{public_id}/dashboard (not
the slug ?workspace=…).
TestCase:
- URL::defaults({wsId: 'test-workspace'}) so route('dashboard') etc.
generates valid URLs in tests.
- actingAsWithWorkspace() helper for tests that need a real workspace
bound to a logged-in user.
Browser-verified: click Sidebar → Sites navigates to
/w/bPSyuQpMDI0bl0LBKlwVOLdsl/sites, public_id sticks across every
internal navigation. 36/36 Page-Pest still green.
|
||
|---|---|---|
| .. | ||
| burger.blade.php | ||
| sidebar.blade.php | ||