feat: Enhance plugin store and experiment design infrastructure

- Add plugin store system with dynamic loading of robot actions
- Implement plugin store API routes and database schema
- Update experiment designer to support plugin-based actions
- Refactor environment configuration and sidebar navigation
- Improve authentication session handling with additional user details
- Update Tailwind CSS configuration and global styles
- Remove deprecated files and consolidate project structure
This commit is contained in:
2025-02-28 11:10:56 -05:00
parent 88c305de61
commit ab08c1b724
75 changed files with 7641 additions and 3382 deletions

55
docs/DESIGN_DECISIONS.md Normal file
View File

@@ -0,0 +1,55 @@
# HRIStudio Design Decisions Documentation
## Overview
This document captures the design decisions made during the implementation of HRIStudio. It covers various areas including UI improvements, plugin store architecture, data loading and error handling, styling conformance, and overall system architecture. This serves as a reference for developers and other stakeholders to understand the rationale behind our choices.
## 1. UI Design & User Experience
- **Conditional Rendering and User Feedback:**
- Implemented dynamic messages in components (e.g., PluginBrowser, RobotList) to notify users when repositories or plugins are missing.
- Clear call-to-action prompts guide users to add repositories/plugins when necessary.
- **Consistent Styling and Themes:**
- Merged multiple globals.css files to create a cohesive, unified theme across the application.
- Updated sidebar and top bar styling, including gradients, background colors, and hover effects to ensure visual consistency in both light and dark modes.
- **Component-Based Approach:**
- Developed reusable, functional components (e.g., StudyCard, StudyForm, DeleteStudyButton) with TypeScript interfaces for enhanced maintainability and readability.
## 2. Plugin Store Architecture
- **Repository and Plugin Loading:**
- Updated the plugin loading mechanism to fetch repository metadata from `repository.json` instead of `index.json`.
- Implemented a fall-back sequence for loading individual plugin files (prioritizing `robot-plugins/plugins/{filename}` over alternative locations).
- **Error Handling:**
- Introduced a custom `PluginLoadError` to encapsulate errors in plugin loading and provide clearer error messages.
- **Caching Strategy:**
- Incorporated cache management with a TTL (5 minutes) to reduce frequent metadata fetching.
- **Transform Functions:**
- Allowed registration and retrieval of transformation functions (e.g., `transformToTwist`, `transformToPoseStamped`) that convert plugin parameters into suitable payload formats.
## 3. Data Access & ORM Integration
- **Drizzle ORM:**
- Adopted Drizzle ORM for type-safe database operations, using a custom `createTable` utility to avoid table naming conflicts.
- Defined schemas for studies, participants, permissions, and plugin repositories with clear relationships.
- **Type-Safe Data Validation:**
- Used Zod schemas along with TypeScript interfaces to validate data structures, ensuring integrity in plugin metadata and user data.
## 4. Authentication & Permissions
- **NextAuth & Auth.js Integration:**
- Configured NextAuth with a credentials provider, including bcrypt-based password verification.
- Extended session callbacks to include custom user details like name and role.
- **Role-Based Access Control:**
- Established a clear role hierarchy (Owner, Admin, Principal Investigator, Wizard, Researcher, Observer) with distinct permissions defined in a role-permission matrix.
- Enforced visibility and access controls for sensitive information based on user roles.
## 5. Next.js Architecture and Component Providers
- **Next.js 15 App Router:**
- Leveraged the new App Router to prioritize server components over client components, reducing reliance on `useEffect` and client-side state for data fetching.
- **Global Providers:**
- Wrapped the application with multiple providers (ThemeProvider, PluginStoreProvider, StudyProvider) to supply context and maintain consistent application state.
## 6. Additional Design Considerations
- **Error Logging and Debugging:**
- Enhanced error logging throughout the plugin store and repository loading processes to aid in troubleshooting.
- **Naming Conventions and File Structure:**
- Maintained a clear naming convention for directories (lowercase with dashes) and reused descriptive interface names to ensure clarity and consistency.
## Conclusion
These design decisions were made to build a robust, scalable, and user-friendly platform for managing human-robot interaction studies. By emphasizing type safety, modularization, and thorough error handling, HRIStudio is well-equipped for both current needs and future enhancements. Future work may focus on extending experimental features and integrating advanced analytics tools.

29
docs/README.md Normal file
View File

@@ -0,0 +1,29 @@
# HRIStudio Documentation
Welcome to the HRIStudio documentation. This directory contains comprehensive documentation about the design, architecture, and implementation of HRIStudio.
## Documentation Structure
- [`architecture.md`](./architecture.md) - System architecture, tech stack, and core design decisions
- [`ui-design.md`](./ui-design.md) - UI/UX guidelines, component structure, and styling decisions
- [`plugin-store.md`](./plugin-store.md) - Plugin system architecture and implementation details
- [`auth-and-permissions.md`](./auth-and-permissions.md) - Authentication and role-based access control
- [`experiment-designer.md`](./experiment-designer.md) - Experiment designer implementation and flow control
- [`data-layer.md`](./data-layer.md) - Database schema, ORM usage, and data validation
- [`development.md`](./development.md) - Development guidelines, conventions, and best practices
- [`future-roadmap.md`](./future-roadmap.md) - Planned features and future enhancements
## Quick Start
1. Read through [`architecture.md`](./architecture.md) for a high-level overview
2. Review [`development.md`](./development.md) for development setup and guidelines
3. Explore specific topics in their dedicated files
## Contributing
When adding new features or making significant changes:
1. Update relevant documentation files
2. Follow the established format and style
3. Include code examples where appropriate
4. Update this README if adding new documentation files

249
docs/architecture.md Normal file
View File

@@ -0,0 +1,249 @@
# System Architecture
## Overview
HRIStudio is built on a modern tech stack centered around Next.js 15's App Router, emphasizing server-side rendering and type safety throughout the application. This document outlines the core architectural decisions and system design.
## Tech Stack
- **Framework:** Next.js 15 (App Router)
- **Language:** TypeScript
- **Database ORM:** Drizzle
- **Authentication:** NextAuth.js (Auth.js)
- **API Layer:** tRPC
- **UI Components:**
- Shadcn UI
- Radix UI
- Tailwind CSS
- **State Management:** React Context + Hooks
- **Form Handling:** React Hook Form + Zod
## Core Architecture Components
### Next.js App Router Structure
```
src/
├── app/ # Next.js 15 App Router pages
│ ├── api/ # API routes
│ ├── auth/ # Authentication pages
│ ├── dashboard/ # Dashboard and main application
│ └── layout.tsx # Root layout
├── components/ # Shared React components
├── lib/ # Utility functions and shared logic
├── server/ # Server-side code
│ ├── api/ # tRPC routers
│ ├── auth/ # Authentication configuration
│ └── db/ # Database schemas and utilities
└── styles/ # Global styles and Tailwind config
```
### Global Providers
The application is wrapped in several context providers that manage global state:
```typescript
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider>
<PluginStoreProvider>
<StudyProvider>
{children}
<Toaster />
</StudyProvider>
</PluginStoreProvider>
</ThemeProvider>
);
}
```
### Server Components vs Client Components
We prioritize Server Components for better performance and SEO:
- **Server Components (Default):**
- Data fetching
- Static content rendering
- Layout components
- Database operations
- **Client Components (Marked with "use client"):**
- Interactive UI elements
- Components requiring browser APIs
- Real-time updates
- Form handling
## API Layer
### tRPC Integration
Type-safe API routes are implemented using tRPC:
```typescript
export const appRouter = createTRPCRouter({
study: studyRouter,
participant: participantRouter,
experiment: experimentRouter,
});
```
Each router provides strongly-typed procedures:
```typescript
export const studyRouter = createTRPCRouter({
create: protectedProcedure
.input(studySchema)
.mutation(async ({ ctx, input }) => {
// Implementation
}),
// Other procedures...
});
```
### Error Handling
Centralized error handling through tRPC:
```typescript
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const session = await getServerAuthSession();
return {
session,
db,
// Additional context...
};
};
```
## Database Architecture
### Drizzle ORM Integration
Custom table creation utility to prevent naming conflicts:
```typescript
export const createTable = pgTableCreator((name) => `hs_${name}`);
```
Example schema definition:
```typescript
export const studies = createTable("study", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
title: varchar("title", { length: 256 }).notNull(),
description: text("description"),
// Additional fields...
});
```
### Relations
Explicit relation definitions using Drizzle's relations API:
```typescript
export const studiesRelations = relations(studies, ({ one, many }) => ({
creator: one(users, {
fields: [studies.createdById],
references: [users.id],
}),
members: many(studyMembers),
// Additional relations...
}));
```
## Performance Considerations
### Server-Side Rendering
- Leveraging Next.js App Router for optimal server-side rendering
- Minimizing client-side JavaScript
- Implementing proper caching strategies
### Data Fetching
- Using React Suspense for loading states
- Implementing stale-while-revalidate patterns
- Optimizing database queries
### Caching Strategy
- Browser-level caching for static assets
- Server-side caching for API responses
- Plugin store metadata caching (5-minute TTL)
## Security
### Authentication Flow
1. User credentials validation
2. Password hashing with bcrypt
3. Session management with NextAuth
4. Role-based access control
### Data Protection
- Input validation using Zod schemas
- SQL injection prevention through Drizzle ORM
- XSS prevention through proper React escaping
- CSRF protection via Auth.js tokens
## Monitoring and Debugging
### Error Tracking
- Custom error classes for specific scenarios
- Detailed error logging
- Error boundary implementation
### Performance Monitoring
- Web Vitals tracking
- Server-side metrics collection
- Client-side performance monitoring
## Development Workflow
### Type Safety
- Strict TypeScript configuration
- Zod schema validation
- tRPC for end-to-end type safety
### Code Organization
- Feature-based directory structure
- Clear separation of concerns
- Consistent naming conventions
### Testing Strategy
- Unit tests for utility functions
- Integration tests for API routes
- End-to-end tests for critical flows
## Deployment
### Environment Configuration
- Development environment
- Staging environment
- Production environment
### CI/CD Pipeline
- Automated testing
- Type checking
- Linting
- Build verification
## Conclusion
This architecture provides a solid foundation for HRIStudio, emphasizing:
- Type safety
- Performance
- Scalability
- Maintainability
- Security
Future architectural decisions should align with these principles while considering the evolving needs of the platform.

