Trovella Wiki

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:

  1. ensurePersonalOrganization() -- creates the user's personal org on first sign-in
  2. ensureActiveOrganization() -- 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:

GroupItems
NavigationDashboard, Discover, Create, Chat, Memory
AdminAI 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 pageNames lookup 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:

ComponentDescription
LandingPageOrchestrator -- composes all sections in order
LandingHeroHeadline, CTA buttons, persona cards, trust signal bar
LandingBentoFeature grid with visual showcase
LandingHowItWorksThree-step explanation flow
LandingUseCasesPersona-specific use case cards
LandingFeatureStripHorizontal feature highlights
LandingVideoVideo showcase section
LandingPricingPricing tier cards
LandingFaqAccordion FAQ section
LandingCtaFinal call-to-action section
LandingFooterFooter with links and brand
PersonaCardsAnimated 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:

  1. Content component -- top-level component rendering the full admin page (e.g., AILogsDashboard, ResearchPlansContent)
  2. KPI cards -- summary statistics at the top of the page
  3. Data tables -- sortable, filterable tables using the Table primitive
  4. 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 management
  • playground-response.tsx -- response display with markdown rendering
  • thinking-section.tsx / tool-use-section.tsx / web-results.tsx -- content sections
  • settings-fields.tsx / playground-settings.tsx -- configuration UI
  • types.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 icon
  • className: 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.tsx exports DashboardTopBar)
  • 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.ts barrel 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 -- DashboardPage is a server component that delegates to client children

See Routing & Pages -- Component Boundaries for the full decision rationale.

Styling Conventions

  1. Always use cn() for class composition -- never string concatenation for conditional classes
  2. Semantic tokens only -- bg-primary, text-muted-foreground, border-border (never bg-teal-700)
  3. Responsive design uses Tailwind breakpoint prefixes (sm:, md:, lg:) -- mobile-first
  4. Spacing uses Tailwind's default scale (p-4, gap-6, mt-8) -- no custom spacing tokens
  5. Transitions use transition-all duration-200 or transition-colors duration-150 -- brief and subtle
  6. 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/10 or bg-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-foreground for the title, text-muted-foreground for the description

Building a New Component

When creating a new feature component:

  1. Create the file in the appropriate feature directory (src/components/{feature}/)
  2. Add a TSDoc comment on the exported component explaining its purpose
  3. Mark "use client" only if the component needs interactivity
  4. Import UI primitives from @/components/ui/ -- do not recreate button, card, or dialog behavior
  5. Use cn() from @/lib/utils for all class composition
  6. Follow the semantic token rules from Design Tokens
  7. Keep files under 250 lines (the project convention from CLAUDE.md)

On this page