diff --git a/README.md b/README.md
index 2fa0bf5..fd7eabc 100644
--- a/README.md
+++ b/README.md
@@ -265,6 +265,8 @@ npm run build
- Added workspace-scoped MongoDB document metadata and dashboard filtering.
- Existing owner-owned spaces and pages are backfilled into the selected
workspace on first workspace load without changing their content or sharing.
+- Legacy users without a tenant are automatically given a personal
+ organization and workspace, so Invite Fellow works immediately after login.
- New document creation rejects workspace IDs the authenticated user cannot
access.
- Added a workspace administration page at `/settings/workspaces` for
@@ -276,6 +278,7 @@ npm run build
- Removed the unused Customize sidebar action and its controls.
- Updated Invite Fellow to create a workspace invitation and open a prefilled
email containing the invitation link.
+- Added the invitation landing page at `/invite/:token`.
Validation completed:
@@ -292,3 +295,28 @@ Next tenant work:
- Add invitation and audit history views to the administration page.
- Add organization-level security settings.
- Add authenticated browser coverage for workspace creation and role denial.
+
+#### Workspace navigation polish
+
+- Workspace and page views now retain the primary dashboard navigation:
+ For you, Templates, Recent, Favourite Projects, and All Projects.
+- The navigation appears above the current space/page tree instead of replacing
+ the dashboard experience; the duplicate Dashboard button was removed because
+ the workspace header already provides that route.
+- Space views now keep the sidebar visible, with the active space and its
+ content shown beneath the primary navigation.
+- Navigating to a child page now automatically keeps its ancestor pages
+ expanded in the tree.
+- New child pages are created as `Untitled` so users can name them directly.
+- Template content now stays isolated to the page created from that template;
+ switching between pages remounts the editor for the active document instead
+ of reusing stale content from the previous page.
+- Workspace sidebar navigation now opens the corresponding dashboard view:
+ Recent, Favourite Projects, All Projects, and Templates no longer all open
+ the default dashboard state.
+- Workspace navigation now opens those flyouts in place without leaving the
+ current page or space. Dashboard and workspace navigation use matching
+ sidebar width, button height, padding, and text sizing.
+- Workspace navigation now mirrors dashboard controls: “For you” links to the
+ dashboard, while Recent, Favourite Projects, All Projects, and Templates
+ use trailing chevrons and active flyout states.
diff --git a/backend/app/routers/tenant.py b/backend/app/routers/tenant.py
index 7daff05..ceec6d7 100644
--- a/backend/app/routers/tenant.py
+++ b/backend/app/routers/tenant.py
@@ -28,6 +28,7 @@ from app.services.tenant_service import (
list_members,
list_workspaces,
update_member_role,
+ ensure_personal_workspace,
)
router = APIRouter(prefix="/tenants", tags=["tenants"])
@@ -48,6 +49,7 @@ def workspace_response(row) -> WorkspaceResponse:
@router.get("/workspaces", response_model=list[WorkspaceResponse])
def get_workspaces(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
+ ensure_personal_workspace(db, current_user)
return [workspace_response(row) for row in list_workspaces(db, current_user.id)]
diff --git a/backend/app/services/tenant_service.py b/backend/app/services/tenant_service.py
index 242e9a6..1555503 100644
--- a/backend/app/services/tenant_service.py
+++ b/backend/app/services/tenant_service.py
@@ -53,6 +53,37 @@ def create_workspace(db: Session, owner: User, data) -> Workspace:
return workspace
+def ensure_personal_workspace(db: Session, owner: User) -> Workspace:
+ existing = (
+ db.query(Workspace)
+ .join(WorkspaceMembership, WorkspaceMembership.workspace_id == Workspace.id)
+ .filter(WorkspaceMembership.user_id == owner.id)
+ .order_by(Workspace.created_at.asc())
+ .first()
+ )
+ if existing is not None:
+ return existing
+ label = (owner.full_name or owner.username or "Personal").strip()
+ org_slug = f"{slugify(owner.username)}-personal"
+ if db.query(Organization).filter_by(slug=org_slug).first():
+ org_slug = f"{org_slug}-{secrets.token_hex(3)}"
+ organization = Organization(name=f"{label}'s organization", slug=org_slug, owner_id=owner.id)
+ db.add(organization)
+ db.flush()
+ workspace = Workspace(
+ organization_id=organization.id,
+ name="Personal workspace",
+ slug="personal-workspace",
+ )
+ db.add(workspace)
+ db.flush()
+ db.add(WorkspaceMembership(workspace_id=workspace.id, user_id=owner.id, role="owner"))
+ audit(db, organization.id, workspace.id, owner.id, "workspace.bootstrapped", workspace.name)
+ db.commit()
+ db.refresh(workspace)
+ return workspace
+
+
def list_workspaces(db: Session, user_id: int):
return (
db.query(Workspace, Organization, WorkspaceMembership.role)
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index c9d0c16..c8df6dd 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -12,6 +12,7 @@ import ForbiddenPage from './pages/ForbiddenPage'
import ProtectedRoute from './components/ProtectedRoute'
import AccountPage from './pages/AccountPage'
import WorkspaceAdminPage from './pages/WorkspaceAdminPage'
+import InvitationPage from './pages/InvitationPage'
import { authApi, saveAuth } from './lib/api'
function GoogleCallback() {
@@ -79,6 +80,7 @@ export default function App() {
}
/>
+ } />
} />
> = {
}
export default function DashboardLayout() {
+ const [searchParams] = useSearchParams()
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [activeNav, setActiveNav] = useState('for-you')
const [flyout, setFlyout] = useState(null)
+ useEffect(() => {
+ const view = searchParams.get('view') as NavItemId | null
+ if (!view || !(view in flyoutMap)) return
+ setActiveNav(view)
+ setFlyout(flyoutMap[view] ?? null)
+ }, [searchParams])
+
const { recent, projects, starredProjects, loading, error, refresh } =
useDashboardDocuments()
diff --git a/frontend/src/components/dashboard/DashboardSidebar.tsx b/frontend/src/components/dashboard/DashboardSidebar.tsx
index 1f0af67..a306e54 100644
--- a/frontend/src/components/dashboard/DashboardSidebar.tsx
+++ b/frontend/src/components/dashboard/DashboardSidebar.tsx
@@ -65,7 +65,7 @@ export default function DashboardSidebar({
if (collapsed) {
return (
-