View File

@@ -0,0 +1,365 @@
# Authentication & Permissions System
## Overview
HRIStudio implements a robust authentication and role-based access control (RBAC) system using NextAuth.js (Auth.js) and a custom permissions framework. This system ensures secure access to resources and proper isolation of sensitive data.
## Authentication
### NextAuth Configuration
```typescript
export const authConfig = {
adapter: DrizzleAdapter(db),
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Credential validation logic
}
})
],
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
name: user.firstName && user.lastName
? `${user.firstName} ${user.lastName}`
: null,
firstName: user.firstName,
lastName: user.lastName,
},
}),
},
pages: {
signIn: '/auth/signin',
},
};
```
### Session Management
```typescript
interface Session {
user: {
id: string;
email: string;
firstName: string | null;
lastName: string | null;
} & DefaultSession["user"];
}
```
## Role-Based Access Control
### Role Hierarchy
1. **Owner**
- Single owner per study
- Full control over all aspects
- Can delete study or transfer ownership
- Can manage all other roles
2. **Admin**
- Multiple admins allowed
- Can manage participants and experiments
- Cannot delete study or transfer ownership
- Can invite and manage other users (except Owner)
3. **Principal Investigator (PI)**
- Scientific oversight role
- Full access to participant data
- Can manage experiment protocols
- Cannot modify core study settings
4. **Wizard**
- Operates robots during experiments
- Can control live experiment sessions
- Limited view of participant data
- Cannot modify study design
5. **Researcher**
- Can view and analyze data
- Access to anonymized information
- Cannot modify study or participant data
- Cannot run experiment trials
6. **Observer**
- Can view live experiments
- Access to anonymized data
- Can add annotations
- Cannot modify any study aspects
### Permission Categories
```typescript
export const PERMISSIONS = {
// Study Management
CREATE_STUDY: "create_study",
DELETE_STUDY: "delete_study",
EDIT_STUDY: "edit_study",
TRANSFER_OWNERSHIP: "transfer_ownership",
// Participant Management
VIEW_PARTICIPANTS: "view_participants",
ADD_PARTICIPANT: "add_participant",
EDIT_PARTICIPANT: "edit_participant",
DELETE_PARTICIPANT: "delete_participant",
VIEW_PARTICIPANT_NAMES: "view_participant_names",
// Experiment Management
CREATE_EXPERIMENT: "create_experiment",
EDIT_EXPERIMENT: "edit_experiment",
DELETE_EXPERIMENT: "delete_experiment",
RUN_EXPERIMENT: "run_experiment",
// Data Access
EXPORT_DATA: "export_data",
VIEW_ANALYTICS: "view_analytics",
// User Management
INVITE_USERS: "invite_users",
MANAGE_ROLES: "manage_roles",
} as const;
```
### Role-Permission Matrix
```typescript
export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
OWNER: Object.values(PERMISSIONS),
ADMIN: [
PERMISSIONS.EDIT_STUDY,
PERMISSIONS.VIEW_PARTICIPANTS,
PERMISSIONS.ADD_PARTICIPANT,
PERMISSIONS.EDIT_PARTICIPANT,
PERMISSIONS.DELETE_PARTICIPANT,
PERMISSIONS.VIEW_PARTICIPANT_NAMES,
PERMISSIONS.CREATE_EXPERIMENT,
PERMISSIONS.EDIT_EXPERIMENT,
PERMISSIONS.DELETE_EXPERIMENT,
PERMISSIONS.RUN_EXPERIMENT,
PERMISSIONS.EXPORT_DATA,
PERMISSIONS.VIEW_ANALYTICS,
PERMISSIONS.INVITE_USERS,
PERMISSIONS.MANAGE_ROLES,
],
PRINCIPAL_INVESTIGATOR: [
PERMISSIONS.VIEW_PARTICIPANTS,
PERMISSIONS.ADD_PARTICIPANT,
PERMISSIONS.EDIT_PARTICIPANT,
PERMISSIONS.VIEW_PARTICIPANT_NAMES,
PERMISSIONS.CREATE_EXPERIMENT,
PERMISSIONS.EDIT_EXPERIMENT,
PERMISSIONS.RUN_EXPERIMENT,
PERMISSIONS.EXPORT_DATA,
PERMISSIONS.VIEW_ANALYTICS,
],
// ... additional role permissions
};
```
## Implementation
### Permission Checking
```typescript
export async function checkPermissions({
studyId,
permission,
session,
}: {
studyId?: number;
permission: Permission;
session: Session | null;
}): Promise<void> {
if (!session?.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You must be logged in to perform this action",
});
}
// Anyone who is logged in can create a study
if (!studyId) {
if (permission === "CREATE_STUDY") {
return;
}
throw new TRPCError({
code: "BAD_REQUEST",
message: "Study ID is required for this action",
});
}
const membership = await db.query.studyMembers.findFirst({
where: and(
eq(studyMembers.studyId, studyId),
eq(studyMembers.userId, session.user.id),
),
});
if (!membership) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You do not have permission to perform this action",
});
}
const normalizedRole = membership.role.toUpperCase() as keyof typeof ROLE_PERMISSIONS;
const permittedActions = ROLE_PERMISSIONS[normalizedRole] ?? [];
if (normalizedRole === "OWNER") {
return;
}
if (!permittedActions.includes(permission)) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You do not have permission to perform this action",
});
}
}
```
### Database Schema
```typescript
export const studyMembers = createTable("study_member", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
studyId: integer("study_id")
.notNull()
.references(() => studies.id, { onDelete: "cascade" }),
userId: varchar("user_id", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
role: studyRoleEnum("role").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
```
## UI Integration
### Protected Routes
```typescript
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const { data: session, status } = useSession()
const router = useRouter()
useEffect(() => {
if (status === "unauthenticated") {
router.replace("/auth/signin")
}
}, [status, router])
if (!session) {
return null
}
return (
// Layout content
);
}
```
### Conditional Rendering
```typescript
export function ParticipantDetails({ participant, role }: Props) {
const canViewIdentifiableInfo = [
ROLES.OWNER,
ROLES.ADMIN,
ROLES.PRINCIPAL_INVESTIGATOR
].includes(role);
return (
<div>
{canViewIdentifiableInfo ? (
<div>
<h3>{participant.name}</h3>
<p>{participant.email}</p>
</div>
) : (
<div>
<h3>Participant {participant.id}</h3>
<p>[Redacted]</p>
</div>
)}
</div>
);
}
```
## Security Considerations
### Password Handling
- Passwords are hashed using bcrypt before storage
- Minimum password requirements enforced
- Rate limiting on authentication attempts
### Session Security
- CSRF protection enabled
- Secure session cookies
- Session expiration and renewal
### Data Access
- Row-level security through role checks
- Audit logging of sensitive operations
- Data encryption at rest
## Best Practices
1. **Permission Checking:**
- Always check permissions before sensitive operations
- Use the checkPermissions utility consistently
- Include proper error messages
2. **Role Assignment:**
- Validate role assignments
- Maintain role hierarchy
- Prevent privilege escalation
3. **UI Security:**
- Hide sensitive UI elements based on permissions
- Clear error messages without exposing internals
- Proper loading states during authentication
4. **Audit Trail:**
- Log authentication attempts
- Track permission changes
- Monitor sensitive data access
## Future Enhancements
1. **Advanced Authentication:**
- Multi-factor authentication
- OAuth provider integration
- SSO support
2. **Enhanced Permissions:**
- Custom role creation
- Temporary permissions
- Permission inheritance
3. **Audit System:**
- Detailed activity logging
- Security alerts
- Compliance reporting
4. **UI Improvements:**
- Role management interface
- Permission visualization
- Audit log viewer

304
docs/data-layer.md Normal file
View File

