Routing and Layouts
AEGIS uses the Next.js App Router with route groups to separate public pages (login) from authenticated dashboard pages. This page documents the complete routing structure, layout hierarchy, and authentication middleware.
Layout Hierarchy
RootLayout (app/layout.tsx)
├── ThemeProvider (next-themes, dark default)
│ └── AuthProvider (React context for user state)
│ └── TooltipProvider (shadcn/ui tooltips)
│ ├── LoginPage (/login)
│ └── DashboardLayout (app/(dashboard)/layout.tsx)
│ ├── Sidebar (collapsible navigation)
│ ├── Topbar (tenant info, density toggle, theme, user)
│ ├── Toaster (sonner toast notifications)
│ └── DashboardShell
│ ├── DensityProvider (compact/comfortable/spacious)
│ ├── CommandPalette (Cmd+K)
│ └── PageTransition (Framer Motion)
│ └── {page content}Root Layout
Located at src/app/layout.tsx, the root layout wraps the entire application:
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${inter.variable} ${jetbrainsMono.variable} antialiased`}>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
<AuthProvider>
<TooltipProvider>{children}</TooltipProvider>
</AuthProvider>
</ThemeProvider>
</body>
</html>
);
}Key details:
- Fonts: Inter (body text) and JetBrains Mono (monospace/code) loaded via
next/font/google. - Theme: Dark by default, with system theme detection enabled.
- Auth:
AuthProviderwraps all pages so both login and dashboard pages can access auth state. - Metadata: Title is “AEGIS” (
app/layout.tsx—description: "Agent Execution, Graph Intelligence & State").
Root Page
The root page at src/app/page.tsx immediately redirects to the compliance dashboard:
import { redirect } from "next/navigation";
export default function Home() { redirect("/compliance"); }Dashboard Layout
The (dashboard) route group at src/app/(dashboard)/layout.tsx wraps all authenticated pages:
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen overflow-hidden">
<Sidebar />
<div className="flex-1 flex flex-col overflow-hidden">
<Topbar />
<main className="flex-1 overflow-auto bg-background p-6 flex flex-col">
<DashboardShell>{children}</DashboardShell>
</main>
</div>
<Toaster richColors position="bottom-right" />
</div>
);
}The layout uses a horizontal flex layout: collapsible sidebar on the left, main content area on the right with topbar + scrollable content.
The DashboardShell component provides:
- DensityProvider: manages compact/comfortable/spacious display modes, stored in
localStorage. - CommandPalette: global Cmd+K navigation overlay.
- PageTransition: Framer Motion
AnimatePresencefor smooth page transitions keyed onpathname.
Page Routes
| Path | File | Description |
|---|---|---|
/ | app/page.tsx | Redirects to /compliance |
/login | app/login/page.tsx | Email/password login page (public) |
/invite/[token] | app/invite/[token]/page.tsx | Invite redemption — validate token, set password, log in (public, R42b) |
/compliance | app/(dashboard)/compliance/page.tsx | Compliance dashboard (default landing) |
/compliance/[entityId]/[domain] | app/(dashboard)/compliance/[entityId]/[domain]/page.tsx | Entity compliance workspace (three-panel layout) |
/filings | app/(dashboard)/filings/page.tsx | Filing review queue (filing-class HITL checkpoints) |
/approvals | app/(dashboard)/approvals/page.tsx | Agent approvals queue (non-filing HITL checkpoints) |
/flaring | app/(dashboard)/flaring/page.tsx | Flaring dashboard with R-32 tracking |
/events | app/(dashboard)/events/page.tsx | Events timeline |
/events/[eventId] | app/(dashboard)/events/[eventId]/page.tsx | Event detail view |
/conversations | app/(dashboard)/conversations/page.tsx | Agent chat with SSE streaming |
/entity | app/(dashboard)/entity/page.tsx | Redirects to the explorer’s first tab (first registry entity type, same ordering as the tab bar; falls back to /entity/well if the registry is unreachable) |
/entity/[type] | app/(dashboard)/entity/[type]/page.tsx | Entity list by type |
/entity/well/[id] | app/(dashboard)/entity/well/[id]/page.tsx | Well detail view |
/entity/facility/[id] | app/(dashboard)/entity/facility/[id]/page.tsx | Facility detail view |
/entity/[type]/[id] | app/(dashboard)/entity/[type]/[id]/page.tsx | Generic entity detail view |
/skill-builder | app/(dashboard)/skill-builder/page.tsx | Author’s “My Skills” list (owner-scoped via ?mine=true) with review status |
/skill-builder/new | app/(dashboard)/skill-builder/new/page.tsx | Create a skill (author lands pending_review) |
/skill-builder/[skillId] | app/(dashboard)/skill-builder/[skillId]/page.tsx | Author skill editor (shared SkillEditor, authorView) |
/configuration/entity-types | app/(dashboard)/configuration/entity-types/page.tsx | Entity type definitions |
/configuration/entity-types/[typeId] | app/(dashboard)/configuration/entity-types/[typeId]/page.tsx | Entity type detail/edit |
/configuration/relationship-types | app/(dashboard)/configuration/relationship-types/page.tsx | Relationship type definitions |
/configuration/relationship-types/[id] | app/(dashboard)/configuration/relationship-types/[id]/page.tsx | Relationship type detail |
/configuration/event-types | app/(dashboard)/configuration/event-types/page.tsx | Event type definitions |
/configuration/event-types/new | app/(dashboard)/configuration/event-types/new/page.tsx | Create new event type |
/configuration/event-types/[typeId]/edit | app/(dashboard)/configuration/event-types/[typeId]/edit/page.tsx | Edit event type |
/configuration/event-types/[typeId]/detection-rules/[ruleId] | nested page | Detection rule editor |
/configuration/prompts | app/(dashboard)/configuration/prompts/page.tsx | Prompt namespace list |
/configuration/prompts/[namespaceKey] | nested page | Namespace detail (templates, settings, tiers, access) |
/configuration/prompts/[ns]/[slug] | nested page | Template detail with version sidebar |
/configuration/prompts/[ns]/[slug]/versions/draft | nested page | Version editor with validation panel |
/configuration/prompts/[ns]/[slug]/versions/history | nested page | Version history table |
/configuration/prompts/[ns]/[slug]/versions/compare | nested page | Side-by-side version comparison |
/configuration/prompts/approvals | app/(dashboard)/configuration/prompts/approvals/page.tsx | Prompt approval queue |
/configuration/skills | app/(dashboard)/configuration/skills/page.tsx | All skills tenant-wide + review queue (admin) |
/configuration/skills/new | app/(dashboard)/configuration/skills/new/page.tsx | Create a skill (admin lands approved) |
/configuration/skills/[skillId]/edit | app/(dashboard)/configuration/skills/[skillId]/edit/page.tsx | Admin skill editor (shared SkillEditor) |
/configuration/routers | app/(dashboard)/configuration/routers/page.tsx | Skill Routers list: recruitment taxonomy for message-driven selection, with an unrouted-skills banner (approved skills unreachable via selection) and admin router creation |
/configuration/routers/[routerId] | app/(dashboard)/configuration/routers/[routerId]/page.tsx | Router detail: edit blurb / retire-reactivate (no delete — soft-deactivation only), mappings table with per-skill render state, disambiguation description versions, and a read-only “what the LLM sees” payload preview |
/configuration/platform-settings | app/(dashboard)/configuration/platform-settings/page.tsx | Platform Settings (admin-only): tenant LLM runtime config — default/fallback model from the curated picker, per-execution budgets, daily token/cost caps, and per-user budget overrides (user picker fed by GET /api/v1/auth/users). Reads/writes /api/v1/settings*; blank fields inherit the env defaults, and the sources map from GET /settings labels each effective value tenant or default |
/configuration/capabilities | app/(dashboard)/configuration/capabilities/page.tsx | Core capability catalog (the tool/action primitives skills and rules bind to) |
/configuration/catalogs | app/(dashboard)/configuration/catalogs/page.tsx | Catalogs & Categories admin |
/configuration/evals | app/(dashboard)/configuration/evals/page.tsx | Eval Runs dashboard (baseline-able regression runs; reads eval_runs/eval_case_results) |
/configuration/policies | app/(dashboard)/configuration/policies/page.tsx | R37d — Compliance Policies: the admin-owned mandatory rule floor. Lists mandatory rules with their applies_to selector; create/edit via the shared RuleForm (structured condition + live match preview). Skill authors see these read-only in the skill editor’s Rules tab (two sections: locked “Mandatory (enforced)” with provenance over the skill’s own rules). R37e makes the “This skill’s rules” section add/edit/delete on both editor surfaces via the same RuleForm in skill-rule mode (no applies_to, advisory forced, writes through the skill-nested author path); a non-admin author’s rule write reopens the skill’s review, and circumvention lint warnings render inline, at save time, and in the ReviewDialog |
/admin | app/(admin)/admin/page.tsx | Admin home (separate (admin) route group, its own layout) |
/admin/users | app/(admin)/admin/users/page.tsx | R42d — Users & Roles admin: list/create users (invite links), edit roles, deactivate/reactivate, one-time password-reset links (/api/v1/auth/admin/*) |
The Skill Builder and Configuration › Skills routes share one editor
(components/skills/SkillEditor) and create form (SkillCreateForm); the two
route trees are thin wrappers differing only in basePath, breadcrumb, and the
authorView flag. Skill authoring authz is state-machine based (R37a): a
non-admin create lands pending_review and any non-admin edit reopens review,
so agents cannot load unapproved skill code. Dry-run (R37b) is reachable from the
code-block editor for a skill’s author or an admin, and is not review-gated.
Sidebar Navigation
The sidebar is defined in src/components/sidebar.tsx and features:
- Collapsible with animated width transition (52px collapsed, 240px expanded). State is persisted to
localStorageunderaegis-sidebar-collapsed. - Active indicator — a blue vertical bar animates between items using Framer Motion
layoutId. - Sections: primary navigation items, a divider, and a “Configuration” section with nested children.
- Role-gated Configuration (R42d): the Configuration section renders when
useAuth().user?.rolesincludesadminorpower_user. Two entries stay admin-only (adminOnly): Prompt Approvals and Platform Settings. Non-admin authors also reach the top-level Skill Builder entry. This is nav-gating only — backend routes enforce the real authorization.
Navigation items:
Compliance /compliance (ShieldCheck icon)
Filings /filings (ClipboardList icon)
Approvals /approvals (CheckCircle icon)
Flaring /flaring (Flame icon)
Events /events (CalendarClock icon)
Conversations /conversations (MessageSquare icon)
Entity Explorer /entity (Landmark icon)
Skill Builder /skill-builder (Hammer icon)
--- divider ---
Configuration (admin or power_user)
Entity Types /configuration/entity-types (LayoutGrid icon)
Relationship Types /configuration/relationship-types (GitBranch icon)
Event Types /configuration/event-types (Zap icon)
Capabilities /configuration/capabilities (Zap icon)
Prompts /configuration/prompts (FileCode icon)
Skills /configuration/skills (Layers icon)
Skill Routers /configuration/routers (Route icon)
Compliance Policies /configuration/policies (ShieldCheck icon)
Catalogs & Categories /configuration/catalogs (BookOpen icon)
Prompt Approvals /configuration/prompts/approvals (CheckCircle icon, admin-only)
Platform Settings /configuration/platform-settings (SlidersHorizontal icon, admin-only)
Eval Runs /configuration/evals (FlaskConical icon)The sidebar footer displays the tenant branding label (the tenant_branding Platform Setting; the seeded RRC tenant shows “RRC District 08 — Permian Basin”, and a blank setting shows no footer line — E0.3).
Authentication Middleware
The middleware at src/middleware.ts protects all dashboard routes:
const PUBLIC = ["/login", "/_next", "/favicon.ico"];
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (PUBLIC.some((p) => pathname.startsWith(p))) return NextResponse.next();
const token = request.cookies.get("aegis_token")?.value;
if (!token) {
const url = new URL("/login", request.url);
url.searchParams.set("redirect", pathname);
return NextResponse.redirect(url);
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};Key behavior:
- Public paths (
/login,/_next,/favicon.ico) are excluded from auth checks. - If no
aegis_tokencookie is found, the user is redirected to/loginwith aredirectquery parameter to return them to their original destination after login. - The middleware only checks for the presence of a cookie — actual token validation happens at the API Gateway when backend requests are made.
Login Page
The login page at src/app/login/page.tsx provides an email + password form:
- User enters their email and password.
- The
login()function inlib/api.tssends a POST to/api/v1/auth/token(same-origin, routed through the gateway to auth-service). - On success, auth-service sets the httpOnly
aegis_tokencookie viaSet-Cookie(HttpOnly; Secure; SameSite=Lax). The response body carries only{user_id, roles, email, display_name}— noaccess_token(R42b). AuthContextis hydrated from that response body. There is nolocalStoragepersistence — a reload re-hydrates identity fromGET /api/v1/auth/me.- The user is redirected to their original path (the
redirectquery param) or/conversations.
For local development, log in with admin@aegis.local / aegis-dev-admin (the seeded bootstrap admin). Accounts are admin-provisioned — the primary flow is the Users & Roles admin UI at /admin/users, which issues one-time invite links (the invitee sets their own password via /invite/[token]); the auth_service.create_user CLI is a break-glass fallback. There is no self-serve signup.
Topbar
The topbar (src/components/topbar.tsx) displays:
- Tenant info: “Permian Basin Energy LLC” with tenant label.
- Search: Cmd+K shortcut button that triggers the command palette.
- Density toggle: Three-mode toggle (compact, comfortable, spacious) for adjusting UI density.
- Admin link: External link to
/admin. - User info: Role badge (Admin/Operator), user ID, theme toggle, and sign-out button.