Merge branch 'main' into feat/l1-workspace
Some checks failed
Mirror to GitHub / mirror (push) Successful in 4s
CI / frontend (pull_request) Failing after 1m52s
CI / e2e (pull_request) Failing after 6m6s
CI / backend (pull_request) Successful in 12m15s

# Conflicts:
#	frontend/src/router.tsx
This commit is contained in:
2026-05-29 00:24:54 -04:00
43 changed files with 1246 additions and 412 deletions

View File

@@ -58,7 +58,7 @@ export function AppLayout() {
}
const mobileNavItems = [
{ path: '/', label: 'Dashboard', icon: LayoutGrid },
{ path: '/home', label: 'Dashboard', icon: LayoutGrid },
{ path: '/sessions', label: 'Session History', icon: Clock },
{ path: '/escalations', label: 'Escalations', icon: AlertTriangle },
{ path: '/trees', label: 'Guided Flows', icon: GitBranch },
@@ -106,7 +106,7 @@ export function AppLayout() {
style={{ background: 'var(--color-bg-sidebar)', borderRight: '1px solid var(--color-border-default)' }}
>
<div className="flex h-14 items-center justify-between px-4" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
<Link to="/" className="flex items-center gap-2.5">
<Link to="/home" className="flex items-center gap-2.5">
<BrandLogo size="sm" />
<span className="text-sm font-heading font-bold text-text-heading">ResolutionFlow</span>
</Link>

View File

@@ -40,7 +40,7 @@ interface Group {
}
const PAGES: PaletteItem[] = [
{ id: 'page-dashboard', group: 'pages', title: 'Dashboard', path: '/', icon: 'page' },
{ id: 'page-dashboard', group: 'pages', title: 'Dashboard', path: '/home', icon: 'page' },
{ id: 'page-flows', group: 'pages', title: 'All Flows', subtitle: 'Browse your flow library', path: '/trees', icon: 'page' },
{ id: 'page-sessions', group: 'pages', title: 'Sessions', subtitle: 'View session history', path: '/sessions', icon: 'page' },
{ id: 'page-flowpilot', group: 'pages', title: 'FlowPilot', subtitle: 'AI troubleshooting', path: '/pilot', icon: 'page' },

View File

@@ -22,7 +22,7 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps)
}
if (!isAuthenticated) {
return <Navigate to="/landing" state={{ from: location }} replace />
return <Navigate to="/" state={{ from: location }} replace />
}
// Enforce must_change_password — redirect unless already on /change-password

View File

@@ -63,7 +63,7 @@ export function TopBar() {
>
{/* Logo area */}
<Link
to="/"
to="/home"
className="flex items-center gap-2.5 pr-4 transition-all duration-200"
>
<BrandLogo size="sm" />

View File

@@ -71,11 +71,11 @@ const FROZEN_NOW = new Date('2026-05-06T00:00:00Z')
function renderAppLayout() {
return render(
<MemoryRouter initialEntries={['/']}>
<MemoryRouter initialEntries={['/home']}>
<Routes>
<Route element={<AppLayout />}>
<Route
index
path="/home"
element={<div data-testid="child-route-content">child route</div>}
/>
</Route>

View File

@@ -0,0 +1,59 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'
import { ProtectedRoute } from '../ProtectedRoute'
import { useAuthStore } from '@/store/authStore'
/**
* Probe component: surfaces the current pathname and `location.state.from` so
* the test can assert both the redirect target and that the original
* destination is preserved for post-login return.
*/
function LocationProbe() {
const loc = useLocation()
const from =
(loc.state as { from?: { pathname?: string } } | null)?.from?.pathname ?? ''
return (
<>
<div data-testid="probe-pathname">{loc.pathname}</div>
<div data-testid="probe-from">{from}</div>
</>
)
}
describe('ProtectedRoute — unauthenticated redirect', () => {
beforeEach(() => {
useAuthStore.setState({
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
})
})
it('redirects unauthenticated visits to /home → / and preserves origin in state.from', () => {
render(
<MemoryRouter initialEntries={['/home']}>
<Routes>
<Route
path="/home"
element={
<ProtectedRoute>
<div data-testid="home-content">home</div>
</ProtectedRoute>
}
/>
<Route path="/" element={<LocationProbe />} />
</Routes>
</MemoryRouter>,
)
// The protected page should not render.
expect(screen.queryByTestId('home-content')).not.toBeInTheDocument()
// We landed on / (the public landing route), not /landing.
expect(screen.getByTestId('probe-pathname')).toHaveTextContent('/')
expect(screen.getByTestId('probe-from')).toHaveTextContent('/home')
})
})