@@ -0,0 +1,304 @@
# Data Layer
## Overview
HRIStudio's data layer is built on PostgreSQL using Drizzle ORM for type-safe database operations. The system implements a comprehensive schema design that supports studies, experiments, participants, and plugin management while maintaining data integrity and proper relationships.
## Database Schema
### Core Tables
#### Studies
```typescript
export const studies = createTable("study", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
title: varchar("title", { length: 256 }).notNull(),
description: text("description"),
createdById: varchar("created_by", { length: 255 })
.notNull()
.references(() => users.id),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
export const studyMembers = createTable("study_member", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
studyId: integer("study_id")
.notNull()
.references(() => studies.id, { onDelete: "cascade" }),
userId: varchar("user_id", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
role: studyRoleEnum("role").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
```
#### Experiments
```typescript
export const experiments = createTable("experiment", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
studyId: integer("study_id")
.notNull()
.references(() => studies.id, { onDelete: "cascade" }),
title: varchar("title", { length: 256 }).notNull(),
description: text("description"),
version: integer("version").notNull().default(1),
status: experimentStatusEnum("status").notNull().default("draft"),
steps: jsonb("steps").$type<Step[]>().default([]),
createdById: varchar("created_by", { length: 255 })
.notNull()
.references(() => users.id),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
```
#### Participants
```typescript
export const participants = createTable("participant", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
studyId: integer("study_id")
.notNull()
.references(() => studies.id, { onDelete: "cascade" }),
identifier: varchar("identifier", { length: 256 }),
email: varchar("email", { length: 256 }),
firstName: varchar("first_name", { length: 256 }),
lastName: varchar("last_name", { length: 256 }),
notes: text("notes"),
status: participantStatusEnum("status").notNull().default("active"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
```
### Plugin Store Tables
```typescript
export const pluginRepositories = createTable("plugin_repository", {
id: varchar("id", { length: 255 }).primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
description: text("description"),
url: varchar("url", { length: 255 }).notNull(),
official: boolean("official").default(false).notNull(),
author: jsonb("author").notNull().$type<{
name: string;
email?: string;
url?: string;
organization?: string;
}>(),
maintainers: jsonb("maintainers").$type<Array<{
name: string;
email?: string;
url?: string;
}>>(),
compatibility: jsonb("compatibility").notNull().$type<{
hristudio: {
min: string;
recommended?: string;
};
ros2?: {
distributions: string[];
recommended?: string;
};
}>(),
stats: jsonb("stats").$type<{
downloads: number;
stars: number;
plugins: number;
}>(),
addedById: varchar("added_by", { length: 255 })
.references(() => users.id),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
```
## Relations
### Study Relations
```typescript
export const studiesRelations = relations(studies, ({ one, many }) => ({
creator: one(users, {
fields: [studies.createdById],
references: [users.id],
}),
members: many(studyMembers),
participants: many(participants),
experiments: many(experiments),
}));
export const studyMembersRelations = relations(studyMembers, ({ one }) => ({
study: one(studies, {
fields: [studyMembers.studyId],
references: [studies.id],
}),
user: one(users, {
fields: [studyMembers.userId],
references: [users.id],
}),
}));
```
## Data Validation
### Zod Schemas
```typescript
const studySchema = z.object({
title: z.string().min(1, "Title is required").max(256, "Title is too long"),
description: z.string().optional(),
});
const participantSchema = z.object({
identifier: z.string().optional(),
email: z.string().email().optional(),
firstName: z.string().optional(),
lastName: z.string().optional(),
notes: z.string().optional(),
status: z.enum(["active", "inactive", "completed", "withdrawn"]),
});
const experimentSchema = z.object({
title: z.string().min(1, "Title is required").max(256, "Title is too long"),
description: z.string().optional(),
steps: z.array(stepSchema),
status: z.enum(["draft", "active", "archived"]),
});
```
## Query Building
### Type-Safe Queries
```typescript
const study = await db.query.studies.findFirst({
where: eq(studies.id, studyId),
with: {
creator: true,
members: {
with: {
user: true,
},
},
participants: true,
},
});
```
### Mutations
```typescript
const newStudy = await db.transaction(async (tx) => {
const [study] = await tx.insert(studies).values({
title,
description,
createdById: userId,
}).returning();
await tx.insert(studyMembers).values({
studyId: study.id,
userId,
role: "OWNER",
});
return study;
});
```
## Error Handling
### Database Errors
```typescript
try {
await db.insert(participants).values({
studyId,
identifier,
email,
});
} catch (error) {
if (error instanceof PostgresError) {
if (error.code === "23505") { // Unique violation
throw new TRPCError({
code: "CONFLICT",
message: "A participant with this identifier already exists",
});
}
}
throw error;
}
```
## Migrations
### Migration Structure
```typescript
import { sql } from "drizzle-orm";
import { createTable, integer, text, timestamp, varchar } from "drizzle-orm/pg-core";
export async function up(db: Database) {
await db.schema.createTable("study")
.addColumn("id", "serial", (col) => col.primaryKey())
.addColumn("title", "varchar(256)", (col) => col.notNull())
.addColumn("description", "text")
.addColumn("created_by", "varchar(255)", (col) =>
col.notNull().references("user.id"))
.addColumn("created_at", "timestamp", (col) =>
col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`))
.addColumn("updated_at", "timestamp");
}
export async function down(db: Database) {
await db.schema.dropTable("study");
}
```
## Best Practices
1. **Type Safety:**
- Use Drizzle's type inference
- Define explicit types for complex queries
- Validate input with Zod schemas
2. **Performance:**
- Use appropriate indexes
- Optimize complex queries
- Implement caching where needed
3. **Data Integrity:**
- Use transactions for related operations
- Implement proper cascading
- Validate data before insertion
4. **Security:**
- Sanitize user input
- Implement proper access control
- Use parameterized queries
## Future Enhancements
1. **Query Optimization:**
- Query caching
- Materialized views
- Query analysis tools
2. **Data Management:**
- Data archiving
- Backup strategies
- Data export tools
3. **Schema Evolution:**
- Zero-downtime migrations
- Schema versioning
- Backward compatibility
4. **Monitoring:**
- Query performance metrics
- Error tracking
- Usage analytics

376
docs/development.md Normal file
View File

@@ -0,0 +1,376 @@
# Development Guidelines
## Overview
This document outlines the development practices, coding standards, and workflow guidelines for contributing to HRIStudio. Following these guidelines ensures consistency and maintainability across the codebase.
## Development Environment
### Prerequisites
- Node.js (v18+)
- PostgreSQL (v14+)
- Docker (for local development)
- VS Code (recommended)
### Setup
1. **Clone the Repository:**
```bash
git clone https://github.com/yourusername/hristudio.git
cd hristudio
```
2. **Install Dependencies:**
```bash
npm install
```
3. **Environment Configuration:**
```bash
cp .env.example .env
# Edit .env with your local settings
```
4. **Database Setup:**
```bash
npm run docker:up # Start PostgreSQL container
npm run db:push # Apply database schema
npm run db:seed # Seed initial data
```
5. **Start Development Server:**
```bash
npm run dev
```
## Code Organization
### Directory Structure
```
src/
├── app/ # Next.js pages and layouts
├── components/ # React components
│ ├── auth/ # Authentication components
│ ├── experiments/ # Experiment-related components
│ ├── layout/ # Layout components
│ ├── navigation/ # Navigation components
│ ├── studies/ # Study-related components
│ └── ui/ # Shared UI components
├── lib/ # Utility functions and shared logic
│ ├── experiments/ # Experiment-related utilities
│ ├── permissions/ # Permission checking utilities
│ └── plugin-store/ # Plugin store implementation
├── server/ # Server-side code
│ ├── api/ # tRPC routers
│ ├── auth/ # Authentication configuration
│ └── db/ # Database schemas and utilities
└── styles/ # Global styles and Tailwind config
```
### Naming Conventions
1. **Files and Directories:**
```typescript
// Components
components/auth/sign-in-form.tsx
components/studies/study-card.tsx
// Pages
app/dashboard/studies/[id]/page.tsx
app/dashboard/experiments/new/page.tsx
```
2. **Component Names:**
```typescript
// PascalCase for component names
export function SignInForm() { ... }
export function StudyCard() { ... }
```
3. **Variables and Functions:**
```typescript
// camelCase for variables and functions
const userSession = useSession();
function handleSubmit() { ... }
```
## Coding Standards
### TypeScript
1. **Type Definitions:**
```typescript
// Use interfaces for object definitions
interface StudyProps {
id: number;
title: string;
description?: string;
createdAt: Date;
}
// Use type for unions and intersections
type Status = "draft" | "active" | "archived";
```
2. **Type Safety:**
```typescript
// Use proper type annotations
function getStudy(id: number): Promise<Study> {
return db.query.studies.findFirst({
where: eq(studies.id, id),
});
}
```
### React Components
1. **Functional Components:**
```typescript
interface ButtonProps {
variant?: "default" | "outline" | "ghost";
children: React.ReactNode;
}
export function Button({ variant = "default", children }: ButtonProps) {
return (
<button className={cn(buttonVariants({ variant }))}>
{children}
</button>
);
}
```
2. **Hooks:**
```typescript
function useStudy(studyId: number) {
const { data, isLoading } = api.study.getById.useQuery({ id: studyId });
return {
study: data,
isLoading,
};
}
```
### Styling
1. **Tailwind CSS:**
```typescript
// Use Tailwind classes
<div className="flex items-center justify-between p-4 bg-card">
<h2 className="text-lg font-semibold">Title</h2>
<Button className="hover:bg-primary/90">Action</Button>
</div>
```
2. **CSS Variables:**
```css
:root {
--primary: 217 91% 60%;
--primary-foreground: 0 0% 100%;
}
.custom-element {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
```
## Testing
### Unit Tests
```typescript
describe("StudyCard", () => {
it("renders study information correctly", () => {
const study = {
id: 1,
title: "Test Study",
description: "Test Description",
};
render(<StudyCard study={study} />);
expect(screen.getByText("Test Study")).toBeInTheDocument();
expect(screen.getByText("Test Description")).toBeInTheDocument();
});
});
```
### Integration Tests
```typescript
describe("Study Creation", () => {
it("creates a new study", async () => {
const user = userEvent.setup();
render(<CreateStudyForm />);
await user.type(screen.getByLabelText("Title"), "New Study");
await user.click(screen.getByText("Create Study"));
expect(await screen.findByText("Study created successfully")).toBeInTheDocument();
});
});
```
## Error Handling
### API Errors
```typescript
try {
const result = await api.study.create.mutate(data);
toast({
title: "Success",
description: "Study created successfully",
});
} catch (error) {
toast({
title: "Error",
description: error.message,
variant: "destructive",
});
}
```
### Form Validation
```typescript
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
title: "",
description: "",
},
});
function onSubmit(data: FormData) {
try {
// Form submission logic
} catch (error) {
form.setError("root", {
type: "submit",
message: "Something went wrong",
});
}
}
```
## Performance Optimization
### React Optimization
1. **Memoization:**
```typescript
const MemoizedComponent = memo(function Component({ data }: Props) {
return <div>{data}</div>;
});
```
2. **Code Splitting:**
```typescript
const DynamicComponent = dynamic(() => import("./HeavyComponent"), {
loading: () => <Skeleton />,
});
```
### Database Optimization
1. **Efficient Queries:**
```typescript
// Use select to only fetch needed fields
const study = await db.query.studies.findFirst({
select: {
id: true,
title: true,
},
where: eq(studies.id, studyId),
});
```
2. **Batch Operations:**
```typescript
await db.transaction(async (tx) => {
await Promise.all(
participants.map(p =>
tx.insert(participants).values(p)
)
);
});
```
## Git Workflow
### Branching Strategy
1. `main` - Production-ready code
2. `develop` - Development branch
3. Feature branches: `feature/feature-name`
4. Bug fixes: `fix/bug-description`
### Commit Messages
```bash
# Format
<type>(<scope>): <description>
# Examples
feat(studies): add study creation workflow
fix(auth): resolve sign-in validation issue
docs(api): update API documentation
```
## Deployment
### Environment Configuration
```bash
# Development
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/hristudio"
NEXTAUTH_URL="http://localhost:3000"
# Production
DATABASE_URL="postgresql://user:pass@production-db/hristudio"
NEXTAUTH_URL="https://hristudio.com"
```
### Build Process
```bash
# Build application
npm run build
# Type check
npm run typecheck
# Run tests
npm run test
# Start production server
npm run start
```
## Best Practices
1. **Code Quality:**
- Write self-documenting code
- Add comments for complex logic
- Follow TypeScript best practices
2. **Security:**
- Validate all inputs
- Implement proper authentication
- Use HTTPS in production
3. **Performance:**
- Optimize bundle size
- Implement caching strategies
- Monitor performance metrics
4. **Maintenance:**
- Keep dependencies updated
- Document breaking changes
- Maintain test coverage

383
docs/experiment-designer.md Normal file
View File

@@ -0,0 +1,383 @@
# Experiment Designer
## Overview
The Experiment Designer is a core feature of HRIStudio that enables researchers to create and configure robot experiments using a visual, flow-based interface. It supports drag-and-drop functionality, real-time updates, and integration with the plugin system.
## Architecture
### Core Components
```typescript
interface ExperimentDesignerProps {
className?: string;
defaultSteps?: Step[];
onChange?: (steps: Step[]) => void;
readOnly?: boolean;
}
export function ExperimentDesigner({
className,
defaultSteps = [],
onChange,
readOnly = false,
}: ExperimentDesignerProps) {
// Implementation
}
```
### Data Types
```typescript
export type ActionType =
| "move" // Robot movement
| "speak" // Robot speech
| "wait" // Wait for a duration
| "input" // Wait for user input
| "gesture" // Robot gesture
| "record" // Start/stop recording
| "condition" // Conditional branching
| "loop"; // Repeat actions
export interface Action {
id: string;
type: ActionType;
parameters: Record<string, any>;
order: number;
}
export interface Step {
id: string;
title: string;
description?: string;
actions: Action[];
order: number;
}
export interface Experiment {
id: number;
studyId: number;
title: string;
description?: string;
version: number;
status: "draft" | "active" | "archived";
steps: Step[];
createdAt: Date;
updatedAt: Date;
}
```
## Visual Components
### Action Node
```typescript
interface ActionNodeData {
type: string;
parameters: Record<string, any>;
onChange?: (parameters: Record<string, any>) => void;
}
export const ActionNode = memo(({ data, selected }: NodeProps<ActionNodeData>) => {
const [configOpen, setConfigOpen] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const actionConfig = AVAILABLE_ACTIONS.find((a) => a.type === data.type);
return (
<motion.div
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.2 }}
className={cn(
"relative",
"before:absolute before:inset-[-2px] before:rounded-xl before:bg-gradient-to-br",
selected && "before:from-primary/50 before:to-primary/20"
)}
>
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-gradient-to-br">
{actionConfig?.icon}
</div>
<CardTitle>{actionConfig?.title}</CardTitle>
</div>
</CardHeader>
<CardContent>
<CardDescription>{actionConfig?.description}</CardDescription>
</CardContent>
</Card>
</motion.div>
);
});
```
### Flow Edge
```typescript
export function FlowEdge({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
style = {},
markerEnd,
}: EdgeProps) {
const [edgePath] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
});
return (
<>
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
<motion.path
id={id}
style={{
strokeWidth: 3,
fill: "none",
stroke: "hsl(var(--primary))",
strokeDasharray: "5,5",
opacity: 0.5,
}}
d={edgePath}
animate={{
strokeDashoffset: [0, -10],
}}
transition={{
duration: 1,
repeat: Infinity,
ease: "linear",
}}
/>
</>
);
}
```
## Action Configuration
### Available Actions
```typescript
export const AVAILABLE_ACTIONS: ActionConfig[] = [
{
type: "move",
title: "Move Robot",
description: "Move the robot to a specific position",
icon: <Move className="h-4 w-4" />,
defaultParameters: {
position: { x: 0, y: 0, z: 0 },
speed: 1,
easing: "linear",
},
},
{
type: "speak",
title: "Robot Speech",
description: "Make the robot say something",
icon: <MessageSquare className="h-4 w-4" />,
defaultParameters: {
text: "",
speed: 1,
pitch: 1,
volume: 1,
},
},
// Additional actions...
];
```
### Parameter Configuration Dialog
```typescript
interface ActionConfigDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
type: ActionType;
parameters: Record<string, any>;
onSubmit: (parameters: Record<string, any>) => void;
}
export function ActionConfigDialog({
open,
onOpenChange,
type,
parameters,
onSubmit,
}: ActionConfigDialogProps) {
const actionConfig = AVAILABLE_ACTIONS.find(a => a.type === type);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Configure {actionConfig?.title}</DialogTitle>
<DialogDescription>
{actionConfig?.description}
</DialogDescription>
</DialogHeader>
<Form>
{/* Parameter fields */}
</Form>
</DialogContent>
</Dialog>
);
}
```
## Database Schema
```typescript
export const experiments = createTable("experiment", {
id: integer("id").primaryKey().notNull().generatedAlwaysAsIdentity(),
studyId: integer("study_id")
.notNull()
.references(() => studies.id, { onDelete: "cascade" }),
title: varchar("title", { length: 256 }).notNull(),
description: text("description"),
version: integer("version").notNull().default(1),
status: experimentStatusEnum("status").notNull().default("draft"),
steps: jsonb("steps").$type<Step[]>().default([]),
createdById: varchar("created_by", { length: 255 })
.notNull()
.references(() => users.id),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
```
## Integration with Plugin System
### Action Transformation
```typescript
interface ActionTransform {
type: "direct" | "transform";
transformFn?: string;
map?: Record<string, string>;
}
function transformActionParameters(
parameters: Record<string, any>,
transform: ActionTransform
): unknown {
if (transform.type === "direct") {
return parameters;
}
const transformFn = getTransformFunction(transform.transformFn!);
return transformFn(parameters);
}
```
### Plugin Action Integration
```typescript
function getAvailableActions(plugin: RobotPlugin): ActionConfig[] {
return plugin.actions.map(action => ({
type: action.type,
title: action.title,
description: action.description,
icon: getActionIcon(action.type),
defaultParameters: getDefaultParameters(action.parameters),
transform: action.ros2?.payloadMapping,
}));
}
```
## User Interface Features
### Drag and Drop
```typescript
function onDragStart(event: DragEvent, nodeType: string) {
event.dataTransfer.setData("application/reactflow", nodeType);
event.dataTransfer.effectAllowed = "move";
}
function onDrop(event: DragEvent) {
event.preventDefault();
const type = event.dataTransfer.getData("application/reactflow");
const position = project({
x: event.clientX,
y: event.clientY,
});
const newNode = {
id: getId(),
type,
position,
data: { label: `${type} node` },
};
setNodes((nds) => nds.concat(newNode));
}
```
### Step Organization
```typescript
function reorderSteps(steps: Step[], sourceIndex: number, targetIndex: number): Step[] {
const result = Array.from(steps);
const [removed] = result.splice(sourceIndex, 1);
result.splice(targetIndex, 0, removed);
return result.map((step, index) => ({
...step,
order: index,
}));
}
```
## Best Practices
1. **Performance:**
- Use React.memo for expensive components
- Implement virtualization for large flows
- Optimize drag and drop operations
2. **User Experience:**
- Provide clear visual feedback
- Implement undo/redo functionality
- Show validation errors inline
3. **Data Management:**
- Validate experiment data
- Implement auto-save
- Version control experiments
4. **Error Handling:**
- Validate action parameters
- Handle plugin loading errors
- Provide clear error messages
## Future Enhancements
1. **Advanced Flow Control:**
- Conditional branching
- Parallel execution
- Loop constructs
2. **Visual Improvements:**
- Custom node themes
- Animation preview
- Mini-map navigation
3. **Collaboration:**
- Real-time collaboration
- Comment system
- Version history
4. **Analysis Tools:**
- Flow validation
- Performance analysis
- Debug tools

306
docs/future-roadmap.md Normal file
View File

@@ -0,0 +1,306 @@
# Future Roadmap
## Overview
This document outlines the planned features, improvements, and future direction for HRIStudio. The roadmap is organized by priority and expected timeline.
## Q2 2024
### 1. Enhanced Plugin System
- **Plugin Marketplace**
- Community plugin submissions
- Plugin ratings and reviews
- Download statistics
- Version management
- **Advanced Plugin Features**
- Real-time plugin updates
- Plugin dependency management
- Custom action visualization
- Plugin testing framework
- **Plugin Development Tools**
- Plugin scaffolding CLI
- Development documentation
- Plugin validation tools
- Local testing environment
### 2. Experiment Designer Improvements
- **Advanced Flow Control**
- Conditional branching
- Parallel execution paths
- Loop constructs
- Event-based triggers
- **Visual Enhancements**
- Custom node themes
- Animation preview
- Mini-map navigation
- Grid snapping
- **Collaboration Features**
- Real-time collaboration
- Comment system
- Version history
- Experiment templates
## Q3 2024
### 1. Data Analysis Tools
- **Analytics Dashboard**
- Experiment metrics
- Participant statistics
- Performance analytics
- Custom reports
- **Data Visualization**
- Interactive charts
- Timeline views
- Heat maps
- Export capabilities
- **Machine Learning Integration**
- Pattern recognition
- Behavior analysis
- Predictive modeling
- Anomaly detection
### 2. Real-time Monitoring
- **Live Experiment Tracking**
- Real-time status updates
- Video streaming
- Sensor data visualization
- Remote control capabilities
- **Performance Monitoring**
- System metrics
- Robot status
- Network health
- Resource usage
- **Alert System**
- Custom alert rules
- Notification preferences
- Incident reporting
- Alert history
## Q4 2024
### 1. Advanced Authentication
- **Multi-factor Authentication**
- SMS verification
- Authenticator apps
- Hardware key support
- Biometric authentication
- **Single Sign-On**
- SAML integration
- OAuth providers
- Active Directory
- Custom IdP support
- **Enhanced Security**
- Audit logging
- Session management
- IP restrictions
- Rate limiting
### 2. Collaboration Tools
- **Team Management**
- Team hierarchies
- Resource sharing
- Permission inheritance
- Team analytics
- **Communication Features**
- In-app messaging
- Discussion boards
- File sharing
- Notification system
- **Knowledge Base**
- Documentation
- Best practices
- Troubleshooting guides
- Community forums
## 2025 and Beyond
### 1. AI Integration
- **Intelligent Assistance**
- Experiment suggestions
- Optimization recommendations
- Automated analysis
- Natural language processing
- **Predictive Features**
- Resource forecasting
- Behavior prediction
- Risk assessment
- Performance optimization
- **Automated Testing**
- Test case generation
- Regression testing
- Load testing
- Security scanning
### 2. Extended Platform Support
- **Mobile Applications**
- iOS app
- Android app
- Responsive web
- Cross-platform sync
- **Additional Robot Platforms**
- ROS1 support
- Custom protocols
- Hardware abstraction
- Simulator integration
- **Cloud Integration**
- Multi-cloud support
- Edge computing
- Data replication
- Disaster recovery
## Technical Improvements
### 1. Performance Optimization
- **Frontend**
- Bundle optimization
- Code splitting
- Lazy loading
- Service workers
- **Backend**
- Query optimization
- Caching strategies
- Load balancing
- Database sharding
- **Infrastructure**
- Container orchestration
- Auto-scaling
- CDN integration
- Geographic distribution
### 2. Developer Experience
- **Development Tools**
- CLI improvements
- Debug tools
- Testing utilities
- Documentation generator
- **Code Quality**
- Automated testing
- Code coverage
- Static analysis
- Performance profiling
- **Deployment**
- CI/CD enhancements
- Environment management
- Monitoring tools
- Rollback capabilities
## Research Integration
### 1. Academic Features
- **Publication Support**
- Data export
- Citation generation
- Figure creation
- Statistical analysis
- **Study Management**
- IRB integration
- Consent management
- Protocol tracking
- Data anonymization
- **Collaboration Tools**
- Institution management
- Grant tracking
- Resource sharing
- Publication tracking
### 2. Industry Integration
- **Enterprise Features**
- SLA management
- Compliance reporting
- Asset tracking
- Cost analysis
- **Integration Options**
- API expansion
- Custom connectors
- Data pipeline
- Workflow automation
- **Support Services**
- Training programs
- Technical support
- Consulting services
- Custom development
## Timeline and Milestones
### Q2 2024
- Plugin marketplace beta release
- Advanced experiment designer features
- Initial analytics dashboard
### Q3 2024
- Real-time monitoring system
- Data analysis tools
- Machine learning integration beta
### Q4 2024
- Multi-factor authentication
- Team collaboration tools
- Knowledge base launch
### 2025
- AI assistant beta
- Mobile applications
- Extended platform support
- Research integration features
## Success Metrics
1. **User Engagement**
- Active users
- Feature adoption
- User satisfaction
- Time spent in platform
2. **Platform Growth**
- Number of studies
- Plugin ecosystem
- API usage
- Community growth
3. **Technical Performance**
- System uptime
- Response times
- Error rates
- Resource utilization
4. **Research Impact**
- Published studies
- Citations
- Collaborations
- Grant success

119
docs/plan.md Normal file
View File

@@ -0,0 +1,119 @@
# HRIStudio Development Plan
## Immediate Goal: Paper Submission (1 Month)
Focus on delivering a functional experiment designer that demonstrates the platform's capabilities for Wizard-of-Oz HRI studies.
### 1. Experiment Designer Core
- [x] Basic flow-based designer UI
- [ ] Step containers with drag-and-drop, that can contain sets of actions
- [ ] Action node system
- [ ] Action schema definition
- [ ] Visual node editor
- [ ] Connection validation
- [ ] Parameter configuration UI
### 2. Plugin System
- [x] Plugin store infrastructure
- [x] Basic plugin loading mechanism
- [ ] Action Libraries
- [ ] Wizard Actions
- [ ] Robot movement control
- [ ] Speech synthesis
- [ ] Gesture control
- [ ] TurtleBot3 Integration
- [ ] ROS2 message types
- [ ] Movement actions
- [ ] Sensor feedback
- [ ] Experiment Flow
- [ ] Timing controls
- [ ] Wait conditions
- [ ] Participant input handling
- [ ] Data recording triggers
### 3. Execution Engine
- [ ] Step execution pipeline
- [ ] Action validation
- [ ] Real-time monitoring
- [ ] Data collection
- [ ] Action logs
- [ ] Timing data
- [ ] Participant responses
## Future Extensions
### 1. Enhanced Plugin Ecosystem
- Community plugin repository
- Plugin versioning and compatibility
- Custom action development tools
### 2. Advanced Experiment Features
- Conditional branching
- Dynamic parameter adjustment
- Multi-robot coordination
- Real-time visualization
### 3. Data Analysis Tools
- Session replay
- Data export
- Analysis templates
- Visualization tools
## Technical Requirements
### Action Schema
```typescript
interface ActionDefinition {
actionId: string;
type: ActionType;
title: string;
description: string;
parameters: {
type: "object";
properties: Record<string, {
type: string;
title: string;
description?: string;
default?: any;
minimum?: number;
maximum?: number;
enum?: string[];
unit?: string;
}>;
required: string[];
};
ros2?: {
messageType: string;
topic?: string;
service?: string;
action?: string;
payloadMapping: {
type: "direct" | "transform";
map?: Record<string, string>;
transformFn?: string;
};
qos?: {
reliability: "reliable" | "best_effort";
durability: "volatile" | "transient_local";
history: "keep_last" | "keep_all";
depth?: number;
};
};
}
```
### Plugin Structure
```
plugin-name/
├── plugin.json # Plugin metadata and action definitions
├── transforms.ts # Custom transform functions
├── validators.ts # Parameter validation
└── assets/ # Icons and documentation
```
## Implementation Priority
1. Core action system and visual editor
2. Basic wizard actions (movement, speech)
3. TurtleBot3 integration
4. Flow control actions
5. Data collection
6. Analysis tools

View File

@@ -1,403 +1,371 @@
# Robot Plugin Store Architecture
The Robot Plugin Store is a central system for managing robot definitions and their associated actions. It enables contributors to add new robotics platforms and actions, which can then be used in the experiment designer and ultimately bridge to ROS2 or other robotics middleware.
# Plugin Store System
## Overview
The plugin store consists of:
- A JSON-based plugin format for defining robots and their actions
- A loader system for managing and serving these plugins
- An admin interface for managing plugins
- Integration with the experiment designer
- A bridge to ROS2 for executing actions
The Plugin Store is a core feature of HRIStudio that manages robot plugins, their repositories, and their integration into the platform. It provides a robust system for loading, validating, and utilizing robot plugins within experiments.
## Plugin Schema
## Architecture
### Robot Plugin Definition
### Core Components
```typescript
export class PluginStore {
private plugins: Map<string, RobotPlugin> = new Map();
private repositories: Map<string, RepositoryMetadata> = new Map();
private transformFunctions: Map<string, Function> = new Map();
private pluginToRepo: Map<string, string> = new Map();
private lastRefresh: Map<string, number> = new Map();
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
}
```
### Plugin Types
```typescript
interface RobotPlugin {
// Core metadata
robotId: string; // Unique identifier for this robot
name: string; // Display name
description?: string; // Optional description
platform: string; // e.g., "ROS2", "custom"
version: string; // Semver version number
// Manufacturer information
robotId: string;
name: string;
description: string;
platform: string;
version: string;
manufacturer: {
name: string; // Manufacturer name
website: string; // Manufacturer website
support?: string; // Support URL
name: string;
website?: string;
support?: string;
};
// Documentation
documentation: {
mainUrl: string; // Main documentation URL
apiReference?: string; // API/ROS2 interface documentation
wikiUrl?: string; // Wiki or community documentation
videoUrl?: string; // Video tutorial or overview
mainUrl: string;
apiReference?: string;
wikiUrl?: string;
videoUrl?: string;
};
// Visual assets
assets: {
thumbnailUrl: string; // Small preview image
images: { // Various robot images
main: string; // Main robot image
angles?: { // Optional different view angles
thumbnailUrl: string;
images: {
main: string;
angles?: {
front?: string;
side?: string;
top?: string;
};
dimensions?: string; // Technical drawing with dimensions
dimensions?: string;
};
model?: { // 3D model information
format: "URDF" | "glTF" | "other";
model?: {
format: string;
url: string;
};
};
// Technical specifications
specs: {
dimensions: {
length: number; // in meters
length: number;
width: number;
height: number;
weight: number; // in kg
weight: number;
};
capabilities: string[]; // e.g., ["differential_drive", "lidar", "camera"]
maxSpeed: number; // in m/s
batteryLife: number; // in hours
payload?: number; // max payload in kg
capabilities: string[];
maxSpeed: number;
batteryLife: number;
};
// Available actions for this robot
actions: ActionDefinition[];
}
```
## Repository Management
### Loading Repositories
```typescript
async loadRepository(url: string): Promise<RepositoryMetadata> {
// Clean URL
const cleanUrl = url.trim().replace(/\/$/, "");
// Platform-specific configuration
ros2Config: {
namespace: string;
nodePrefix: string;
defaultTopics: {
cmd_vel: string;
odom: string;
scan: string;
[key: string]: string;
try {
// Fetch repository metadata
const metadataUrl = this.getRepositoryFileUrl(cleanUrl, "repository.json");
const response = await fetch(metadataUrl);
if (!response.ok) {
throw new Error(`Failed to fetch repository metadata: ${response.statusText}`);
}
const metadata = await response.json();
// Validate and process metadata
return metadata;
} catch (error) {
throw new PluginLoadError("Failed to load repository", undefined, error);
}
}
```
### Repository Metadata
```typescript
interface RepositoryMetadata {
id: string;
name: string;
description: string;
url: string;
official: boolean;
author: {
name: string;
email?: string;
url?: string;
organization?: string;
};
maintainers?: Array<{
name: string;
url?: string;
}>;
compatibility: {
hristudio: {
min: string;
recommended?: string;
};
ros2?: {
distributions: string[];
recommended?: string;
};
};
stats: {
downloads: number;
stars: number;
plugins: number;
};
}
```
## Plugin Loading & Validation
### Loading Process
1. **Repository Metadata:**
```typescript
private async loadRepositoryPlugins(repository: RepositoryMetadata) {
const metadataUrl = this.getRepositoryFileUrl(
repository.url,
"repository.json"
);
// Fetch and validate metadata
}
```
2. **Individual Plugins:**
```typescript
async loadPluginFromJson(jsonString: string): Promise<RobotPlugin> {
try {
const data = JSON.parse(jsonString);
return await this.validatePlugin(data);
} catch (error) {
throw new PluginLoadError(
"Failed to parse plugin JSON",
undefined,
error
);
}
}
```
### Validation
```typescript
private async validatePlugin(data: unknown): Promise<RobotPlugin> {
try {
return robotPluginSchema.parse(data);
} catch (error) {
if (error instanceof z.ZodError) {
throw new PluginLoadError(
`Invalid plugin format: ${error.errors.map(e => e.message).join(", ")}`,
undefined,
error
);
}
throw error;
}
}
```
## Action System
### Action Types
```typescript
type ActionType =
| "move" // Robot movement
| "speak" // Robot speech
| "wait" // Wait for a duration
| "input" // Wait for user input
| "gesture" // Robot gesture
| "record" // Start/stop recording
| "condition" // Conditional branching
| "loop"; // Repeat actions
```
### Action Definition
```typescript
interface ActionDefinition {
actionId: string; // Unique identifier for this action
type: ActionType; // Type of action (move, speak, etc.)
title: string; // Display name
description: string; // Description of what the action does
icon?: string; // Icon identifier for the UI
// Parameter definition (using JSON Schema)
actionId: string;
type: ActionType;
title: string;
description: string;
parameters: {
type: "object";
properties: Record<string, {
type: string;
title: string;
description?: string;
default?: any;
minimum?: number;
maximum?: number;
enum?: string[];
unit?: string; // e.g., "m/s", "rad", "m"
}>;
properties: Record<string, ParameterProperty>;
required: string[];
};
// ROS2 Integration details
ros2: {
messageType: string; // ROS message type
topic?: string; // ROS topic to publish to
service?: string; // ROS service to call
action?: string; // ROS action to execute
payloadMapping: { // How parameters map to ROS messages
ros2?: {
messageType: string;
topic?: string;
service?: string;
action?: string;
payloadMapping: {
type: "direct" | "transform";
map?: Record<string, string>;
transformFn?: string; // Name of transform function if type="transform"
};
qos?: { // Quality of Service settings
reliability: "reliable" | "best_effort";
durability: "volatile" | "transient_local";
history: "keep_last" | "keep_all";
depth?: number;
transformFn?: string;
};
qos?: QoSSettings;
};
}
```
### Example Plugin JSON
### Transform Functions
```json
{
"robotId": "turtlebot3-burger",
"name": "TurtleBot3 Burger",
"description": "A compact, affordable, programmable, ROS2-based mobile robot for education and research",
"platform": "ROS2",
"version": "2.0.0",
```typescript
private transformToTwist(params: { linear: number; angular: number }) {
return {
linear: {
x: params.linear,
y: 0.0,
z: 0.0
},
angular: {
x: 0.0,
y: 0.0,
z: params.angular
}
};
}
```
## Caching & Performance
### Cache Implementation
```typescript
private shouldRefreshCache(repositoryId: string): boolean {
const lastRefresh = this.lastRefresh.get(repositoryId);
if (!lastRefresh) return true;
"manufacturer": {
"name": "ROBOTIS",
"website": "https://www.robotis.com/",
"support": "https://emanual.robotis.com/docs/en/platform/turtlebot3/overview/"
},
"documentation": {
"mainUrl": "https://emanual.robotis.com/docs/en/platform/turtlebot3/overview/",
"apiReference": "https://emanual.robotis.com/docs/en/platform/turtlebot3/ros2_manipulation/",
"wikiUrl": "https://wiki.ros.org/turtlebot3",
"videoUrl": "https://www.youtube.com/watch?v=rVM994ZhsEM"
},
"assets": {
"thumbnailUrl": "/robots/turtlebot3-burger-thumb.png",
"images": {
"main": "/robots/turtlebot3-burger-main.png",
"angles": {
"front": "/robots/turtlebot3-burger-front.png",
"side": "/robots/turtlebot3-burger-side.png",
"top": "/robots/turtlebot3-burger-top.png"
},
"dimensions": "/robots/turtlebot3-burger-dimensions.png"
},
"model": {
"format": "URDF",
"url": "https://raw.githubusercontent.com/ROBOTIS-GIT/turtlebot3/master/turtlebot3_description/urdf/turtlebot3_burger.urdf"
}
},
"specs": {
"dimensions": {
"length": 0.138,
"width": 0.178,
"height": 0.192,
"weight": 1.0
},
"capabilities": [
"differential_drive",
"lidar",
"imu",
"odometry"
],
"maxSpeed": 0.22,
"batteryLife": 2.5
},
"ros2Config": {
"namespace": "turtlebot3",
"nodePrefix": "hri_studio",
"defaultTopics": {
"cmd_vel": "/cmd_vel",
"odom": "/odom",
"scan": "/scan",
"imu": "/imu",
"joint_states": "/joint_states"
}
},
"actions": [
{
"actionId": "move-velocity",
"type": "move",
"title": "Set Velocity",
"description": "Control the robot's linear and angular velocity",
"icon": "navigation",
"parameters": {
"type": "object",
"properties": {
"linear": {
"type": "number",
"title": "Linear Velocity",
"description": "Forward/backward velocity",
"default": 0,
"minimum": -0.22,
"maximum": 0.22,
"unit": "m/s"
},
"angular": {
"type": "number",
"title": "Angular Velocity",
"description": "Rotational velocity",
"default": 0,
"minimum": -2.84,
"maximum": 2.84,
"unit": "rad/s"
}
},
"required": ["linear", "angular"]
},
"ros2": {
"messageType": "geometry_msgs/msg/Twist",
"topic": "/cmd_vel",
"payloadMapping": {
"type": "transform",
"transformFn": "transformToTwist"
},
"qos": {
"reliability": "reliable",
"durability": "volatile",
"history": "keep_last",
"depth": 1
}
}
},
{
"actionId": "move-to-pose",
"type": "move",
"title": "Move to Position",
"description": "Navigate to a specific position on the map",
"icon": "target",
"parameters": {
"type": "object",
"properties": {
"x": {
"type": "number",
"title": "X Position",
"description": "X coordinate in meters",
"default": 0,
"unit": "m"
},
"y": {
"type": "number",
"title": "Y Position",
"description": "Y coordinate in meters",
"default": 0,
"unit": "m"
},
"theta": {
"type": "number",
"title": "Orientation",
"description": "Final orientation",
"default": 0,
"unit": "rad"
}
},
"required": ["x", "y", "theta"]
},
"ros2": {
"messageType": "geometry_msgs/msg/PoseStamped",
"action": "/navigate_to_pose",
"payloadMapping": {
"type": "transform",
"transformFn": "transformToPoseStamped"
}
}
}
]
const now = Date.now();
return now - lastRefresh > this.CACHE_TTL;
}
```
## Implementation Plan
### 1. Plugin Store Module
Create a TypeScript module to manage plugins:
### Error Handling
```typescript
// src/lib/plugin-store/types.ts
export interface RobotPlugin { ... }
export interface ActionDefinition { ... }
// src/lib/plugin-store/store.ts
export class PluginStore {
private plugins: Map<string, RobotPlugin>;
async loadPlugins(): Promise<void>;
async getPlugin(robotId: string): Promise<RobotPlugin>;
async getAllPlugins(): Promise<RobotPlugin[]>;
export class PluginLoadError extends Error {
constructor(
message: string,
public robotId?: string,
public cause?: unknown
) {
super(message);
this.name = "PluginLoadError";
}
}
```
### 2. Admin Interface
## Usage Examples
Build an admin panel for managing plugins:
- Upload/edit JSON plugin definitions
- Validate plugin schema
- Version management
- Preview plugin details
### 3. API Routes
Create API endpoints for plugin management:
### Loading a Repository
```typescript
// GET /api/plugins
// GET /api/plugins/:robotId
// POST /api/plugins (with auth)
// PUT /api/plugins/:robotId (with auth)
// DELETE /api/plugins/:robotId (with auth)
const store = new PluginStore();
await store.loadRepository("https://github.com/org/robot-plugins");
```
### 4. Experiment Designer Integration
Update the experiment designer to:
- Allow robot selection
- Load appropriate actions
- Configure ROS2 bridge settings
### 5. ROS2 Bridge Integration
Create a bridge module for executing actions:
### Getting Plugin Information
```typescript
// src/lib/ros2-bridge/bridge.ts
export class ROS2Bridge {
async executeAction(
robotId: string,
actionId: string,
parameters: Record<string, any>
): Promise<void>;
const plugin = store.getPlugin("turtlebot3-burger");
if (plugin) {
console.log(`Loaded ${plugin.name} version ${plugin.version}`);
console.log(`Supported actions: ${plugin.actions.length}`);
}
```
## Development Phases
### Registering Transform Functions
1. **Phase 1: Core Plugin Store**
- Implement plugin schema and validation
- Build plugin loader
- Create basic API endpoints
```typescript
store.registerTransformFunction("transformToTwist", (params) => {
// Custom transformation logic
return transformedData;
});
```
2. **Phase 2: Admin Interface**
- Build plugin management UI
- Implement plugin upload/edit
- Add version control
## Best Practices
3. **Phase 3: Experiment Designer Integration**
- Add robot selection
- Update action library based on selection
- Enhance action configuration
1. **Error Handling:**
- Always catch and properly handle plugin loading errors
- Provide meaningful error messages
- Include error context when possible
4. **Phase 4: ROS2 Bridge**
- Implement ROS2 connection
- Add message transformation
- Test with real robots
2. **Validation:**
- Validate all plugin metadata
- Verify action parameters
- Check compatibility requirements
5. **Phase 5: Documentation & Testing**
- Write contributor guidelines
- Add comprehensive tests
- Create example plugins
3. **Performance:**
- Use caching appropriately
- Implement lazy loading where possible
- Monitor memory usage
## Contributing
To add a new robot to the plugin store:
1. Create a new JSON file following the plugin schema
2. Test the plugin using the validation tools
3. Submit a pull request with:
- Plugin JSON
- Any custom transformation functions
- Documentation updates
- Test cases
4. **Security:**
- Validate URLs and file paths
- Implement proper access controls
- Sanitize plugin inputs
## Future Enhancements
- Plugin marketplace for sharing robot definitions
- Visual plugin builder in admin interface
- Real-time plugin updates
- Plugin dependency management
- Custom action visualization components
1. **Plugin Versioning:**
- Semantic versioning support
- Version compatibility checking
- Update management
2. **Advanced Caching:**
- Persistent cache storage
- Cache invalidation strategies
- Partial cache updates
3. **Plugin Marketplace:**
- User ratings and reviews
- Download statistics
- Community contributions
4. **Enhanced Validation:**
- Runtime validation
- Performance benchmarking
- Compatibility testing
## Dynamic Plugin Loading
The plugin store in HRI Studio supports modular loading of robot actions. Not every robot action is installed by default; instead, only the necessary plugins for the desired robots are installed. This approach offers several benefits:
- Flexibility: Deploy only the robots and actions you need.
- Performance: Avoid loading unnecessary modules, leading to faster startup times and reduced memory usage.
- Extensibility: Allow companies and users to host their own plugin repositories with custom robot actions.
### Implementation Details
1. Each plugin should export a manifest adhering to the `RobotPlugin` interface, containing a unique identifier, display name, and a list of actions.
2. The system loads only the configured plugins, which can be managed via environment variables, a database table, or an admin interface.
3. Dynamic imports are used in the Next.js server environment to load robot actions on demand. For example:
```ts
async function loadPlugin(pluginUrl: string): Promise<RobotPlugin> {
const pluginModule = await import(pluginUrl);
return pluginModule.default as RobotPlugin;
}
```
This design ensures that HRI Studio remains lean and agile, seamlessly integrating new robot actions without overhead.

1
docs/root.tex Symbolic link
View File

@@ -0,0 +1 @@
/Users/soconnor/Projects/csci378/hristudio-sp2025/root.tex

169
docs/structure.md Normal file
View File

@@ -0,0 +1,169 @@
# HRIStudio Structure and Requirements
## Structure
A *study* is a general term for a research project.
An *experiment* is a specific set of steps and actions that will be conducted with a participant and robot. Experiments are designed and configured via a dedicated drag and drop experiment designer. This interactive designer features a dotted background—similar to Unreal Engine's IDE drag and drop area—that clearly indicates drop zones. Users can add, reorder, and connect individual steps and actions visually.
An *trial* is a specific instance of an experiment. It is a single run of the experiment with a specific participant and robot.
A *step* is a general term for something that is being done in the experiment. It is represented as a collection of actions that are being done in a specific order.
An *action* is a specific operation that is being done (like "move to position", "press button", "say something", etc.) These are the smallest atomic units of the platform.
A *participant* is a person that has been added to a study. This person does not have an account.
A *user* is a person that has an account, which is a person that has been added to a study. Anyone can sign up for an account, but they must be added to a study or create their own. A user can have different roles in different studies.
## Experiment Design and Implementation
Experiments are central to HRIStudio and are managed with full CRUD operations. The Experiment Design feature includes:
- **Drag and Drop Designer:** An interactive design area with a dotted background, reminiscent of Unreal Engine's IDE, which allows users to visually add, reposition, and connect steps and actions. The designer includes:
- A dotted grid background that provides visual cues for alignment and spacing
- Highlighted drop zones that activate when dragging components
- Visual feedback for valid/invalid drop targets
- Smooth animations for reordering and nesting
- Connection lines showing relationships between steps
- A side panel of available actions that can be dragged into steps
- **Experiment Templates:** The ability to save and reuse experiment configurations.
- **CRUD Operations:** Procedures to create, retrieve, update, and delete experiments associated with a study.
- **Dynamic Interaction:** Support for adding and reordering steps, and nesting actions within steps.
## Roles and Permissions
### Core Roles
1. **Owner**
- Single owner per study
- Full control over all aspects of the study
- Can delete study or transfer ownership
- Can manage all other roles
- Usually the study creator or designated successor
- Cannot be removed except through ownership transfer
2. **Admin**
- Multiple admins allowed
- Can manage participants, experiments, and study settings
- Can invite and manage other users (except Owner)
- Cannot delete study or transfer ownership
- Appointed by Owner
3. **Principal Investigator (PI)**
- Scientific oversight role
- Full access to participant data and experiment design
- Can manage experiment protocols
- Can analyze and export all data
- Cannot modify core study settings or manage user roles
- Typically one PI per study
4. **Wizard**
- Operates the robot during experiment trials
- Can control live experiment sessions
- Can view anonymized participant data
- Can annotate experiments in real-time
- Cannot modify study design or access sensitive participant data
- Multiple wizards allowed
5. **Researcher**
- Can view and analyze experiment data
- Can access anonymized participant information
- Can export and analyze results
- Cannot modify study design or participant data
- Cannot run experiment trials
- Multiple researchers allowed
6. **Observer**
- Can view live experiments
- Can view anonymized participant data
- Can add annotations
- Cannot modify any study aspects
- Cannot access sensitive data
- Multiple observers allowed
### Permission Categories
1. **Study Management**
- Create/Delete Study (Owner only)
- Edit Study Settings
- Transfer Ownership (Owner only)
- Manage Study Metadata
2. **Participant Management**
- Add/Remove Participants
- View Participant Details (identifiable vs. anonymized)
- Edit Participant Information
- Manage Participant Consent Forms
3. **Experiment Design**
- Create/Edit Experiment Templates
- Define Steps and Actions
- Set Robot Behaviors
- Configure Data Collection
4. **Experiment Execution**
- Run Experiment Trials
- Control Robot Actions
- Monitor Live Sessions
- Add Real-time Annotations
5. **Data Access**
- View Raw Data
- View Anonymized Data
- Export Data
- Access Participant Identifiable Information
6. **User Management**
- Invite Users
- Assign Roles
- Remove Users
- Manage Permissions
### Role-Permission Matrix
| Permission Category | Owner | Admin | PI | Wizard | Researcher | Observer |
|-----------------------|-------|-------|-----|--------|------------|----------|
| Study Management | Full | Most | No | No | No | No |
| Participant Management| Full | Full | Full| Limited| Limited | View Only|
| Experiment Design | Full | Full | Full| No | No | No |
| Experiment Execution | Full | Full | Full| Full | View Only | View Only|
| Data Access | Full | Full | Full| Limited| Limited | Limited |
| User Management | Full | Most | No | No | No | No |
### Special Considerations
1. **Data Privacy**
- Identifiable participant information is only accessible to Owner, Admin, and PI roles
- All other roles see anonymized data
- Audit logs track all data access
2. **Role Hierarchy**
- Owner > Admin > PI > Wizard/Researcher > Observer
- Higher roles inherit permissions from lower roles
- Certain permissions (like study deletion) are restricted to specific roles
3. **Role Assignment**
- Users can have different roles in different studies
- One user cannot hold multiple roles in the same study
- Role changes are logged and require appropriate permissions
Participant Management: can create, update, delete participants, as well as view their personal information
- Admin: can do everything
- Principal Investigator: can do everything
- Wizard: can view participants, but cannot view their personal information
- Researcher: can view participants, but cannot view their personal information
Experiment Management: can create, update, delete experiments, as well as view their data and results.
- Admin: Can do everything
- Principal Investigator: Can do everything
- Wizard: Runs experiment trials, can view results
- Researcher: Can view results
Experiment Design: can create, update, delete steps and actions, as well as specify general parameters for the experiment.
- Admin: Can do everything
- Principal Investigator: Can do everything
- Wizard: Can create, update, delete steps and actions, as well as specify general parameters for the experiment
- Researcher: Can view steps and actions.

346
docs/ui-design.md Normal file
View File

@@ -0,0 +1,346 @@
# UI Design & User Experience
## Design System
### Color System
Our color system is defined in CSS variables with both light and dark mode variants:
```css
:root {
/* Core colors */
--background: 0 0% 100%;
--foreground: 222 47% 11%;
/* Primary colors */
--primary: 217 91% 60%;
--primary-foreground: 0 0% 100%;
/* Card colors */
--card: 0 0% 100%;
--card-foreground: 222 47% 11%;
/* Additional semantic colors */
--muted: 210 40% 96%;
--muted-foreground: 215 16% 47%;
--accent: 210 40% 96%;
--accent-foreground: 222 47% 11%;
/* ... additional color definitions ... */
}
.dark {
--background: 222 47% 11%;
--foreground: 210 40% 98%;
/* ... dark mode variants ... */
}
```
### Typography
We use the Geist font family for its clean, modern appearance:
```typescript
import { GeistSans } from 'geist/font/sans';
<body className={cn(
"min-h-screen bg-background font-sans antialiased",
GeistSans.className
)}>
```
### Spacing System
Consistent spacing using Tailwind's scale:
- `space-1`: 0.25rem (4px)
- `space-2`: 0.5rem (8px)
- `space-4`: 1rem (16px)
- `space-6`: 1.5rem (24px)
- `space-8`: 2rem (32px)
## Component Architecture
### Base Components
All base components are built on Radix UI primitives and styled with Tailwind:
```typescript
// Example Button Component
const Button = React.forwardRef<
HTMLButtonElement,
ButtonProps
>(({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
"focus-visible:outline-none focus-visible:ring-2",
"disabled:opacity-50 disabled:pointer-events-none",
buttonVariants({ variant, size, className })
)}
ref={ref}
{...props}
/>
);
});
```
### Layout Components
#### Page Layout
```typescript
export function PageLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-full min-h-screen w-full">
<AppSidebar />
<div className="flex w-0 flex-1 flex-col">
<Header />
<main className="flex-1 overflow-auto p-4">
<PageTransition>
{children}
</PageTransition>
</main>
</div>
</div>
);
}
```
#### Sidebar Navigation
The sidebar uses a floating design with dynamic content based on context:
```typescript
export function AppSidebar({ ...props }: SidebarProps) {
return (
<Sidebar
collapsible="icon"
variant="floating"
className="border-none"
{...props}
>
<SidebarHeader>
<StudySwitcher />
</SidebarHeader>
<SidebarContent>
<NavMain items={navItems} />
</SidebarContent>
<SidebarFooter>
<NavUser />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}
```
### Form Components
Forms use React Hook Form with Zod validation:
```typescript
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
title: "",
description: "",
},
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Additional form fields */}
</form>
</Form>
);
```
## Responsive Design
### Breakpoints
We follow Tailwind's default breakpoints:
- `sm`: 640px
- `md`: 768px
- `lg`: 1024px
- `xl`: 1280px
- `2xl`: 1536px
### Mobile-First Approach
```typescript
export function StudyCard({ study }: StudyCardProps) {
return (
<Card className="
w-full
p-4
sm:p-6
md:hover:shadow-lg
transition-all
duration-200
">
{/* Card content */}
</Card>
);
}
```
## Animation System
### Transition Utilities
Common transitions are defined in Tailwind config:
```javascript
theme: {
extend: {
transitionTimingFunction: {
'bounce-ease': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
},
},
}
```
### Page Transitions
Using Framer Motion for smooth page transitions:
```typescript
export function PageTransition({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.2 }}
>
{children}
</motion.div>
);
}
```
## Loading States
### Skeleton Components
```typescript
export function CardSkeleton() {
return (
<div className="p-6 space-y-4">
<Skeleton className="h-7 w-[40%]" />
<Skeleton className="h-4 w-[60%]" />
<div className="pt-4">
<Skeleton className="h-4 w-[25%]" />
</div>
</div>
);
}
```
### Loading Indicators
```typescript
export function LoadingSpinner({ size = "default" }: { size?: "sm" | "default" | "lg" }) {
return (
<div
className={cn(
"animate-spin rounded-full border-2",
"border-background border-t-foreground",
{
"h-4 w-4": size === "sm",
"h-6 w-6": size === "default",
"h-8 w-8": size === "lg",
}
)}
/>
);
}
```
## Accessibility
### ARIA Labels
All interactive components include proper ARIA labels:
```typescript
export function IconButton({ label, icon: Icon, ...props }: IconButtonProps) {
return (
<Button
{...props}
aria-label={label}
className="p-2 hover:bg-muted/50 rounded-full"
>
<Icon className="h-4 w-4" />
<span className="sr-only">{label}</span>
</Button>
);
}
```
### Keyboard Navigation
Support for keyboard navigation in all interactive components:
```typescript
export function NavigationMenu() {
return (
<nav
role="navigation"
className="focus-within:outline-none"
onKeyDown={(e) => {
if (e.key === "Escape") {
// Handle escape key
}
}}
>
{/* Navigation items */}
</nav>
);
}
```
## Best Practices
1. **Component Organization:**
- One component per file
- Clear prop interfaces
- Consistent file naming
2. **Style Organization:**
- Use Tailwind utility classes
- Extract common patterns to components
- Maintain consistent spacing
3. **Performance:**
- Lazy load non-critical components
- Use React.memo for expensive renders
- Implement proper loading states
4. **Accessibility:**
- Include ARIA labels
- Support keyboard navigation
- Maintain proper contrast ratios
5. **Testing:**
- Component unit tests
- Integration tests for flows
- Visual regression testing