- 310 registry:skill items, 1528 files, 7 categories (hub tags) - registry.json generated by @kortix/registry buildRegistry - nested SKILL.md in sn-da-* renamed SUBSKILL.md (no phantom skills) - provenance + licenses: SOURCES.md
22 KiB
Complex Application Stack
Overview
The complex application stack is designed for sophisticated multi-view applications requiring advanced state management, complex navigation, and robust architecture. Use for SaaS platforms, enterprise tools, and feature-rich applications.
When to Use
- SaaS platforms
- Enterprise applications
- Multi-user systems
- Complex workflows with many views
- Applications requiring centralized state
- Tools with advanced features and interactions
Extends Default Stack
This stack includes ALL packages from default-webapp.md PLUS the following additive packages.
Additional Packages
State Management
pnpm add zustand # Lightweight, flexible state management
Date & Time Handling
pnpm add date-fns
Additional Utilities
pnpm add uuid # Generate unique IDs
pnpm add react-resizable-panels # Resizable layouts (often in shadcn)
Additional shadcn Components
# Navigation & Layout
pnpm dlx shadcn@latest add navigation-menu
pnpm dlx shadcn@latest add breadcrumb
pnpm dlx shadcn@latest add sidebar
pnpm dlx shadcn@latest add resizable
pnpm dlx shadcn@latest add sheet
# Data & Forms
pnpm dlx shadcn@latest add table
pnpm dlx shadcn@latest add select
pnpm dlx shadcn@latest add combobox
pnpm dlx shadcn@latest add command
pnpm dlx shadcn@latest add calendar
pnpm dlx shadcn@latest add popover
pnpm dlx shadcn@latest add dropdown-menu
# Feedback
pnpm dlx shadcn@latest add alert
pnpm dlx shadcn@latest add alert-dialog
pnpm dlx shadcn@latest add toast
pnpm dlx shadcn@latest add progress
pnpm dlx shadcn@latest add skeleton
Architecture Patterns
State Management with Zustand
Create stores for different domains:
// src/stores/useAuthStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
token: string | null;
login: (user: User, token: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,
login: (user, token) => set({ user, token }),
logout: () => set({ user: null, token: null }),
}),
{
name: 'auth-storage',
}
)
);
// src/stores/useAppStore.ts
import { create } from 'zustand';
interface AppState {
sidebarOpen: boolean;
currentView: string;
toggleSidebar: () => void;
setView: (view: string) => void;
}
export const useAppStore = create<AppState>((set) => ({
sidebarOpen: true,
currentView: 'dashboard',
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setView: (view) => set({ currentView: view }),
}));
Application Layout
// src/components/AppLayout.tsx
import { Outlet } from '@tanstack/react-router';
import { Sidebar } from './Sidebar';
import { Header } from './Header';
import { useAppStore } from '@/stores/useAppStore';
export function AppLayout() {
const sidebarOpen = useAppStore((state) => state.sidebarOpen);
return (
<div className="flex min-h-screen">
{/* Sidebar */}
{sidebarOpen && (
<aside className="w-64 border-r bg-card">
<Sidebar />
</aside>
)}
{/* Main content */}
<div className="flex-1">
<Header />
<main className="p-6">
<Outlet />
</main>
</div>
</div>
);
}
Layout Scrolling Patterns
Complex applications often need independent scrolling areas (sidebar, main content, modals). Getting scrolling right requires understanding flexbox constraints and when to use ScrollArea vs native overflow.
Core Principle: Flexbox Container Pattern
For scrolling to work in constrained spaces, follow this pattern:
<div className="flex flex-col h-screen"> {/* Define height constraint */}
<header className="flex-shrink-0">Fixed header</header>
<div className="flex-1 overflow-auto">Scrollable content</div>
<footer className="flex-shrink-0">Fixed footer</footer>
</div>
Key rules:
- Parent must have explicit height (
h-screen,h-full, orflex-1from its parent) - Fixed elements use
flex-shrink-0 - Scrollable element uses
flex-1to fill remaining space - Scrollable element needs
overflow-autoor be aScrollAreacomponent
Split-Pane Layout (Sidebar + Content)
Most complex apps use this pattern:
// Full-height split pane with independent scrolling
export function AppShell() {
return (
<div className="flex h-screen overflow-hidden">
{/* Left Sidebar */}
<div className="w-80 flex flex-col border-r bg-card">
<div className="p-4 border-b flex-shrink-0">
<h2 className="font-bold">Sidebar Header</h2>
</div>
<div className="flex-1 overflow-auto">
<nav className="p-4">
{/* Sidebar content */}
</nav>
</div>
</div>
{/* Right Content */}
<div className="flex-1 flex flex-col overflow-hidden">
<header className="p-6 border-b bg-card flex-shrink-0">
<h1 className="text-2xl font-bold">Page Header</h1>
</header>
<div className="flex-1 overflow-auto">
<main className="p-6">
{/* Main content that scrolls */}
</main>
</div>
</div>
</div>
);
}
Why this works:
- Root uses
h-screen overflow-hiddento constrain total height - Both panes use
flex flex-colto stack header + scrollable area - Headers use
flex-shrink-0to prevent compression - Scrollable areas use
flex-1 overflow-autoto fill remaining space
When to Use ScrollArea vs Native Overflow
Use native overflow-auto (recommended):
- Full-height scrolling areas (sidebars, main content)
- Simpler implementation, more reliable
- Better browser support and performance
- Mobile-friendly (respects system scroll behavior)
- Works consistently with flexbox layouts
Use shadcn ScrollArea (advanced):
- Small contained regions with custom scrollbar styling
- Modals/dialogs with fixed max-height
- When you need precise scrollbar customization
- Note: Can have edge cases with flex-1, prefer native overflow for full-height
{/* Full-height areas - use native overflow (recommended) */}
<div className="flex-1 overflow-auto">
<div className="p-4">Sidebar or main content</div>
</div>
{/* Small contained areas - ScrollArea acceptable */}
<ScrollArea className="h-[400px]">
<div className="p-4">Fixed height content</div>
</ScrollArea>
Common Mistakes to Avoid
❌ Don't use calc() for scroll heights:
// BAD: Brittle, breaks on content changes
<ScrollArea className="h-[calc(100vh-100px)]">
✅ Do use flexbox:
// GOOD: Flexible, adapts to any layout
<div className="flex-1 overflow-auto">
❌ Don't forget parent constraints:
// BAD: flex-1 without parent height does nothing
<div className="flex-1 overflow-auto">
✅ Do establish height on parent:
// GOOD: Parent has h-screen
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-auto">
❌ Don't skip overflow-hidden on parent:
// BAD: Can cause layout issues
<div className="flex-1 flex flex-col">
<div className="flex-1 overflow-auto">
✅ Do add overflow-hidden to parent:
// GOOD: Prevents flex-1 from growing beyond bounds
<div className="flex-1 flex flex-col overflow-hidden">
<div className="flex-1 overflow-auto">
Modal with Scrollable Content
<DialogContent className="max-h-[90vh] flex flex-col">
<DialogHeader className="flex-shrink-0">
<DialogTitle>Modal Title</DialogTitle>
</DialogHeader>
<ScrollArea className="flex-1 pr-4">
<div className="space-y-4">
{/* Long scrollable content */}
</div>
</ScrollArea>
<DialogFooter className="flex-shrink-0">
<Button>Close</Button>
</DialogFooter>
</DialogContent>
Debugging Scrolling Issues
If scrolling doesn't work:
- Check parent height: Use browser devtools to verify the scrollable element's parent has a constrained height
- Verify flex setup: Ensure
flex flex-colon parent,flex-1on scrollable child - Confirm overflow: Check if
overflow-autoor ScrollArea is applied - Look for overflow-hidden: Parent of scrollable might need
overflow-hiddento constrain growth
Quick diagnostic:
// Add temporary red border to see element bounds
<div className="flex-1 overflow-auto border-2 border-red-500">
If the red border doesn't have the height you expect, the problem is in the parent flex setup, not the scrolling itself.
Protected Routes
// src/routes/_authenticated.tsx
import { createFileRoute, redirect } from '@tanstack/react-router';
import { useAuthStore } from '@/stores/useAuthStore';
export const Route = createFileRoute('/_authenticated')({
beforeLoad: async () => {
const token = useAuthStore.getState().token;
if (!token) {
throw redirect({ to: '/login' });
}
},
component: AppLayout,
});
// src/routes/_authenticated/dashboard.tsx
export const Route = createFileRoute('/_authenticated/dashboard')({
component: Dashboard,
});
Navigation Patterns
Sidebar Navigation
import { Link } from '@tanstack/react-router';
import { Home, Users, Settings, FileText } from 'lucide-react';
import { cn } from '@/lib/utils';
const navigation = [
{ name: 'Dashboard', href: '/dashboard', icon: Home },
{ name: 'Users', href: '/users', icon: Users },
{ name: 'Documents', href: '/documents', icon: FileText },
{ name: 'Settings', href: '/settings', icon: Settings },
];
export function Sidebar() {
return (
<nav className="flex h-full flex-col p-4">
<div className="mb-8">
<h1 className="text-xl font-bold">App Name</h1>
</div>
<div className="space-y-1">
{navigation.map((item) => (
<Link
key={item.name}
to={item.href}
className={({ isActive }) =>
cn(
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors',
isActive
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)
}
>
<item.icon className="h-4 w-4" />
{item.name}
</Link>
))}
</div>
{/* User section at bottom */}
<div className="mt-auto border-t pt-4">
<UserDropdown />
</div>
</nav>
);
}
Hierarchical Sidebar Items
When displaying hierarchical data (org charts, nested navigation, file trees) in sidebars, use padding-based indentation to prevent text clipping.
✅ Correct Pattern:
interface HierarchyItemProps {
person: { name: string; title: string; initials: string };
level: number; // 0, 1, 2 for depth
isSelected: boolean;
}
function HierarchyItem({ person, level, isSelected }: HierarchyItemProps) {
const paddingLeft = level === 0 ? 'pl-3' : level === 1 ? 'pl-5' : 'pl-7';
return (
<button
className={cn(
'w-full flex items-center gap-2 py-3 pr-3 rounded-lg text-left',
paddingLeft, // Use padding, not margin
isSelected ? 'bg-primary text-primary-foreground' : 'hover:bg-accent'
)}
>
<Avatar className="h-9 w-9 flex-shrink-0">
<AvatarFallback>{person.initials}</AvatarFallback>
</Avatar>
{/* min-w-0 enables truncation to work */}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{person.name}</p>
<p className="text-xs text-muted-foreground truncate">{person.title}</p>
</div>
{isSelected && <ChevronRight className="h-4 w-4 flex-shrink-0" />}
</button>
);
}
Key Rules:
❌ Don't use margin for indentation:
<button className="ml-8 p-3"> {/* Reduces internal width, causes clipping */}
✅ Do use padding for indentation:
<button className="p-3 pl-7"> {/* Indentation stays inside button bounds */}
❌ Don't forget min-w-0 on text containers:
<div className="flex-1"> {/* truncate won't work */}
✅ Do use min-w-0 for proper truncation:
<div className="flex-1 min-w-0"> {/* Enables truncate */}
Sidebar Width Recommendations:
w-64(256px) - Basic flat navigationw-80(320px) - Standard sidebar with short labelsw-96(384px) - Hierarchical items with 2+ levels (recommended)
Quick test for clipping: Add border-2 border-red-500 to text container. If text overflows the red border, you're missing min-w-0 or using margin-based indentation.
Breadcrumbs
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { useMatches } from '@tanstack/react-router';
export function DynamicBreadcrumbs() {
const matches = useMatches();
return (
<Breadcrumb>
<BreadcrumbList>
{matches.map((match, index) => {
const isLast = index === matches.length - 1;
return (
<BreadcrumbItem key={match.pathname}>
{isLast ? (
<BreadcrumbPage>{match.pathname}</BreadcrumbPage>
) : (
<>
<BreadcrumbLink href={match.pathname}>
{match.pathname}
</BreadcrumbLink>
<BreadcrumbSeparator />
</>
)}
</BreadcrumbItem>
);
})}
</BreadcrumbList>
</Breadcrumb>
);
}
Command Palette (⌘K)
import { useEffect, useState } from 'react';
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { useNavigate } from '@tanstack/react-router';
export function CommandPalette() {
const [open, setOpen] = useState(false);
const navigate = useNavigate();
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener('keydown', down);
return () => document.removeEventListener('keydown', down);
}, []);
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Navigation">
<CommandItem
onSelect={() => {
navigate({ to: '/dashboard' });
setOpen(false);
}}
>
Dashboard
</CommandItem>
<CommandItem
onSelect={() => {
navigate({ to: '/users' });
setOpen(false);
}}
>
Users
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}
Data Management Patterns
Optimistic Updates
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface Task {
id: string;
title: string;
completed: boolean;
}
export function useToggleTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, completed }: { id: string; completed: boolean }) => {
return api.updateTask(id, { completed });
},
onMutate: async ({ id, completed }) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['tasks'] });
// Snapshot previous value
const previous = queryClient.getQueryData(['tasks']);
// Optimistically update
queryClient.setQueryData(['tasks'], (old: Task[]) =>
old.map((task) =>
task.id === id ? { ...task, completed } : task
)
);
return { previous };
},
onError: (_err, _variables, context) => {
// Rollback on error
queryClient.setQueryData(['tasks'], context?.previous);
},
onSettled: () => {
// Refetch after mutation
queryClient.invalidateQueries({ queryKey: ['tasks'] });
},
});
}
Pagination
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
export function UserList() {
const [page, setPage] = useState(1);
const { data, isLoading } = useQuery({
queryKey: ['users', page],
queryFn: () => fetchUsers(page),
keepPreviousData: true,
});
return (
<div>
{isLoading ? (
<LoadingSkeleton />
) : (
<UserTable users={data.users} />
)}
<div className="mt-4 flex items-center justify-between">
<Button
disabled={page === 1}
onClick={() => setPage((p) => p - 1)}
>
Previous
</Button>
<span>Page {page}</span>
<Button
disabled={!data.hasMore}
onClick={() => setPage((p) => p + 1)}
>
Next
</Button>
</div>
</div>
);
}
Complex Forms
Multi-Step Form
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
const step1Schema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
const step2Schema = z.object({
company: z.string().min(2),
role: z.string(),
});
export function MultiStepForm() {
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({});
const schema = step === 1 ? step1Schema : step2Schema;
const form = useForm({
resolver: zodResolver(schema),
});
const onSubmit = (data: any) => {
if (step === 1) {
setFormData({ ...formData, ...data });
setStep(2);
} else {
// Final submission
const finalData = { ...formData, ...data };
console.log(finalData);
}
};
const progress = (step / 2) * 100;
return (
<div className="max-w-md">
<Progress value={progress} className="mb-8" />
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{step === 1 && (
<>
{/* Step 1 fields */}
<Input {...form.register('name')} placeholder="Name" />
<Input {...form.register('email')} placeholder="Email" />
</>
)}
{step === 2 && (
<>
{/* Step 2 fields */}
<Input {...form.register('company')} placeholder="Company" />
<Input {...form.register('role')} placeholder="Role" />
</>
)}
<div className="flex justify-between">
{step > 1 && (
<Button type="button" variant="outline" onClick={() => setStep(step - 1)}>
Back
</Button>
)}
<Button type="submit">
{step === 2 ? 'Submit' : 'Next'}
</Button>
</div>
</form>
</div>
);
}
Performance Optimization
Code Splitting by Route
TanStack Router handles this automatically. Each route is lazy-loaded.
Virtual Scrolling for Large Lists
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
export function VirtualList({ items }: { items: any[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index].name}
</div>
))}
</div>
</div>
);
}
React Compiler
Enable for automatic memoization:
pnpm add -D babel-plugin-react-compiler
Configure in Vite:
// vite.config.ts
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
['babel-plugin-react-compiler', {}],
],
},
}),
],
});
Testing Strategy
Component Testing
import { render, screen } from '@testing-library/react';
import { Button } from './button';
test('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
E2E Testing (Playwright)
pnpm add -D @playwright/test
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
Security Considerations
- Validate all inputs with Zod
- Sanitize user-generated content
- Use HTTPS in production
- Implement CSRF protection
- Set secure cookie flags
- Use environment variables for secrets
- Implement rate limiting
- Regular dependency updates
Deployment
Environment Variables
# .env.production
VITE_API_URL=https://api.production.com
VITE_APP_URL=https://app.production.com
Access in code:
const API_URL = import.meta.env.VITE_API_URL;
Build Optimization
# Production build
pnpm build
# Analyze bundle size
pnpm add -D rollup-plugin-visualizer
Next Steps
- Define application architecture and feature domains
- Set up Zustand stores for global state
- Create protected route structure
- Implement authentication flow
- Build core navigation (sidebar, command palette)
- Develop feature modules
- Add comprehensive error handling
- Optimize performance (code splitting, caching)
- Implement testing strategy
- Deploy to production