Component Patterns
Dashboard shell architecture, custom component conventions, composition patterns, and guidelines for building new components.
Beyond the shadcn/ui primitives, Trovella has several categories of custom components that follow consistent patterns. This page documents the composition architecture, naming conventions, and guidelines for building new components.
Dashboard Shell
The dashboard shell is the primary application layout, wrapping every authenticated page. It assembles three layers:
DashboardPage (Server Component)
|-- ensurePersonalOrganization()
|-- ensureActiveOrganization()
|
v
Providers (Client) -- tRPC + TanStack Query
|
v
DashboardShell (Client)
|-- SidebarProvider (cookie-persisted sidebar state)
| |-- AppSidebar (collapsible navigation)
| |-- SidebarInset (main content area)
| |-- DashboardTopBar (breadcrumbs, search, user menu)
| |-- <main> (page content)
DashboardPage (Server)
src/components/dashboard/dashboard-page.tsx is the only server component in the dashboard stack. It handles two idempotent setup operations on every render:
ensurePersonalOrganization()-- creates the user's personal org on first sign-inensureActiveOrganization()-- auto-sets the active org if none is selected
Every authenticated page wraps its content in DashboardPage:
export default async function DiscoverPage() {
const session = await getSession();
if (!session) redirect("/auth/sign-in?callbackUrl=/discover");
return (
<DashboardPage session={session}>
<ComingSoon feature="Discover" />
</DashboardPage>
);
}
AppSidebar
src/components/dashboard/app-sidebar.tsx renders the collapsible navigation with two groups:
| Group | Items |
|---|---|
| Navigation | Dashboard, Discover, Create, Chat, Memory |
| Admin | AI Logs, Research Plans, Hybrid Search, Skill Executions, Wiki |
The sidebar uses collapsible="icon" mode, collapsing from a full-width nav panel to a 3rem icon-only rail. Each nav item uses SidebarMenuButton with asChild wrapping a Next.js Link, and the active state is determined by comparing usePathname() against the item's href.
The header zone includes a workspace indicator (showing "Personal" / "Workspace") with a collapse/expand toggle button. In collapsed mode, the workspace icon is replaced by the expand button.
DashboardTopBar
src/components/dashboard/dashboard-top-bar.tsx renders the top navigation bar with:
- Mobile menu button -- visible only on mobile, triggers sidebar sheet
- Breadcrumb navigation -- auto-generated from a
pageNameslookup map - Search input -- styled with a search icon overlay, currently non-functional (placeholder)
- Help button -- links to
/docs - User account menu -- avatar with dropdown containing settings link, theme toggle, and sign-out
The user menu embeds the ThemeToggle component inline (not in a separate settings page), providing quick theme switching from any dashboard page.
ComingSoon
src/components/dashboard/coming-soon.tsx is a placeholder component for routes that are planned but not yet built. It shows a construction icon, the feature name as a heading, and a brief message. Used by stub pages (Discover, Create, Chat, Memory).
Landing Page Components
The landing page (src/components/landing/) uses a section-based architecture where each section is an independent component:
| Component | Description |
|---|---|
LandingPage | Orchestrator -- composes all sections in order |
LandingHero | Headline, CTA buttons, persona cards, trust signal bar |
LandingBento | Feature grid with visual showcase |
LandingHowItWorks | Three-step explanation flow |
LandingUseCases | Persona-specific use case cards |
LandingFeatureStrip | Horizontal feature highlights |
LandingVideo | Video showcase section |
LandingPricing | Pricing tier cards |
LandingFaq | Accordion FAQ section |
LandingCta | Final call-to-action section |
LandingFooter | Footer with links and brand |
PersonaCards | Animated persona cards for the hero section |
Landing components use framer-motion for entrance animations (motion.div with initial/animate/transition props). This is the only place in the codebase that uses Framer Motion.
Admin Components
Admin pages (src/components/admin/) follow a consistent pattern:
- Content component -- top-level component rendering the full admin page (e.g.,
AILogsDashboard,ResearchPlansContent) - KPI cards -- summary statistics at the top of the page
- Data tables -- sortable, filterable tables using the Table primitive
- Detail views -- single-entity detail pages with tabs and sub-tables
Admin components fetch data via tRPC hooks (trpc.routerName.endpoint.useQuery()) and display loading states with Skeleton components. Error states use a shared QueryError component.
The AI Playground (src/components/admin/ai-playground/) is the most complex admin feature, with its own sub-directory containing:
use-playground.ts/use-playground-settings.ts-- custom hooks for state managementplayground-response.tsx-- response display with markdown renderingthinking-section.tsx/tool-use-section.tsx/web-results.tsx-- content sectionssettings-fields.tsx/playground-settings.tsx-- configuration UItypes.ts-- shared type definitions
Brand Components
The TrovellaLogo component (src/components/brand/trovella-logo.tsx) renders the combination mark:
<TrovellaLogo size="md" showWordmark={true} />
Props:
size:"sm"(24px icon),"md"(32px icon),"lg"(40px icon)showWordmark: whether to show the "trovella" text next to the iconclassName: additional CSS classes
The LogoIcon sub-component renders the diamond SVG inline using currentColor, so it inherits the text color of its parent (typically text-primary).
Component Composition Guidelines
Naming Conventions
- File names: kebab-case matching the component name (
dashboard-top-bar.tsxexportsDashboardTopBar) - Component names: PascalCase, descriptive of function (
AppSidebar,ComingSoon,QueryError) - Feature grouping: components are grouped by feature domain into directories (
dashboard/,admin/,landing/,auth/,brand/,theme/,settings/) - No barrel exports: each component file is imported directly -- there are no
index.tsbarrel files in component directories (enforced by ESLint)
Server vs. Client Boundary
- Default to server components for data fetching and auth checks
- Mark
"use client"explicitly for any component that uses hooks, event handlers, or browser APIs - Push the client boundary down --
DashboardPageis a server component that delegates to client children
See Routing & Pages -- Component Boundaries for the full decision rationale.
Styling Conventions
- Always use
cn()for class composition -- never string concatenation for conditional classes - Semantic tokens only --
bg-primary,text-muted-foreground,border-border(neverbg-teal-700) - Responsive design uses Tailwind breakpoint prefixes (
sm:,md:,lg:) -- mobile-first - Spacing uses Tailwind's default scale (
p-4,gap-6,mt-8) -- no custom spacing tokens - Transitions use
transition-all duration-200ortransition-colors duration-150-- brief and subtle - Hover states use opacity modifiers (
hover:bg-primary/90) or semantic state changes (hover:text-foreground)
Action Card Pattern
The dashboard content uses an ActionCard pattern for onboarding-style cards. This pattern demonstrates the accent color system:
function ActionCard({ icon, title, description, href, accent }: ActionCardProps) {
const accentClasses =
accent === "gold" ? "bg-accent/10 text-accent" : "bg-primary/10 text-primary";
return (
<Link
href={href}
className="group flex flex-col rounded-lg border border-border bg-card p-5 ..."
>
<div
className={`mb-3 inline-flex size-10 items-center justify-center rounded-md ${accentClasses}`}
>
{icon}
</div>
<h3 className="font-serif text-base text-card-foreground">{title}</h3>
<p className="mt-1.5 flex-1 text-sm text-muted-foreground">{description}</p>
<div className="mt-3 flex items-center text-sm font-medium text-primary opacity-0 group-hover:opacity-100">
Get started <ArrowRight className="ml-1 size-3.5" />
</div>
</Link>
);
}
Key patterns:
- Icon on a tinted background (
bg-primary/10orbg-accent/10) - Group hover for progressive disclosure (the "Get started" arrow appears on hover)
- Display font for the title (
font-serif), body font for the description text-card-foregroundfor the title,text-muted-foregroundfor the description
Building a New Component
When creating a new feature component:
- Create the file in the appropriate feature directory (
src/components/{feature}/) - Add a TSDoc comment on the exported component explaining its purpose
- Mark
"use client"only if the component needs interactivity - Import UI primitives from
@/components/ui/-- do not recreate button, card, or dialog behavior - Use
cn()from@/lib/utilsfor all class composition - Follow the semantic token rules from Design Tokens
- Keep files under 250 lines (the project convention from CLAUDE.md)