diff --git a/.context/api-integration.md b/.context/api-integration.md deleted file mode 100644 index 0b1440179..000000000 --- a/.context/api-integration.md +++ /dev/null @@ -1,92 +0,0 @@ -# API Integration Context - -About ResearchHub (the platform we are building): - -- ResearchHub is a web app that intends to incentivize scientists with ResearchCoin (ERC20 token) when they engage in "good" science practices (e.g. peer review, publishing openly, etc.) -- ResearchHub has a strong social network aspect, where users can follow each other and see each other's activity as wee as discover new content -- ResearchHub is built on Django (backend) and Next.js (frontend). - -Project goals: - -- Migrate from old Next.js app to new version when improved UI/UX and new components/features -- Maintain connection to existing Django backend - -## Reference Paths - -All paths are relative to the root of the project. - -- Publicly facing pages: `app/` -- UI components: `components/ui` -- Django API: `external_references/django-app` -- Old Next.js app (we are migrating from this): `external_references/old-nextjs-app` -- Django API endpoints: `external_references/django-app/src/urls.py` -- Modals should be added in `components/modals` -- Services should be added in `services` - -## Auth - -- Next.js app is using Auth via next-auth -- Django API does not use JWT but it does return a token in the response of the login endpoint -- app/api/auth/[...nextauth]/route.ts is the file that handles the authentication - -## Services - -- Services are located in `services` directory -- Services are used to interact with the Django API -- DTO transformer functions are located in `services/types` directory and are used to transform response data from the Django API into app-usable types. - -## Layout and conventions - -- `app/layouts/PageLayout.tsx` is the main layout file for the app -- `app/layouts/TopBar.tsx` is the top bar component for the app -- Every new page should be wrapped with `app/layouts/PageLayout.tsx` and have a three column layout. Initialize `RightSidebar` to be empty. - -## Contexts - -- Contexts including data providers should be located in `contexts` directory - -## Other - -- Utility functions are located in `utils` directory -- Date functions are located in `utils/date.ts` - -## Main Feed - -- `components/Feed/Feed.tsx` is the main feed component -- Feed item types are fetched from the /feed endpoint -- actions are: publish, repost, post, contribute -- types are: grant, paper, review, comment, contribution - -## Directives - -- Use next/Link component whenever rendering a component that redirects to another page -- Use `components/ui/Tabs` component whenever rendering a horizontal navigation -- Use `components/ui/Button` component whenever rendering a button. do not use ` - ); - }; - ``` - -2. **Variants with class-variance-authority**: - - Use `class-variance-authority` (cva) for components with multiple variants - - Define all variants in a single `cva` call - - Reference the variant styles in the component - - ```tsx - const buttonVariants = cva( - 'rounded-md font-medium transition-colors', - { - variants: { - variant: { - default: 'bg-primary-600 text-white hover:bg-primary-700', - secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300', - }, - size: { - sm: 'px-2 py-1 text-sm', - md: 'px-4 py-2', - lg: 'px-6 py-3 text-lg', - }, - }, - defaultVariants: { - variant: 'default', - size: 'md', - }, - } - ); - ``` - -## State Management - -1. **Local State**: - - Use `useState` for component-specific state - - Use `useReducer` for complex state logic - -2. **Shared State**: - - Use React Context for state that needs to be shared across multiple components - - Create dedicated context providers in the `contexts/` directory - -3. **Derived State**: - - Use `useMemo` for expensive computations - - Use `useCallback` for functions that are passed as props to prevent unnecessary re-renders - -## Component Composition - -1. **Component Props**: - - Accept a `className` prop for styling customization - - Use the spread operator for passing additional props - - ```tsx - const Button = ({ className, children, ...props }: ButtonProps) => { - return ( - - ); - }; - ``` - -2. **Composition Patterns**: - - Use the children prop for component composition - - Use render props for complex rendering logic - - Use compound components for related UI elements - -## Performance Optimization - -1. **Memoization**: - - Use `React.memo` for pure functional components that render often - - Use `useMemo` for expensive calculations - - Use `useCallback` for event handlers that are passed to child components - -2. **Code Splitting**: - - Use dynamic imports for large components - - Use Next.js's built-in code splitting features - -3. **Rendering Optimization**: - - Avoid unnecessary re-renders by using proper dependency arrays in hooks - - Implement virtualization for long lists with many items - -These patterns ensure consistent, maintainable, and performant components throughout the ResearchHub application. \ No newline at end of file diff --git a/.cursor/rules/database-schema.mdc b/.cursor/rules/database-schema.mdc deleted file mode 100644 index 25322324d..000000000 --- a/.cursor/rules/database-schema.mdc +++ /dev/null @@ -1,452 +0,0 @@ ---- -description: This document provides architectural and relationship information about the ResearchHub PostgreSQL database, complementing the detailed table specifications in `database-tables.cursor-rules`. -globs: -alwaysApply: false ---- -# ResearchHub Database Architecture (Cursor Rule) - -This document provides architectural and relationship information about the ResearchHub PostgreSQL database, complementing the detailed table specifications in `database-tables.cursor-rules`. - -## System Architecture Overview - -ResearchHub's database design implements a domain-driven approach centered around research content, with several key architectural patterns: - -1. **Unified Document Model**: Content (papers, posts, notes) is abstracted through a unified document system -2. **Polymorphic Relationships**: Generic relations support flexible content relationships -3. **Reputation Economy**: A complete transaction system for academic reputation -4. **Hub-based Organization**: Content categorization through research fields -5. **User Identity System**: Separates user accounts from author profiles -6. **Nonprofit Integration**: Supports fundraising for nonprofit organizations through the Endaoment service - -## Entity Relationship Diagram (Conceptual) - -``` -[USER SYSTEM] [CONTENT SYSTEM] [ORGANIZATION SYSTEM] -+----+ +---+ +----+ -| user_user | <----> | paper_paper | <----> | hub_hub | -+----+ +---+ +----+ - | | | - v v v -+----+ +---+ +----+ -| user_author | <----> | discussion_thread | <----> | topic_topic | -+----+ +---+ +----+ - | | | - v v | -+----+ +---+ | -| user_action | <----> | reputation_score | <----+ -+----+ +---+ - -[NONPROFIT SYSTEM] -+---------------+ +---------------+ -| nonprofit_org | <-------------> | purchase_fundraise | -+---------------+ +---------------+ - | | - v v -+------------------------+ -| nonprofit_fundraise_link | -+------------------------+ -``` - -## Domain Models - -### Content Domain - -The content model centers around unified documents that can represent different types of content: - -1. **Papers** (`paper_paper`): Research papers, which can be: - - Imported from external sources with DOIs - - Uploaded directly by users - - Created as preprints within the system - -2. **Discussions** (`discussion_thread`): Conversations that can be: - - Attached to papers - - Created as standalone discussions - - Function as comments on other content - -3. **Posts** (`researchhub_document_researchhubpost`): User-generated content that can be: - - Research summaries - - Commentary - - Blog-style posts - -All content connects through the `researchhub_document_researchhubunifieddocument` table, which establishes a polymorphic pattern allowing different content types to be treated uniformly in many contexts. - -### User Domain - -The user model separates identity from academic profile: - -1. **User Accounts** (`user_user`): Authentication and platform identity -2. **Author Profiles** (`user_author`): Academic identity and credentials -3. **Verification** (`user_userverification`): Processes for validating academic credentials - -This separation enables: -- One user to manage multiple author profiles -- Author identity verification without affecting user account access -- Academic metrics separate from platform activity metrics - -### Reputation Domain - -The reputation system implements a complete economic model: - -1. **Scoring** (`reputation_score`): Point values representing academic impact -2. **Transactions** (`reputation_distribution`): Movement of reputation between users -3. **Incentives** (`reputation_bounty`): Rewards for valuable contributions -4. **Escrow** (`reputation_escrow`): Holding system for reputation in process - -### Organization Domain - -Content is organized through multiple taxonomies: - -1. **Hubs** (`hub_hub`): Primary research field categorization -2. **Topics** (`topic_topic`): Finer-grained subject matter classification -3. **Concepts** (`researchhub_document_unifieddocumentconcepts`): Specific concepts within content - -## Key Relationships - -### Content to Users - -Content links to users through multiple relationship types: - -1. **Authorship**: `paper_authorship` connects papers to authors -2. **Creation**: Most content has a `created_by_id` reference to `user_user` -3. **Interaction**: `discussion_vote`, `user_action` track user interactions with content - -### Content Organization - -Content is organized through: - -1. **Hub Assignment**: `researchhub_document_researchhubunifieddocument_hubs` junction table -2. **Topic Association**: `topic_unifieddocumenttopics` relevancy-scored connections -3. **Hierarchy**: Topics belong to subfields, which belong to fields - -### Polymorphic Relationships - -The system uses Django's ContentType framework for polymorphic relationships: - -1. **Content Type ID**: References the `django_content_type` table to identify model types -2. **Object ID**: References the primary key of the specific instance -3. Together they create a "generic foreign key" pattern used in: - - `user_action` - - `discussion_vote` - - `reputation_distribution` - - `reputation_bounty` - -## Core Workflows - -### Nonprofit Fundraising Workflow - -``` - ┌─── Search for Nonprofit - │ -User → Create Fundraise → Select Nonprofit → Link to Document → Process Donations - │ - └─── Distribute Funds -``` - -Implementation path: -1. Search for nonprofit via Endaoment API -2. Create or select a nonprofit entry in `nonprofit_org` -3. Create a fundraise in `purchase_fundraise` -4. Link them through `nonprofit_fundraise_link` -5. Process donations and distribute funds - -### Paper Publication Workflow - -``` - ┌─── Assign Topics - │ -User → Upload Paper → Create Unified Doc → Assign Hubs → Link Authors → Add Metadata - │ - └─── Calculate Score -``` - -Implementation path: -1. Insert into `paper_paper` -2. Create entry in `researchhub_document_researchhubunifieddocument` -3. Link to hubs via `researchhub_document_researchhubunifieddocument_hubs` -4. Create entries in `paper_authorship` -5. Assign topics via `topic_unifieddocumenttopics` -6. Update metrics (score, views, etc.) - -### Discussion Creation Workflow - -``` - ┌─── Award Reputation - │ -User → Create Thread → Link to Paper/Post → Update Feeds → Update Metrics - │ - └─── Notify Subscribers -``` - -Implementation path: -1. Insert into `discussion_thread` -2. Link to paper or post through `paper_id` or generic relation -3. Create feed entries in `feed_feedentry` -4. Award reputation through `reputation_distribution` -5. Update content metrics (discussion_count, etc.) - -### Reputation Transaction Workflow - -``` - ┌─── Update Sender Balance - │ -User → Vote/Reward → Create Distribution → Update Recipient Score → Record History - │ - └─── Update Content Score -``` - -Implementation path: -1. Create entry in triggering table (e.g., `discussion_vote`) -2. Create distribution in `reputation_distribution` -3. Update scores in `reputation_score` -4. Record history in `reputation_scorechange` -5. Update content metrics (score, etc.) - -## Query Optimization Guidelines - -### Nonprofit Query Patterns - -When performing nonprofit-related queries: - -1. **Search via External API**: - ```sql - -- Avoid directly querying the database for nonprofits not yet in the system - -- Instead, use the Endaoment API for initial searches - -- Then store results in nonprofit_org for future reference - ``` - -2. **Fundraise Relationships**: - ```sql - -- Good: Efficient join through the link table - SELECT n.name, n.ein, f.goal_amount - FROM nonprofit_org n - JOIN nonprofit_fundraise_link nfl ON n.id = nfl.nonprofit_id - JOIN purchase_fundraise f ON nfl.fundraise_id = f.id - WHERE f.unified_document_id = 123 - ``` - -### Indexing Strategy - -The database employs strategic indexing to optimize performance: - -1. **Compound Indexes**: - - `(content_type_id, object_id)` for polymorphic lookups - - `(author_id, hub_id)` for reputation queries - - `(unified_document_id, is_public)` for content filtering - -2. **Text Search Indexes**: - - `paper_paper.title` and `paper_paper.abstract` for paper search - - `discussion_thread.plain_text` for content search - -3. **Foreign Key Indexes**: - - All foreign key columns are indexed - - Junction tables have indexes on both sides of relationships - -### Query Optimization Patterns - -When writing queries, follow these patterns for optimal performance: - -1. **Use Unified Document IDs**: - ```sql - -- Good: Query through unified document - SELECT * FROM paper_paper WHERE unified_document_id IN - (SELECT id FROM researchhub_document_researchhubunifieddocument WHERE score > 100) - - -- Avoid: Multiple separate queries - SELECT * FROM paper_paper WHERE score > 100 - UNION - SELECT * FROM researchhub_document_researchhubpost WHERE score > 100 - ``` - -2. **Filter Early in Joins**: - ```sql - -- Good: Filter before joining - SELECT p.* FROM paper_paper p - JOIN (SELECT unified_document_id FROM researchhub_document_researchhubunifieddocument_hubs - WHERE hub_id = 123) h ON p.unified_document_id = h.unified_document_id - - -- Avoid: Filter after joining multiple tables - SELECT p.* FROM paper_paper p - JOIN researchhub_document_researchhubunifieddocument_hubs uh ON p.unified_document_id = uh.unified_document_id - JOIN hub_hub h ON uh.hub_id = h.id - WHERE h.id = 123 - ``` - -3. **Use Content Type Lookups Efficiently**: - ```sql - -- Good: Pre-resolve content type - SELECT * FROM discussion_vote - WHERE content_type_id = (SELECT id FROM django_content_type - WHERE app_label = 'discussion' AND model = 'thread') - AND object_id = 123 - - -- Avoid: Multiple joins for simple lookups - SELECT * FROM discussion_vote v - JOIN django_content_type ct ON v.content_type_id = ct.id - WHERE ct.app_label = 'discussion' AND ct.model = 'thread' - AND v.object_id = 123 - ``` - -## Transaction Management - -The database requires careful transaction management in several areas: - -### Reputation Transactions - -Reputation transactions should follow ACID principles: - -1. **Atomicity**: Use database transactions to ensure distributions and score updates happen together -2. **Consistency**: Verify reputation totals match distributions -3. **Isolation**: Lock reputation records during updates to prevent race conditions -4. **Durability**: Ensure score changes are committed before notifying users - -Example transaction: -```sql -BEGIN; --- Create distribution -INSERT INTO reputation_distribution (sender_id, recipient_id, amount, ...) -VALUES (1, 2, 10, ...); - --- Update recipient score -UPDATE reputation_score -SET score = score + 10 -WHERE author_id = 2 AND hub_id = 3; - --- Record history -INSERT INTO reputation_scorechange (score_id, score_change, ...) -VALUES (123, 10, ...); -COMMIT; -``` - -### Content Relationship Integrity - -When creating or modifying content relationships: - -1. Create the primary content entity first -2. Establish the unified document connection -3. Create dependent relationships within the same transaction -4. Update denormalized counters and metrics - -## Data Migration Considerations - -When migrating or evolving the schema: - -1. **Denormalized Fields**: Several tables contain denormalized count fields that must be updated when related data changes -2. **Polymorphic Relationships**: Changes to model names require updates to `django_content_type` references -3. **Score Recalculation**: Reputation scores may need recalculation after structural changes - -## Common Query Examples - -### Paper Lookup with Related Information - -Find a paper with all related metadata, including authors, hubs, and metrics: - -```sql -SELECT - p.id, p.title, p.doi, p.abstract, - p.score, p.citations, p.views, p.downloads, - STRING_AGG(DISTINCT a.raw_author_name, ', ') AS authors, - STRING_AGG(DISTINCT h.name, ', ') AS hubs, - COUNT(DISTINCT dt.id) AS thread_count -FROM - paper_paper p - LEFT JOIN paper_authorship a ON p.id = a.paper_id - LEFT JOIN researchhub_document_researchhubunifieddocument_hubs rdh - ON p.unified_document_id = rdh.researchhubunifieddocument_id - LEFT JOIN hub_hub h ON rdh.hub_id = h.id - LEFT JOIN discussion_thread dt ON dt.paper_id = p.id -WHERE - p.doi = '10.1234/example.doi' - OR p.id = 12345 -GROUP BY - p.id; -``` - -### Finding Active Contributors - -Identify the most active contributors in a specific research field: - -```sql -SELECT - u.id, u.username, - ua.first_name, ua.last_name, - COUNT(DISTINCT p.id) AS paper_count, - COUNT(DISTINCT dt.id) AS discussion_count, - SUM(rs.score) AS total_reputation -FROM - user_user u - JOIN user_author ua ON u.id = ua.user_id - LEFT JOIN paper_authorship pa ON ua.id = pa.author_id - LEFT JOIN paper_paper p ON pa.paper_id = p.id - LEFT JOIN discussion_thread dt ON dt.created_by_id = u.id - LEFT JOIN reputation_score rs ON ua.id = rs.author_id -WHERE - rs.hub_id IN ( - SELECT id FROM hub_hub WHERE name LIKE '%Neuroscience%' - ) - AND dt.created_date >= NOW() - INTERVAL '6 months' -GROUP BY - u.id, ua.id -ORDER BY - total_reputation DESC -LIMIT 20; -``` - -### Content Recommendation Query - -Find relevant papers for a user based on their interests and activity: - -```sql -WITH user_hubs AS ( - SELECT DISTINCT h.id - FROM hub_hub h - JOIN researchhub_document_researchhubunifieddocument_hubs hd ON h.id = hd.hub_id - JOIN paper_paper p ON p.unified_document_id = hd.researchhubunifieddocument_id - JOIN paper_authorship pa ON p.id = pa.paper_id - WHERE pa.author_id = 123 - - UNION - - SELECT h.id - FROM hub_hub h - JOIN user_follow uf ON h.id = uf.object_id - WHERE uf.user_id = 456 - AND uf.content_type_id = (SELECT id FROM django_content_type WHERE app_label = 'hub' AND model = 'hub') -), -user_topics AS ( - SELECT DISTINCT t.id - FROM topic_topic t - JOIN topic_unifieddocumenttopics tud ON t.id = tud.topic_id - JOIN paper_paper p ON p.unified_document_id = tud.unified_document_id - JOIN discussion_thread dt ON dt.paper_id = p.id - WHERE dt.created_by_id = 456 -) -SELECT - p.id, p.title, p.doi, - p.score * 0.5 + p.citations * 0.3 + p.downloads * 0.2 AS relevance_score -FROM - paper_paper p - JOIN researchhub_document_researchhubunifieddocument_hubs hd - ON p.unified_document_id = hd.researchhubunifieddocument_id - JOIN topic_unifieddocumenttopics tud ON p.unified_document_id = tud.unified_document_id -WHERE - hd.hub_id IN (SELECT id FROM user_hubs) - AND tud.topic_id IN (SELECT id FROM user_topics) - AND p.is_public = TRUE - AND p.is_removed = FALSE - AND p.created_date >= NOW() - INTERVAL '1 year' -ORDER BY - relevance_score DESC -LIMIT 10; -``` - -## Schema Evolution Best Practices - -When modifying the ResearchHub database schema, follow these guidelines: - -1. **Backward Compatibility**: Keep existing relationships functional during transitions -2. **Transaction Safety**: Ensure migrations can be rolled back cleanly -3. **Data Validation**: Verify data integrity before and after migrations -4. **Performance Impact**: Consider query patterns when adding or removing indexes -5. **Polymorphic Relations**: Update content types appropriately when modifying models - -For detailed table information, refer to `database-tables.cursor-rules`. diff --git a/.cursor/rules/database-tables.mdc b/.cursor/rules/database-tables.mdc deleted file mode 100644 index 5b84510f6..000000000 --- a/.cursor/rules/database-tables.mdc +++ /dev/null @@ -1,554 +0,0 @@ ---- -description: This document provides a comprehensive reference of table compositions in the ResearchHub database. -globs: -alwaysApply: false ---- -# ResearchHub Database Tables (Cursor Rule) - -This document provides a comprehensive reference of table compositions in the ResearchHub database. - -## User Management - -### nonprofit_org - -**Description**: Nonprofit organizations that can receive donations. - -| Column | Type | Description | -| ----- | --- | ---- | -| id | integer | Primary key | -| name | character varying(255) | Name of the nonprofit | -| ein | character varying(20) | Employer Identification Number | -| endaoment_org_id | character varying(100) | Unique ID in Endaoment system | -| base_wallet_address | character varying(42) | Blockchain wallet address | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | - -### nonprofit_fundraise_link - -**Description**: Join table connecting nonprofits and fundraising campaigns. - -| Column | Type | Description | -| ----- | ----- | --- | -| id | integer | Primary key | -| nonprofit_id | integer | Foreign key to nonprofit_org | -| fundraise_id | integer | Foreign key to purchase_fundraise | -| note | text | Notes about this specific link | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | - -### user_user - -**Description**: Core user account table. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| username | character varying | Unique username | -| email | character varying | User's email address | -| password | character varying | Hashed password | -| first_name | character varying | User's first name | -| last_name | character varying | User's last name | -| is_active | boolean | Whether the account is active | -| date_joined | timestamp with time zone | When the user joined | -| reputation | integer | User's reputation score | -| is_staff | boolean | Whether user has staff privileges | -| is_superuser | boolean | Whether user has superuser privileges| - -### user_author - -**Description**: Author profile information linked to users. - -| Column | Type | Description | -| ------------------ | ---------------------- | ----------------------------------- | -| id | integer | Primary key | -| first_name | character varying | Author's first name | -| last_name | character varying | Author's last name | -| user_id | integer | Foreign key to user_user | -| orcid | character varying | ORCID identifier | -| scopus_author_id | character varying | Scopus author identifier | -| google_scholar_id | character varying | Google Scholar identifier | -| twitter | character varying | Twitter handle | -| linkedin | character varying | LinkedIn profile URL | -| website | character varying | Personal website URL | - -### user_userverification - -**Description**: User verification information. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| first_name | text | User's first name | -| last_name | text | User's last name | -| status | text | Verification status | -| verified_by | text | Verification method | -| external_id | text | External identifier | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| user_id | integer | Foreign key to user_user | - -### user_follow - -**Description**: User following relationships. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| object_id | integer | ID of followed object | -| content_type_id| integer | Type of followed content | -| user_id | integer | User who follows | - -### user_action - -**Description**: User actions on the platform. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| object_id | integer | ID of action object | -| content_type_id| integer | Type of content | -| user_id | integer | User who performed action | -| read_date | timestamp with time zone | When action was read | -| display | boolean | Whether to display action | -| is_removed | boolean | Whether action is removed | - -## Paper Resources - -### paper_paper - -**Description**: Research paper metadata. - -| Column | Type | Description | -| -------------------- | ------------------------ | ---------------------------------- | -| id | integer | Primary key | -| title | character varying(1024) | Paper title | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| paper_publish_date | date | Publication date | -| doi | character varying(255) | Digital Object Identifier | -| url | character varying(1024) | URL to paper | -| uploaded_by_id | integer | User who uploaded the paper | -| file | character varying(512) | File path | -| abstract | text | Paper abstract | -| is_public | boolean | Whether paper is public | -| is_removed | boolean | Whether paper has been removed | -| slug | character varying(1024) | URL-friendly slug | -| paper_type | character varying(32) | Type of paper | -| score | integer | Aggregated score | -| citations | integer | Citation count | -| downloads | integer | Download count | -| views | integer | View count | -| unified_document_id | integer | Related unified document | -| is_open_access | boolean | Whether paper is open access | - -### paper_authorship - -**Description**: Paper author relationships. - -| Column | Type | Description | -| ---------------- | ------------------------ | ---------------------------------- | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| author_position | character varying(10) | Position in author list | -| is_corresponding | boolean | Whether author is corresponding | -| raw_author_name | character varying(255) | Raw author name from source | -| author_id | integer | Foreign key to user_author | -| paper_id | integer | Foreign key to paper_paper | -| email | character varying(255) | Author's email | -| department | character varying(255) | Author's department | - -## Discussion Resources - -### discussion_thread - -**Description**: Discussion threads associated with papers. - -| Column | Type | Description | -| -------------------- | ------------------------ | ---------------------------------- | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| is_public | boolean | Whether thread is public | -| is_removed | boolean | Whether thread has been removed | -| text | jsonb | Text content (rich format) | -| title | character varying(255) | Thread title | -| created_by_id | integer | User who created the thread | -| paper_id | integer | Related paper | -| was_edited | boolean | Whether thread was edited | -| plain_text | text | Plain text version | -| source | character varying(32) | Source of the thread | -| score | integer | Thread score | -| discussion_post_type | character varying(16) | Type of discussion post | -| is_accepted_answer | boolean | Whether marked as accepted answer | - -## Content Types - -### django_content_type - -**Description**: Maps content types to their IDs. - -| Column | Type | Description | -| ----------- | --------------------- | ----------------------------------- | -| id | integer | Primary key | -| app_label | character varying(100)| Application label | -| model | character varying(100)| Model name | - -## Feed System - -### feed_feedentry - -**Description**: Feed entries for user activity. - -| Column | Type | Description | -| -------------------- | ------------------------ | ---------------------------------- | -| id | bigint | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| object_id | integer | ID of feed object | -| parent_object_id | integer | ID of parent object | -| action | text | Action performed | -| action_date | timestamp with time zone | When action was performed | -| content_type_id | integer | Type of content | -| parent_content_type_id| integer | Type of parent content | -| user_id | integer | User associated with feed entry | - -## Notes System - -### note_note - -**Description**: Notes created by users. - -| Column | Type | Description | -| -------------------- | ------------------------ | ---------------------------------- | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| created_by_id | integer | User who created the note | -| latest_version_id | integer | Latest version of note content | -| organization_id | integer | Associated organization | -| unified_document_id | integer | Related unified document | -| title | text | Note title | - -### note_notecontent - -**Description**: Content of notes. - -| Column | Type | Description | -| ----------- | ---------------------- | ----------------------------------- | -| id | integer | Primary key | -| created_date| timestamp with time zone| Creation timestamp | -| src | character varying(512) | Source of content | -| plain_text | text | Plain text version | -| note_id | integer | Related note | -| json | jsonb | JSON content | - -## Purchase System - -### purchase_balance - -**Description**: User balances. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| object_id | integer | Associated object ID | -| amount | character varying(255) | Balance amount | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| content_type_id| integer | Type of content | -| user_id | integer | Associated user | -| testnet_amount | character varying(255) | Testnet balance amount | - -### purchase_fundraise - -**Description**: Fundraising campaigns. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| status | character varying(32) | Fundraise status | -| goal_amount | numeric | Goal amount | -| goal_currency | character varying(16) | Currency of goal | -| start_date | timestamp with time zone | Start date | -| end_date | timestamp with time zone | End date | -| created_by_id | integer | User who created fundraise | -| escrow_id | integer | Associated escrow | -| unified_document_id | integer | Related unified document | - -### purchase_purchase - -**Description**: User purchases. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| object_id | integer | Associated object ID | -| purchase_type | character varying(32) | Type of purchase | -| amount | character varying(255) | Purchase amount | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| content_type_id| integer | Type of content | -| user_id | integer | User who made purchase | -| purchase_hash | character varying(32) | Purchase hash | -| purchase_method| character varying(16) | Purchase method | -| transaction_hash| character varying(255) | Blockchain transaction hash | -| boost_time | double precision | Boost time | -| paid_date | timestamp with time zone | Payment date | -| paid_status | character varying(255) | Payment status | -| group_id | integer | Group ID for batch purchases | - -## Reputation System - -### reputation_score - -**Description**: User reputation scores. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| score | integer | Reputation score value | -| author_id | integer | Related author | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| hub_id | integer | Related hub | - -### reputation_scorechange - -**Description**: Changes to reputation scores. - -| Column | Type | Description | -| -------------------- | ------------------------ | ---------------------------------- | -| id | integer | Primary key | -| updated_date | timestamp with time zone | Last update timestamp | -| algorithm_version | integer | Algorithm version | -| score_after_change | integer | Score after change | -| score_change | integer | Amount of change | -| raw_value_change | integer | Raw value change | -| changed_object_id | integer | Changed object ID | -| changed_object_field | character varying(100) | Field that changed | -| variable_counts | jsonb | Variable counts | -| created_date | timestamp with time zone | Creation timestamp | -| algorithm_variables_id| integer | Algorithm variables | -| changed_content_type_id| integer | Changed content type | -| score_id | integer | Related score | - -### reputation_distribution - -**Description**: Reputation distribution transactions. - -| Column | Type | Description | -| ---------------- | ------------------------ | ------------------------------------- | -| id | integer | Primary key | -| amount | integer | Amount distributed | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| sender_id | integer | User sending reputation | -| recipient_id | integer | User receiving reputation | -| content_type_id | integer | Type of content | -| object_id | integer | ID of related object | -| distribution_type| character varying | Type of distribution | - -### reputation_bounty - -**Description**: Bounties for content contributions. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| amount | integer | Bounty amount | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| creator_id | integer | User who created bounty | -| content_type_id| integer | Type of content | -| object_id | integer | ID of related object | -| expires_date | timestamp with time zone | Expiration date | -| status | character varying | Bounty status | - -### reputation_escrow - -**Description**: Escrow for reputation transactions. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| hold_type | character varying(16) | Type of hold | -| amount_holding | numeric | Amount in escrow | -| object_id | integer | Associated object ID | -| status | character varying(16) | Escrow status | -| content_type_id| integer | Type of content | -| created_by_id | integer | User who created escrow | -| bounty_fee_id | integer | Associated bounty fee | -| amount_paid | numeric | Amount paid out | - -### reputation_escrowrecipients - -**Description**: Recipients of escrow payouts. - -| Column | Type | Description | -| -------------- | -------------------------- | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| amount | numeric | Amount to receive | -| escrow_id | integer | Associated escrow | -| user_id | integer | User receiving payout | - -### reputation_withdrawal - -**Description**: Withdrawal of reputation tokens. - -| Column | Type | Description | -| ---------------- | ------------------------ | ------------------------------------- | -| id | integer | Primary key | -| from_address | character varying(255) | From address | -| to_address | character varying(255) | To address | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| paid_date | timestamp with time zone | Payment date | -| transaction_hash | character varying(255) | Transaction hash | -| user_id | integer | Associated user | -| is_removed | boolean | Whether withdrawal is removed | -| is_removed_date | timestamp with time zone | When withdrawal was removed | -| paid_status | character varying(255) | Payment status | -| token_address | character varying(255) | Token address | -| amount | character varying(255) | Withdrawal amount | -| fee | character varying(255) | Withdrawal fee | -| is_public | boolean | Whether withdrawal is public | -| network | character varying(10) | Blockchain network | - -## Document System - -### researchhub_document_researchhubunifieddocument - -**Description**: Unified document model combining different content types. - -| Column | Type | Description | -| ------------------ | ------------------------ | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| document_type | character varying(32) | Type of document | -| score | integer | Document score | -| is_removed | boolean | Whether document is removed | -| published_date | timestamp with time zone | Publication date | -| is_public | boolean | Whether document is public | -| hot_score | integer | Hot score for trending | -| document_filter_id | integer | Associated document filter | -| is_removed_date | timestamp with time zone | When document was removed | - -### researchhub_document_researchhubpost - -**Description**: Posts in ResearchHub. - -| Column | Type | Description | -| ------------------ | ------------------------ | ------------------------------------ | -| id | integer | Primary key | -| version_number | integer | Version number | -| prev_version_id | integer | Previous version ID | -| unified_document_id| integer | Related unified document | -| created_date | timestamp with time zone | Creation timestamp | -| discussion_src | character varying(512) | Discussion source | -| editor_type | character varying(32) | Type of editor | -| eln_src | character varying(512) | ELN source | -| renderable_text | text | Renderable text content | -| updated_date | timestamp with time zone | Last update timestamp | -| title | text | Post title | -| document_type | character varying(32) | Type of document | -| created_by_id | integer | User who created post | -| preview_img | character varying(200) | Preview image | -| discussion_count | integer | Count of discussions | -| slug | character varying(1024) | URL-friendly slug | -| note_id | integer | Associated note | -| doi | character varying(255) | Digital Object Identifier | -| bounty_type | character varying(64) | Type of bounty | -| score | integer | Post score | - -### researchhub_document_unifieddocumentconcepts - -**Description**: Concepts associated with unified documents. - -| Column | Type | Description | -| ------------------ | ------------------------ | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| relevancy_score | double precision | Relevancy score | -| level | integer | Concept level | -| concept_id | integer | Associated concept | -| unified_document_id| integer | Related unified document | - -## Reviews - -### review_review - -**Description**: Reviews of content. - -| Column | Type | Description | -| ------------------ | ------------------------ | ------------------------------------ | -| id | integer | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| score | double precision | Review score | -| created_by_id | integer | User who created review | -| unified_document_id| integer | Related unified document | -| is_removed | boolean | Whether review is removed | -| is_public | boolean | Whether review is public | -| is_removed_date | timestamp with time zone | When review was removed | -| content_type_id | integer | Type of content | -| object_id | integer | ID of reviewed object | - -## Topics - -### topic_topic - -**Description**: Research topics. - -| Column | Type | Description | -| --------------------- | ------------------------ | ------------------------------------ | -| id | bigint | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| openalex_id | character varying(255) | OpenAlex ID | -| display_name | text | Display name | -| works_count | integer | Count of works | -| cited_by_count | integer | Count of citations | -| keywords | ARRAY | Keywords array | -| openalex_updated_date | timestamp with time zone | OpenAlex update timestamp | -| subfield_id | bigint | Associated subfield | -| openalex_created_date | timestamp with time zone | OpenAlex creation timestamp | - -### topic_subfield - -**Description**: Research subfields. - -| Column | Type | Description | -| --------------------- | ------------------------ | ------------------------------------ | -| id | bigint | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| openalex_id | character varying(255) | OpenAlex ID | -| display_name | text | Display name | -| field_id | bigint | Associated field | - -### topic_unifieddocumenttopics - -**Description**: Association between documents and topics. - -| Column | Type | Description | -| ------------------ | ------------------------ | ------------------------------------ | -| id | bigint | Primary key | -| created_date | timestamp with time zone | Creation timestamp | -| updated_date | timestamp with time zone | Last update timestamp | -| relevancy_score | double precision | Relevancy score | -| topic_id | bigint | Associated topic | -| unified_document_id| integer | Related unified document | -| is_primary | boolean | Whether topic is primary | diff --git a/.cursor/rules/design-system.mdc b/.cursor/rules/design-system.mdc deleted file mode 100644 index c09f8a2d9..000000000 --- a/.cursor/rules/design-system.mdc +++ /dev/null @@ -1,236 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- - # ResearchHub Design System - -This document outlines the design system and styling guidelines used in the ResearchHub codebase. - -## Styling Approach - -ResearchHub uses Tailwind CSS as the primary styling solution, complemented by CSS modules for complex components when necessary. - -1. **Tailwind CSS**: - - Use Tailwind utility classes for most styling needs - - Follow the "utility-first" approach - - Use Tailwind's built-in responsive classes for responsive design - -2. **CSS Modules**: - - Use CSS modules for complex components that require custom animations or styles that are difficult to achieve with Tailwind - - Name CSS module files with the `.module.css` extension - -3. **Styling Utilities**: - - Use the `cn()` utility function for conditional class name composition - - Use `class-variance-authority` (cva) for component variants - -## Color System - -1. **Color Palette**: - - Use the color tokens defined in the Tailwind configuration - - Primary colors: Shades of indigo and blue - - Secondary colors: Gray scale and orange/yellow for accent - - Semantic colors: Success (green), warning (yellow), error (red), info (blue) - - ```tsx - // Use semantic color tokens -
Primary button
-
Secondary button
-
Error state
- ``` - -2. **Color Usage**: - - Use primary colors for primary actions and branding - - Use gray scale for UI structure, backgrounds, and secondary elements - - Use semantic colors consistently for their respective meanings - - Maintain appropriate contrast ratios for accessibility (4.5:1 minimum) - -## Typography - -1. **Font Family**: - - Primary font: Inter (sans-serif) - - Heading font: Cal Sans for titles and large headings - - Monospace font: For code blocks and technical content - -2. **Font Sizes**: - - Follow the Tailwind CSS font size scale - - Use relative units (rem) for font sizes to support user preferences - - ```tsx -

Page Title

-

Section Heading

-

Body text

- Caption text - ``` - -3. **Font Weights**: - - Regular (400) for body text - - Medium (500) for semi-emphasis - - Semibold (600) for headings and emphasis - - Bold (700) for strong emphasis - -4. **Line Heights**: - - Use appropriate line heights for readability - - Body text: `leading-normal` (1.5) - - Headings: `leading-tight` (1.25) - - Single-line elements: `leading-none` (1) - -## Spacing - -1. **Spacing Scale**: - - Follow the Tailwind CSS spacing scale - - Use consistent spacing for margins and padding - - Use the spacing scale for gaps, grid gaps, and other spacing properties - - ```tsx -
-
-

Section Title

-

Content

-
-
- ``` - -2. **Layout Spacing**: - - Page margins: px-4 sm:px-6 md:px-8 lg:px-12 - - Section spacing: my-8 md:my-12 lg:my-16 - - Component spacing: p-4 for cards, p-2 for smaller components - -## Components - -1. **Base Components**: - - Use the components in the `components/ui` directory as the foundation - - Common components include Button, Input, Card, Badge, and more - - All components are built with accessibility in mind - -2. **Component Variants**: - - Components have consistent variants using `class-variance-authority` - - Common variants include size, color, and emphasis level - - ```tsx - // Button with variants - - - ``` - -3. **Component Composition**: - - Build complex UI by composing smaller components - - Use the children prop for flexible content - - Use render props for complex rendering logic - -## Icons - -1. **Icon System**: - - Use Lucide icons as the primary icon set - - Use Font Awesome for additional icons when necessary - - Keep icons consistent in style and usage - - ```tsx - import { User, Settings, Bell } from 'lucide-react'; - - - ``` - -2. **Icon Sizing**: - - Use consistent sizing: sm (16px), md (20px), lg (24px) - - Adjust stroke width for better visibility at different sizes - - Maintain proper alignment with text using flex layout - -## Responsive Design - -1. **Breakpoints**: - - Follow Tailwind's breakpoint system: - - sm: 640px - - md: 768px - - lg: 1024px - - xl: 1280px - - 2xl: 1536px - - custom: wide (1200px), 3xl (1600px) - -2. **Mobile-first Approach**: - - Start with mobile layouts and progressively enhance for larger screens - - Use responsive utilities to adapt layout, typography, and spacing - - ```tsx -
- {/* Content */} -
- ``` - -3. **Responsive Components**: - - Design components to adapt to different screen sizes - - Use responsive variants for component properties - - Test all components across different breakpoints - -## Accessibility - -1. **Color Contrast**: - - Maintain a minimum contrast ratio of 4.5:1 for normal text - - Maintain a minimum contrast ratio of 3:1 for large text and UI components - - Test color combinations with accessibility tools - -2. **Keyboard Navigation**: - - Ensure all interactive elements are keyboard accessible - - Implement proper focus states with visible focus indicators - - Maintain a logical tab order - -3. **Screen Readers**: - - Add appropriate ARIA attributes when necessary - - Use semantic HTML elements when possible - - Provide alt text for images and aria-labels for interactive elements - -4. **Reduced Motion**: - - Respect user preferences for reduced motion - - Provide alternatives to motion-heavy interactions - - ```tsx -
- {/* Content */} -
- ``` - -## Dark Mode - -1. **Color Adaptation**: - - Use Tailwind's dark mode utilities to adapt colors - - Keep proper contrast in both light and dark modes - - Test all components in both modes - - ```tsx -
- {/* Content */} -
- ``` - -2. **Dark Mode Strategy**: - - Implement dark mode based on user preference - - Allow user toggling with persistent preference - - Ensure smooth transitions between modes - -## Animation and Interaction - -1. **Transitions**: - - Use subtle transitions for state changes - - Keep transitions short (150-300ms) - - Use appropriate easing functions - - ```tsx - - ``` - -2. **Hover and Focus States**: - - Provide clear visual feedback for hover and focus states - - Keep hover effects subtle but noticeable - - Ensure focus states meet accessibility standards - -3. **Loading States**: - - Show appropriate loading indicators for async operations - - Use skeleton loaders for content loading - - Maintain layout stability during loading states - -These design system guidelines ensure a consistent, accessible, and visually cohesive user experience throughout the ResearchHub application. \ No newline at end of file diff --git a/.cursor/rules/hooks-architecture.mdc b/.cursor/rules/hooks-architecture.mdc deleted file mode 100644 index 01732633b..000000000 --- a/.cursor/rules/hooks-architecture.mdc +++ /dev/null @@ -1,216 +0,0 @@ ---- -description: This document outlines the architectural patterns and best practices for custom React hooks in the ResearchHub codebase. -globs: -alwaysApply: false ---- - # ResearchHub Hooks Architecture - -This document outlines the architectural patterns and best practices for custom React hooks in the ResearchHub codebase. - -## Hook Structure - -1. **Naming Convention**: - - Prefix all hooks with `use` (e.g., `useVote`, `useFeed`, `useNote`) - - Use camelCase for hook names - - Name hooks based on their primary functionality or domain - -2. **Client Components**: - - Add the 'use client' directive at the top of client hooks - - Hooks that use browser-specific APIs should always have 'use client' - - ```typescript - 'use client'; - - import { useState } from 'react'; - - export const useFeature = () => { - // Hook implementation - }; - ``` - -3. **Hook Return Types**: - - For simple hooks, return an object with named properties - - For hooks with actions, use tuple pattern: `[state, action]` - - Explicitly define return types with TypeScript interfaces - - ```typescript - // Object return pattern - interface UseFeatureReturn { - data: Data[]; - isLoading: boolean; - error: Error | null; - refresh: () => void; - } - - // Tuple return pattern - type UseFeatureState = { data: Data | null; isLoading: boolean; error: Error | null }; - type UseFeatureAction = (params: ActionParams) => Promise; - type UseFeatureReturn = [UseFeatureState, UseFeatureAction]; - ``` - -## State Management - -1. **State Organization**: - - Group related state variables in a state object - - Manage loading, error, and data states consistently - - Use descriptive state variable names - - ```typescript - const useFeature = () => { - const [data, setData] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - // Hook implementation - - return { data, isLoading, error }; - }; - ``` - -2. **Error Handling**: - - Always include error state in hooks that make API calls - - Parse and transform API errors to meaningful user-facing messages - - Implement consistent error handling patterns across hooks - - ```typescript - try { - const response = await ServiceName.method(params); - setData(response); - } catch (err) { - const { data = {} } = err instanceof ApiError ? JSON.parse(err.message) : {}; - const errorMsg = data?.detail || 'Default error message'; - setError(errorMsg); - } finally { - setIsLoading(false); - } - ``` - -## Hook Composition - -1. **Separation of Concerns**: - - Create single-purpose hooks that handle one specific feature - - Compose multiple hooks together for complex functionality - - Keep each hook focused on a specific domain - - ```typescript - // In a component: - const { data: user } = useUser(userId); - const { posts, isLoading: postsLoading } = usePosts(userId); - const { comments, isLoading: commentsLoading } = useComments(userId); - ``` - -2. **Data Fetching Patterns**: - - Initialize data fetching in `useEffect` - - Provide refetch/refresh functions for manual updates - - Handle loading and error states consistently - - ```typescript - const [data, setData] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - fetchData(); - }, [dependencies]); - - const fetchData = async () => { - setIsLoading(true); - setError(null); - try { - const result = await ServiceName.getData(params); - setData(result); - } catch (error) { - setError(error); - } finally { - setIsLoading(false); - } - }; - ``` - -3. **Pagination and Infinite Scrolling**: - - Implement consistent pagination patterns - - Track page number, hasMore flag, and loading states - - Append to existing data rather than replacing on loadMore - - ```typescript - const loadMore = async () => { - if (!hasMore || isLoading) return; - - try { - const nextPage = page + 1; - const result = await Service.getData({ page: nextPage, ...otherParams }); - setData((prev) => [...prev, ...result.items]); - setHasMore(result.hasMore); - setPage(nextPage); - } catch (error) { - console.error('Error loading more:', error); - } - }; - ``` - -## Performance Optimization - -1. **Don't memoize**: - - Properly manage dependency arrays to prevent unnecessary re-renders - -2. **Debouncing and Throttling**: - - Debounce input handlers and search functions - - Implement throttling for scroll events and frequent updates - - Clean up debounced/throttled functions in `useEffect` return - - ```typescript - const debouncedSave = useRef( - debounce((content: string) => { - saveToServer(content); - }, 500) - ).current; - - useEffect(() => { - return () => { - debouncedSave.cancel(); - }; - }, [debouncedSave]); - ``` - -## API Integration - -1. **Service Integration**: - - Use service classes for API calls (never make direct fetch calls in hooks) - - Abstract API interaction details away from component logic - - Transform API responses to match the expected UI data structure - - ```typescript - // Good - const data = await NoteService.getNote(noteId); - - // Avoid - const data = await fetch(`/api/notes/${noteId}`).then(res => res.json()); - ``` - -2. **Optimistic Updates**: - - Implement optimistic updates for better user experience - - Store previous state for rollback in case of errors - - Update UI immediately, then confirm with API response - - ```typescript - const addItem = async (newItem: Item) => { - // Optimistically update UI - const previousItems = [...items]; - setItems((prev) => [...prev, { ...newItem, id: 'temp-id' }]); - - try { - // Actual API call - const savedItem = await ItemService.addItem(newItem); - // Update with real data from server - setItems((prev) => prev.map(item => - item.id === 'temp-id' ? savedItem : item - )); - } catch (error) { - // Rollback on error - setItems(previousItems); - setError(error); - } - }; - ``` - -These patterns ensure consistent, maintainable, and performant hooks throughout the ResearchHub application. \ No newline at end of file diff --git a/.cursor/rules/mobile-optimization-strategy.mdc b/.cursor/rules/mobile-optimization-strategy.mdc deleted file mode 100644 index a20f84025..000000000 --- a/.cursor/rules/mobile-optimization-strategy.mdc +++ /dev/null @@ -1,268 +0,0 @@ ---- -description: When applying buttons or styles that depend on screen size, or mobile, or having issues with staging compared to local -globs: -alwaysApply: false ---- -# Mobile Optimization Strategy - -This document outlines the mobile optimization strategy developed for ResearchHub landing page components to address environment-specific responsive layout issues. - -## Problem Context - -### Root Issue Identified -- **Problem**: Responsive Tailwind classes (`sm:`, `md:`, etc.) weren't compiling properly on staging despite JavaScript correctly detecting screen sizes -- **Symptoms**: - - Buttons stacked vertically and went full-width on staging - - Same code displayed correctly on localhost - - Debug tools showed correct screen size detection but wrong visual rendering -- **Root Cause**: Environment-specific CSS compilation/loading issue affecting responsive class generation - -## Core Solution Strategy - -Replace responsive breakpoints with environment-agnostic approaches that work consistently across all deployment environments. - -### 1. Button Standardization -```tsx -// ❌ Avoid: Custom responsive button implementations - -

Description

- -
- -

Description

-
- -``` - -### Single Button Layout -```tsx -// For features with single action -
- -

Description

-
-``` - -### Benefits Grid Layout -```tsx -// Replace responsive grids with flex-wrap -
- {benefits.map((benefit, index) => ( -
-
- {benefit} -
- ))} -
-``` - -### Navigation Tabs -```tsx -// Environment-agnostic tab navigation -
- {features.map((feature, index) => ( - - ))} -
-``` - -## Mobile-Specific Optimizations - -### Icon Layout Prevention -```tsx -// ❌ Avoid: Horizontal layouts that can distort icons on mobile -
- -
Content
-
- -// ✅ Preferred: Vertical layouts for mobile-friendly icons -
-
- -
-
Content
-
-``` - -### Compact Data Display -```tsx -// For price/data displays that need to fit on mobile -
-
-
- Label -
-
$0.33
-
- -
- -
-
- Change -
-
+12.5%
-
-
-``` - -### Text Optimization for Mobile -- Shorten navigation labels: "Align Incentives" → "Incentives" -- Reduce header sizes: `text-5xl` → `text-4xl` for better vertical fit -- Remove redundant text: "USD" when "$" symbol is present - -## Technical Principles - -### 1. Environment Independence -- Avoid any CSS that could behave differently between localhost and staging -- Use compilation-independent approaches -- Test across multiple environments before deployment - -### 2. Natural Responsiveness -- Leverage intrinsic sizing (`min-w-`, `max-w-`, `flex-1`) -- Use flex-wrapping instead of explicit breakpoints -- Allow content to determine layout naturally - -### 3. Component Consistency -- Utilize existing design system components (Button, SpotlightCard) -- Maintain consistent spacing and typography scales -- Follow established component patterns - -### 4. Progressive Enhancement -- Design mobile-first with natural scaling up -- Ensure core functionality works without responsive classes -- Add enhancements that don't break base experience - -## Testing Strategy - -### Environment Testing -1. Test on localhost with various screen sizes -2. Deploy to staging and verify identical behavior -3. Use browser dev tools to simulate different devices -4. Test with network throttling to catch loading issues - -### Debug Techniques -```tsx -// Screen size detection for debugging -const [screenWidth, setScreenWidth] = useState(0); - -useEffect(() => { - const updateWidth = () => setScreenWidth(window.innerWidth); - updateWidth(); - window.addEventListener('resize', updateWidth); - return () => window.removeEventListener('resize', updateWidth); -}, []); - -// Visual breakpoint indicator -
- {screenWidth}px -
-``` - -## Common Anti-Patterns to Avoid - -### ❌ Responsive Class Dependencies -```tsx -// These can fail in certain build environments -className="w-full sm:w-auto" -className="grid grid-cols-1 md:grid-cols-2" -className="text-lg sm:text-xl md:text-2xl" -``` - -### ❌ Complex Breakpoint Logic -```tsx -// Overly complex responsive behavior -className="flex flex-col sm:flex-row md:flex-col lg:flex-row" -``` - -### ❌ Environment-Specific Assumptions -```tsx -// Assuming responsive classes will always compile correctly -const isMobile = useMediaQuery('(max-width: 768px)'); // JS works -// But corresponding CSS classes might not compile on staging -``` - -## Success Metrics - -- **Consistency**: Identical behavior across localhost and staging -- **Mobile Performance**: Natural stacking and appropriate sizing on mobile devices -- **Maintainability**: Reduced complexity in responsive logic -- **Reliability**: No environment-specific layout failures - -## Implementation Checklist - -- [ ] Replace custom buttons with Button component -- [ ] Convert responsive grids to flex-wrap layouts -- [ ] Remove responsive typography classes -- [ ] Test across multiple environments -- [ ] Verify mobile layout behavior -- [ ] Ensure natural content wrapping -- [ ] Validate icon and image sizing -- [ ] Check navigation usability on mobile -- [ ] Test loading states and transitions -- [ ] Verify accessibility across screen sizes - -This strategy ensures robust, environment-agnostic mobile optimization that works consistently across all deployment environments while maintaining excellent user experience on mobile devices. diff --git a/.cursor/rules/performance-optimization.mdc b/.cursor/rules/performance-optimization.mdc deleted file mode 100644 index 4996136e7..000000000 --- a/.cursor/rules/performance-optimization.mdc +++ /dev/null @@ -1,244 +0,0 @@ ---- -description: This document outlines the performance optimization strategies used in the ResearchHub codebase. -globs: -alwaysApply: false ---- - # ResearchHub Performance Optimization - -This document outlines the performance optimization strategies used in the ResearchHub codebase. - -## React Optimization - - -2. **Hook Optimization**: - - Use `useCallback()` for functions passed as props - - Always specify dependencies correctly in dependency arrays - - ```tsx - - // Event handler passed as prop - const handleEdit = useCallback((id: string) => { - // Edit logic - }, [/* dependencies */]); - ``` - -3. **State Management**: - - Split large state objects into smaller, more focused pieces - - Use context selectors to prevent unnecessary re-renders - - Consider using libraries like Zustand or Jotai for complex state management - -## Next.js Optimization - -1. **Image Optimization**: - - Use Next.js `Image` component for all images - - Specify width and height to prevent layout shifts - - Use appropriate loading strategies (`lazy` for below-the-fold content) - - ```tsx - import Image from 'next/image'; - - // Good usage - User profile - ``` - -2. **Code Splitting**: - - Use dynamic imports for large components and libraries - - Implement page-level code splitting with Next.js's built-in features - - ```tsx - // Dynamic import for a large component - const HeavyChart = dynamic(() => import('@/components/HeavyChart'), { - loading: () => , - ssr: false, // If not needed on the server - }); - ``` - -3. **Server Components**: - - Use React Server Components for data fetching and rendering static content - - Keep client components small and focused on interactive elements - -4. **Font Optimization**: - - Use Next.js font optimization to reduce CLS - - Preload critical fonts - - ```tsx - import { Inter } from 'next/font/google'; - - const inter = Inter({ - subsets: ['latin'], - display: 'swap', - }); - ``` - -## Rendering Optimization - -1. **List Virtualization**: - - Implement virtualization for long lists (>50 items) - - Consider using libraries like `react-window` or `react-virtualized` - - ```tsx - import { FixedSizeList } from 'react-window'; - - const VirtualizedList = ({ items }) => ( - - {({ index, style }) => ( -
- {items[index].name} -
- )} -
- ); - ``` - -2. **Debouncing and Throttling**: - - Debounce search inputs and form fields that trigger API calls - - Throttle scroll and resize event handlers - - ```tsx - import { useState, useEffect } from 'react'; - import { debounce } from 'lodash-es'; - - const SearchInput = () => { - const [value, setValue] = useState(''); - - useEffect(() => { - const handleSearch = debounce((term: string) => { - // Perform search - }, 300); - - if (value) handleSearch(value); - - return () => { - handleSearch.cancel(); - }; - }, [value]); - - return ( - setValue(e.target.value)} - /> - ); - }; - ``` - -3. **Lazy Loading**: - - Implement lazy loading for below-the-fold content - - Use intersection observer for efficient detection - - ```tsx - import { useEffect, useRef, useState } from 'react'; - - const LazyLoadedComponent = () => { - const [isVisible, setIsVisible] = useState(false); - const ref = useRef(null); - - useEffect(() => { - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting) { - setIsVisible(true); - observer.disconnect(); - } - }, - { rootMargin: '100px' } - ); - - if (ref.current) { - observer.observe(ref.current); - } - - return () => { - observer.disconnect(); - }; - }, []); - - return ( -
- {isVisible ? : } -
- ); - }; - ``` - -## Network Optimization - - -1. **Optimistic Updates**: - - Implement optimistic UI updates for better user experience - - Always handle failure cases gracefully - - ```tsx - import { useMutation, useQueryClient } from '@tanstack/react-query'; - - const UpdateButton = ({ user }) => { - const queryClient = useQueryClient(); - - const mutation = useMutation({ - mutationFn: updateUser, - onMutate: async (newUser) => { - await queryClient.cancelQueries(['user', user.id]); - const previousUser = queryClient.getQueryData(['user', user.id]); - queryClient.setQueryData(['user', user.id], newUser); - return { previousUser }; - }, - onError: (err, newUser, context) => { - queryClient.setQueryData( - ['user', user.id], - context.previousUser - ); - }, - onSettled: () => { - queryClient.invalidateQueries(['user', user.id]); - }, - }); - - return ( - - ); - }; - ``` - -3. **API Response Size**: - - Request only necessary data from APIs - - Implement pagination for large datasets - -## Bundle Optimization - -1. **Tree Shaking**: - - Import only what you need from libraries - - Use selective imports for large libraries - - ```tsx - // Good - only imports what's needed - import { Button } from '@/components/ui'; - import { formatDate } from '@/utils/date'; - - // Bad - imports the entire library - import * as UI from '@/components/ui'; - import * as Utils from '@/utils'; - ``` - -2. **Bundle Analysis**: - - Regularly analyze bundle size with tools like `@next/bundle-analyzer` - - Optimize or replace large dependencies - -3. **Code Splitting**: - - Split code at the route level - - Use dynamic imports for features not needed on initial load - -These performance optimization guidelines ensure that the ResearchHub application provides a smooth and responsive user experience. \ No newline at end of file diff --git a/.cursor/rules/project-structure.mdc b/.cursor/rules/project-structure.mdc deleted file mode 100644 index 21417b532..000000000 --- a/.cursor/rules/project-structure.mdc +++ /dev/null @@ -1,70 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- - # ResearchHub Project Structure Guidelines - -This document outlines the structure and organization of the ResearchHub codebase to help ensure consistency and maintainability. - -## Directory Structure - -The project follows a standard Next.js 15 application structure with TypeScript: - -- `app/` - Contains the App Router and related components (Next.js 15 page structure) -- `pages/` - Contains the legacy Pages Router components -- `components/` - Contains reusable UI components - - `ui/` - Contains base UI components (buttons, inputs, etc.) - - `[Feature]/` - Feature-specific components (organized by domain) -- `hooks/` - Custom React hooks -- `utils/` - Helper functions and utilities -- `services/` - API service functions -- `contexts/` - React context providers -- `public/` - Static assets -- `store/` - State management -- `types/` - TypeScript type definitions -- `constants/` - Application constants and configuration - -## Naming Conventions - -Follow these naming conventions throughout the codebase: - -1. **Files and Directories**: - - React components: PascalCase (e.g., `Button.tsx`, `UserProfile.tsx`) - - Utility files: camelCase (e.g., `formatDate.ts`, `apiHelpers.ts`) - - Test files: Same name as the file being tested with `.test` or `.spec` (e.g., `Button.test.tsx`) - -2. **Component Organization**: - - Group related components in feature-specific directories - - Place shared/generic UI components in the `components/ui/` directory - - Create index files for easier imports - -## Import Order - -Maintain a consistent import order in all files: - -1. React and Next.js imports -2. Third-party library imports -3. Component imports -4. Hook imports -5. Utility/helper imports -6. Type imports -7. Asset imports (styles, images, etc.) - -## Code Organization - -- Keep files reasonably sized (aim for <300 lines where possible) -- Extract reusable logic into custom hooks -- Extract complex or reusable UI elements into dedicated components - -## Path Aliases - -Use the configured path aliases to maintain clean imports: - -- `@/components` - For component imports -- `@/hooks` - For hook imports -- `@/utils` - For utility imports -- `@/types` - For type imports -- etc. - -This structure ensures code is organized logically and consistently throughout the project. \ No newline at end of file diff --git a/.cursor/rules/senior-eng-review.mdc b/.cursor/rules/senior-eng-review.mdc deleted file mode 100644 index d8c9dac2b..000000000 --- a/.cursor/rules/senior-eng-review.mdc +++ /dev/null @@ -1,374 +0,0 @@ ---- -description: This is a document that provides key PR reviewing as if they were a senior engineer -globs: ---- -# Key Learnings from Senior Engineer Code Review - -1. **Leverage React Providers for Global State** - - - Using context providers (like the ExchangeRateContext) is an effective way to share data application-wide. - - Encapsulating fetch logic within these providers or in dedicated hooks can simplify the rest of the codebase and improve maintainability. - -2. **Separate Concerns Between Services and View Layer** - - - Keep the "services" layer focused on fetching and basic data transformations. - - Avoid placing view-specific formatting (e.g., icon lookup or UI-ready strings) inside services—this leads to entanglement and potential reusability issues. - - Move highly UI-related transforms (for example, mapping distribution types to icons) into a dedicated "lib" or the "view layer" (e.g., inside component utilities or dedicated functions). - -3. **Use Dedicated Transformers Carefully** - - - Keep data transformations minimal and "model-oriented" in services, returning clean, typed data. - - Perform purely presentational transformations (e.g., formatting numeric values or currency strings) within the view layer to reduce the likelihood of unnecessary re-fetching or tight coupling. - -4. **Consider Creating Custom Hooks for Data Fetching** - - - Custom hooks (like "useUserBalance") can abstract away internal fetch logic and state management, similar to how "useSession" works in Next.js. - - This pattern improves readability, makes the code more composable, and centralizes asynchronous logic behind a well-defined interface. - -5. **Watch Out for Unnecessary Re-Renders and Side Effects** - - - Multiple data retrieval triggers can cause performance issues or data inconsistencies if not handled carefully. - - Use checks to ensure that data is only fetched when needed. Employ memoization or context effectively to prevent repetitive fetch calls. - -6. **Embrace a Layered Architecture** - - - A "service" layer for API interactions. - - A "types/model" layer to define and represent data structures. - - A "view/presentation" layer (React components) that handles all UI and formatting. - - This structure is analogous to classic architectural patterns (e.g., "MVC") and ensures a more scalable and maintainable application over time. - -7. **Balance Raw Data vs. Derived Data** - - - Keep a reference to the raw data (unformatted, untransformed) for potential debugging and logic needs. - - Derive only what's necessary for display in the UI to avoid confusion or data loss. - -8. **Consistent, Incremental Refinement** - - - Small refactors—such as relocating formatting functions and hooking into contexts—can make a significant difference in clarity and scalability. - - Regular feedback loops and code reviews help solidify best practices and ensure the application remains well-structured as it grows. - -9. **Minimal Client-Side Caching and Centralized Data** - - - Use React Context for global or shared data rather than implementing multiple fetch calls or storing redundant data in each component. - - Avoid heavy local caching or memoization beyond what React already provides; excessive caching can introduce staleness bugs and complicate the code. - - Avoid React Query. - - Fetch data once (for instance, in a provider) and rely on that single source of truth across your application. - - Keep caching boundaries small: if data is needed by multiple components, lifting it into Context makes sense; otherwise, store it locally. - - Be mindful of Next.js server/client boundaries; avoid turning everything into a client-side component, as it diminishes server-rendering advantages. - -10. **Keep File and Folder Names Consistent** - - - Use a clear and consistent naming scheme for files and folders. For example, if you have a component for an item in a list, call it `TransactionListItem.tsx`, not `TransactionItem.tsx`. - - For directories, use lowercase (e.g., `services`, `skeletons`) and consider capitalization for React component files. - -11. **Use Descriptive Names That Reflect Purpose** - - - Choose intuitive front-end labels that reflect the actual functionality; avoid ambiguous terms. - - If you have a button that exports data, label it "Export CSV" or "Export," rather than something vague like "export by year." - -12. **DTO vs. "Plain" Types** - - - In service files, use type names like `SomethingApiResponse` or `SomethingDto` to indicate it's a direct mapping from an API payload. - - For app-wide types (after you've transformed them), use straightforward names like `Transaction` or `Notification` that don't reference the API layer. - -13. **Keep Function Names Action-Oriented** - - - Use verbs and descriptive phrases, like `markAllAsRead()` instead of `mark()`. - - For fetching or transforming data, name them accordingly, e.g. `getTransactions()` or `transformTransaction()`. - -14. **Use Short but Unambiguous Events or Handlers** - - - Handlers should be clear about which user action they handle, such as `onClickDeposit` or `handleDeposit`. - - Keep them concise: `onClose`, `onClick`, `handleSubmit`, etc. - -15. **Simplify Type Organization and API Integration** - - - Avoid using `.dto` files for API types unless specifically needed (e.g., authentication). - - Maintain two primary files for each domain: - 1. A service file (e.g., `services/organization.service.ts`) handling API calls - 2. A types file (e.g., `types/organization.ts`) containing domain types and transformers - - Use `any` type for raw API responses in service files since we don't control the API - - Define clean, transformed types in the types directory that match our application's needs - - Implement defensive transformers with null checks (e.g., `response.items || []`) - - Keep service methods focused on API calls and basic error handling - - Return transformed, application-specific types from service methods - - Consider adding specific error classes for each service domain - -16. **State Management Best Practices** - - - Be cautious with object dependencies in useEffect hooks - - Remember that object reassignment (even with same values) triggers effects due to reference changes - - Use additional state variables to prevent unnecessary re-fetching when dealing with session-like objects - - Consider implementing fetch flags or other mechanisms to prevent duplicate API calls - -17. **Type Naming Conventions** - - - Avoid prefixes like "Formatted" in type names - - Use clear, domain-focused names (e.g., `Organization` instead of `FormattedOrganization`) - - Keep type definitions close to their domain context - - Use transformer functions to convert API data to application types - -18. **Error Handling Philosophy** - - - Let errors bubble up naturally unless there's a specific need to handle them - - Remove unnecessary try-catch blocks that just re-throw errors - - Fail fast when data is invalid rather than trying to recover silently - - Use try-catch only when: - 1. You can meaningfully recover from the error - 2. You need to transform the error type - 3. The API doesn't return data in the expected format - - For API interactions: - 1. Always use try-catch to transform technical errors into domain-specific ones - 2. Ensure errors are followed by user-facing alerts (even if implementation is deferred) - 3. Keep error messages user-friendly but specific enough for debugging - -19. **Debugging and Logging** - - - Avoid committing console.log statements to production code - - Use proper error tracking and monitoring tools instead - - If logging is needed, implement structured logging with proper levels - - Consider adding debug flags for development-only logging - -20. **Component Naming Best Practices** - - - Name components based on their primary function, not their location or styling - - Use action-oriented names for interactive components (e.g., `OrganizationSwitcher` instead of `OrganizationHeader`) - - Consider the component's role in the larger application context - - Examples: - - ✅ `OrganizationSwitcher` (describes action/purpose) - - ❌ `OrganizationHeader` (describes location/styling) - - ✅ `NoteList` (describes content type) - - ❌ `SidebarContent` (too generic/location-based) - -21. **Avoid Premature Library Adoption** - - - Carefully evaluate third-party libraries before adoption, even popular ones like React Query. - - Consider future architecture plans (e.g., potential GraphQL migration) when making library choices - - Prefer built-in React patterns (contexts, custom hooks) for data fetching until complexity justifies additional abstractions - - Development-mode double rendering (from React Strict Mode) is expected; validate actual issues in staging/production before optimizing - - -22. **URL as Source of Truth** - - - Use URL parameters as the primary source of truth for route-dependent state - - Instead of managing parallel state that needs to stay in sync with the URL: - ```typescript - // ❌ Don't sync state manually - const [currentOrg, setCurrentOrg] = useState(null); - useEffect(() => { - setCurrentOrg(findOrgBySlug(params.orgSlug)); - }, [params.orgSlug]); - - // ✅ Derive state from URL directly - const params = useParams(); - const currentOrg = organizations.find(o => o.slug === params.orgSlug); - ``` - - Let the URL drive state changes rather than managing duplicate state - - Avoid storing URL-derived data in local state unless absolutely necessary - -23. **Provider-Based State Management** - - - Design providers to be self-contained and autonomous: - ```typescript - // ❌ Don't expose state management methods - const OrgContext = { - selectedOrg, - setSelectedOrg, - syncWithUrl, // Avoid external sync methods - }; - - // ✅ Handle state management internally - const OrgContext = { - selectedOrg, // Only expose read-only state - organizations, - isLoading, - }; - ``` - - Providers should manage their own state synchronization - - Components should not need to know how provider state is managed - - Minimize the API surface of providers to prevent misuse - -24. **Component State Dependencies** - - - Be explicit about component state dependencies: - ```typescript - // ❌ Don't depend on multiple sources of truth - if (isLoadingNotes || !note || orgSlug !== selectedOrg?.slug) { - return ; - } - - // ✅ Use derived state from a single source - const isReady = selectedOrg?.slug === orgSlug && !isLoading && note; - if (!isReady) return ; - ``` - - Avoid components having to reconcile state from multiple sources - - Let providers handle state reconciliation - - Components should consume ready-to-use state - -25. **Effect Dependencies in Providers** - - - Keep effect dependencies minimal and stable: - ```typescript - // ❌ Don't include unnecessary dependencies - useEffect(() => { - syncOrganization(selectedOrg, currentSlug); - }, [selectedOrg, organizations, currentSlug]); // Too many deps - - // ✅ Use minimal, stable dependencies - useEffect(() => { - if (!organizations.length || !currentSlug) return; - const org = organizations.find(o => o.slug === currentSlug); - setSelectedOrg(org); - }, [currentSlug, organizations]); // Only essential deps - ``` - - Providers should have clear, focused effects - - Each effect should have a single responsibility - - Use early returns to prevent unnecessary state updates - -26. **Navigation and State Updates** - - - Separate navigation from state management: - ```typescript - // ❌ Don't couple navigation with state updates - const handleOrgSelect = async (org) => { - setSelectedOrg(org); - await router.push(`/org/${org.slug}`); - }; - - // ✅ Handle navigation only, let providers manage state - const handleOrgSelect = (org) => { - router.push(`/org/${org.slug}`); - }; - ``` - - Let URL changes drive state updates through providers - - Components should focus on navigation and UI interactions - - Avoid mixing navigation logic with state management - - These patterns help prevent: - - Duplicate API calls from multiple state syncs - - Race conditions in state updates - - Unnecessary component re-renders - - Complex state management in components - - -27. **Smart Orchestration Layer** - - - Use a smart orchestration layer between services and view components: - ```typescript - // ❌ Don't fetch data directly in view components - const NoteEditor = () => { - const { note } = useNote(noteId); - return ; - }; - - // ✅ Use a smart orchestrator to manage data flow - const NotePage = () => { - const { notes } = useOrganizationNotes(); - const initialNote = notes.find(n => n.id === noteId); - const [shouldFetchContent, setShouldFetchContent] = useState(false); - - // Control when to fetch full content - useEffect(() => { - if (initialNote) setShouldFetchContent(true); - }, [initialNote?.id]); - - return ; - }; - ``` - - Orchestration layer responsibilities: - 1. Coordinate data dependencies - 2. Control fetch timing - 3. Handle loading states - 4. Transform data for view layer - - Benefits: - - Prevents duplicate API calls - - Provides better loading UX - - Keeps view components simple - - Makes data flow explicit - -28. **Progressive Data Loading** - - - Load data in stages based on what the user needs immediately: - ```typescript - // ❌ Don't fetch everything upfront - const { fullNote } = useNote(noteId); - - // ✅ Load progressively - const { metadata } = useNotesList(); // Quick list view - const { content } = useNoteContent(shouldLoad ? id : null); // Full content when needed - ``` - - Implementation patterns: - 1. Start with minimal metadata - 2. Show immediate UI feedback - 3. Load full content when needed - 4. Update UI progressively - - Key principles: - - Show something to users as quickly as possible - - Only fetch what's needed when it's needed - - Use loading states to maintain UI responsiveness - - Let the orchestration layer control the loading sequence - -29. **Data Flow Control** - - - Manage data flow through clear boundaries: - ```typescript - // ❌ Don't mix concerns - const Component = () => { - const [data, setData] = useState(null); - useEffect(() => { - fetchData().then(setData); - }, [id]); - }; - - // ✅ Separate concerns with clear boundaries - // Service layer - Data fetching - const useNoteService = (id) => { - return NoteService.getNote(id); - }; - - // Orchestration layer - Flow control - const NotePage = () => { - const { initialNote } = useNotesList(); - const { fullNote } = useNoteContent(whenReady ? id : null); - }; - - // View layer - Display only - const NoteEditor = ({ content, isLoading }) => { - return ; - }; - ``` - - Key principles: - - Services handle raw data fetching - - Orchestration controls data flow - - Views handle presentation only - - Each layer has a single responsibility - - 30. **URL-Driven Navigation Patterns** - - - Handle navigation state changes in a predictable sequence: - ```typescript - // ❌ Don't mix navigation and data dependencies - const handleSwitch = async (item) => { - setSelected(item); - if (data.length) { - await router.push(`/${item.slug}/${data[0].id}`); - } else { - await router.push(`/${item.slug}`); - } - }; - - // ✅ Use conditional navigation with clear fallbacks - const handleSwitch = async (item) => { - const targetPath = data.length > 0 - ? `/${item.slug}/${data[0].id}` - : `/${item.slug}`; - await router.push(targetPath); - }; - ``` - - Key principles: - - Let URL changes trigger state updates, not the other way around - - Handle navigation edge cases (e.g., missing data) with clear fallbacks - - Keep navigation logic separate from data fetching - - Use the URL to drive the initial state of the application - - diff --git a/.cursor/rules/services-architecture.mdc b/.cursor/rules/services-architecture.mdc deleted file mode 100644 index 76cc32445..000000000 --- a/.cursor/rules/services-architecture.mdc +++ /dev/null @@ -1,227 +0,0 @@ ---- -description: This document outlines the architectural patterns and best practices for services in the ResearchHub codebase. -globs: -alwaysApply: false ---- - # ResearchHub Services Architecture - -This document outlines the architectural patterns and best practices for services in the ResearchHub codebase. - -## Service Structure - -1. **Class-based Organization**: - - Organize services as static TypeScript classes - - Group related API endpoints in domain-specific services - - Name services with the `Service` suffix (e.g., `NoteService`, `ReactionService`) - - ```typescript - export class FeatureService { - private static readonly BASE_PATH = '/api'; - - static async getItem(id: string): Promise { - // Implementation - } - - static async createItem(params: CreateItemParams): Promise { - // Implementation - } - } - ``` - -2. **API Client Pattern**: - - Use the central `ApiClient` for all HTTP requests - - Abstract HTTP methods (GET, POST, PATCH, DELETE) behind ApiClient methods - - Handle authentication, headers, and base URL configuration in ApiClient - - ```typescript - static async getItem(id: string): Promise { - const response = await ApiClient.get(`${this.BASE_PATH}/items/${id}`); - return transformItem(response); - } - ``` - -3. **Method Naming**: - - Use descriptive, action-based names for service methods - - Follow CRUD naming conventions: get, create, update, delete - - Use domain-specific verbs for specialized actions (e.g., `publish`, `flag`, `vote`) - -## Type Definitions - -1. **Request/Response Types**: - - Define clear interface types for API request parameters - - Name request parameter interfaces with `Params` suffix (e.g., `CreateNoteParams`) - - Use descriptive interface names that indicate the action - - ```typescript - export interface CreateItemParams { - title: string; - description: string; - categoryId: number; - tags?: string[]; - } - ``` - -2. **API Path Variables**: - - Store API path variables as private static readonly class properties - - Use uppercase for constant path variables - - Keep path construction logic inside service methods - - ```typescript - private static readonly BASE_PATH = '/api'; - private static readonly ITEMS_PATH = `${this.BASE_PATH}/items`; - ``` - -## Error Handling - -1. **Service-specific Errors**: - - Define service-specific error classes - - Extend the base Error class with additional properties - - Provide meaningful error messages and error codes - - ```typescript - export class ServiceNameError extends Error { - constructor( - message: string, - public readonly code?: string - ) { - super(message); - this.name = 'ServiceNameError'; - } - } - ``` - -2. **Error Propagation**: - - Catch and transform HTTP errors within service methods - - Provide consistent error formats for consumers - - Include relevant error details and status codes - - ```typescript - try { - return await ApiClient.post( - `${this.BASE_PATH}/items`, - params - ); - } catch (error) { - if (error instanceof ApiError) { - throw new ServiceNameError( - `Failed to create item: ${error.message}`, - error.status.toString() - ); - } - throw new ServiceNameError('An unexpected error occurred'); - } - ``` - -## Data Transformation - -1. **Transformation Pattern**: - - Transform API responses to match client-side data models - - Use transformer functions to convert snake_case to camelCase - - Ensure all transformed data includes proper TypeScript types - - ```typescript - static async getItem(id: string): Promise { - const response = await ApiClient.get(`${this.BASE_PATH}/items/${id}`); - return transformItem(response); - } - ``` - -2. **Transformer Organization**: - - Keep transformer functions with relevant type definitions - - Maintain consistent transformation patterns across services - - Preserve raw response data for debugging purposes - -## Request Parameters - -1. **Parameter Formatting**: - - Accept camelCase parameters in service methods - - Convert camelCase to snake_case for API requests when needed - - Handle URL parameter encoding for GET requests - - ```typescript - static async getItems(params: GetItemsParams): Promise { - const queryParams = new URLSearchParams(); - - if (params.categoryId) { - queryParams.append('category_id', params.categoryId.toString()); - } - - if (params.sortBy) { - queryParams.append('sort_by', params.sortBy); - } - - const url = `${this.ITEMS_PATH}?${queryParams.toString()}`; - const response = await ApiClient.get(url); - return transformItemsResponse(response); - } - ``` - -2. **Optional Parameters**: - - Use optional parameters with sensible defaults - - Validate required parameters before making API calls - - Provide clear error messages for missing required parameters - - ```typescript - static async searchItems(query: string, options: SearchOptions = {}): Promise { - if (!query) { - throw new ServiceNameError('Search query is required'); - } - - const queryParams = new URLSearchParams({ - q: query, - page: (options.page || 1).toString(), - limit: (options.limit || 20).toString(), - }); - - // Implementation - } - ``` - -## Documentation - -1. **JSDoc Comments**: - - Document all public service methods with JSDoc - - Include parameter descriptions, return types, and thrown exceptions - - Add examples for complex methods - - ```typescript - /** - * Fetches a specific item by ID - * @param id - The unique identifier of the item to fetch - * @returns A promise that resolves to the item - * @throws {ServiceNameError} When the request fails or the item doesn't exist - * @example - * const item = await FeatureService.getItem('123'); - */ - static async getItem(id: string): Promise { - // Implementation - } - ``` - -2. **Method Descriptions**: - - Describe the purpose of each method - - Document any side effects or special behaviors - - Include any authorization requirements - -## Service Composition - -1. **Service Boundaries**: - - Keep services focused on specific domains - - Avoid cross-service dependencies where possible - - Use composition when services need to work together - - ```typescript - // Good: Services focused on specific domains - await UserService.getProfile(userId); - await ContentService.getUserContent(userId); - - // Avoid: Services directly depending on each other - // Prefer composition at the hook or component level - ``` - -2. **File Organization**: - - One service class per file - - Group related interfaces and types with the service - - Export types needed by consumers - -These patterns ensure consistent, maintainable, and reliable services throughout the ResearchHub application. \ No newline at end of file diff --git a/.cursor/rules/state-management-architecture.mdc b/.cursor/rules/state-management-architecture.mdc deleted file mode 100644 index 61fb54b50..000000000 --- a/.cursor/rules/state-management-architecture.mdc +++ /dev/null @@ -1,391 +0,0 @@ ---- -description: This document outlines the architectural patterns and best practices for state management in the ResearchHub codebase. -globs: -alwaysApply: false ---- - # ResearchHub State Management Architecture - -This document outlines the architectural patterns and best practices for state management in the ResearchHub codebase. - -## State Management Hierarchy - -1. **Tiered State Strategy**: - - **Local Component State**: For UI-specific, ephemeral state - - **Custom Hooks**: For reusable, domain-specific state logic - - **Context Providers**: For shared, application-wide state - - **URL/Router State**: For navigation and shareable state - - ```typescript - // Local component state - const [isOpen, setIsOpen] = useState(false); - - // Custom hook for domain logic - const { data, isLoading, error } = useDocument(documentId); - - // Global context - const { notificationData, unreadCount } = useNotifications(); - - // Router state - const { params } = useParams(); - ``` - -## React Context - -1. **When to Use Context**: - - Use for state that needs to be accessed by multiple components across different parts of the component tree - - Appropriate for authentication, theme, notifications, and user preferences - - Not recommended for state that changes frequently (can cause re-renders) - - Avoid nesting too many contexts to prevent "wrapper hell" - - ```typescript - // Good use case for context: authentication state - const { status, data: session } = useSession(); - const { showAuthModal } = useAuthModalContext(); - - // Good use case for context: notifications - const { unreadCount } = useNotifications(); - ``` - -2. **Context Structure**: - - Create a dedicated file for each context (`contexts/FeatureContext.tsx`) - - Define a clear interface for the context value - - Include both state and actions that modify that state - - Provide meaningful default values - - ```typescript - interface FeatureContextType { - data: DataType[]; - isLoading: boolean; - error: Error | null; - actions: { - fetchData: () => Promise; - updateItem: (id: string, data: Partial) => Promise; - deleteItem: (id: string) => Promise; - }; - } - - const FeatureContext = createContext({ - data: [], - isLoading: false, - error: null, - actions: { - fetchData: async () => {}, - updateItem: async () => {}, - deleteItem: async () => {}, - }, - }); - ``` - -## Context Providers - -1. **Provider Implementation**: - - Use the 'use client' directive for client-side contexts - - Implement useState or useReducer for state management - - Use useCallback for functions passed to children - - Handle loading and error states consistently - - ```typescript - 'use client'; - - export function FeatureProvider({ children }: { children: ReactNode }) { - const [data, setData] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const fetchData = useCallback(async () => { - setIsLoading(true); - setError(null); - try { - const result = await FeatureService.getData(); - setData(result); - } catch (err) { - setError(err instanceof Error ? err : new Error('Failed to fetch data')); - } finally { - setIsLoading(false); - } - }, []); - - // Additional action functions using useCallback - - const value = { - data, - isLoading, - error, - actions: { - fetchData, - // Other actions - }, - }; - - return ( - - {children} - - ); - } - ``` - -2. **Context Consumer Hooks**: - - Create a custom hook for accessing each context - - Include type checking and error handling - - Use descriptive names for hooks (`useFeatureContext`) - - ```typescript - export function useFeatureContext() { - const context = useContext(FeatureContext); - if (!context) { - throw new Error('useFeatureContext must be used within a FeatureProvider'); - } - return context; - } - ``` - -## State Modeling - -1. **State Object Patterns**: - - Group related state in a single object - - Include loading, error, and data states for async operations - - Define clear interfaces for state objects - - ```typescript - interface FeatureState { - data: DataType[]; - isLoading: boolean; - error: Error | null; - page: number; - hasMore: boolean; - } - - const [state, setState] = useState({ - data: [], - isLoading: false, - error: null, - page: 1, - hasMore: true, - }); - - // Update state immutably - setState((prev) => ({ - ...prev, - data: [...prev.data, newItem], - })); - ``` - -2. **State Updates**: - - Use functional updates for state that depends on previous state - - Maintain immutability in all state updates - - Group related state updates where possible - - ```typescript - // Prefer this functional update pattern - setData((prevData) => [...prevData, newItem]); - - // For multiple related state updates - const handleSuccess = (result) => { - setData(result.data); - setHasMore(result.hasMore); - setPage(result.page); - setError(null); - setIsLoading(false); - }; - ``` - -## Server State Integration - -1. **Data Fetching Pattern**: - - Separate service layer for API calls - - Custom hooks for specific data fetching operations - - Consistent loading, error, and data state management - - ```typescript - export const useDocument = (documentId: string) => { - const [data, setData] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - const fetchDocument = useCallback(async () => { - if (!documentId) return; - - setIsLoading(true); - setError(null); - try { - const result = await DocumentService.getDocument(documentId); - setData(result); - } catch (err) { - setError(err instanceof Error ? err : new Error('Failed to fetch document')); - } finally { - setIsLoading(false); - } - }, [documentId]); - - useEffect(() => { - fetchDocument(); - }, [fetchDocument]); - - return { data, isLoading, error, refetch: fetchDocument }; - }; - ``` - -2. **Pagination and Infinite Scrolling**: - - Maintain page number or cursor in state - - Track loading state for initial load vs. loading more - - Append to existing data instead of replacing - - ```typescript - const loadMore = async () => { - if (!hasMore || isLoading) return; - - setIsLoadingMore(true); - try { - const nextPage = page + 1; - const result = await Service.getData({ page: nextPage, ...otherParams }); - setData((prev) => [...prev, ...result.items]); - setHasMore(result.hasMore); - setPage(nextPage); - } catch (error) { - console.error('Error loading more items:', error); - } finally { - setIsLoadingMore(false); - } - }; - ``` - -## Optimizing Context Performance - -1. **Context Splitting**: - - Split large contexts into smaller, more focused ones - - Separate frequently changing state from stable state - - Group related state and functions together - - ```typescript - // Instead of one large context - const AppContext = createContext({/* too many properties */}); - - // Split into focused contexts - const UserContext = createContext({/* user-related state */}); - const PreferencesContext = createContext({/* user preferences */}); - const NotificationsContext = createContext({/* notifications state */}); - ``` - -2. **Memoization**: - - Use React.memo for context provider components when absolutely needed - - Memoize context values with useMemo when absolutely needed - - Use useCallback for functions in context value when absolutely needed - - ```typescript - export function FeatureProvider({ children }: { children: ReactNode }) { - // State declarations - - // Memoize expensive functions - const processData = useCallback((data) => { - // Process data - }, [dependencies]); - - // Memoize the context value - const value = useMemo(() => ({ - data, - isLoading, - error, - processData, - }), [data, isLoading, error, processData]); - - return ( - - {children} - - ); - } - ``` - -## State Selectors - -1. **Selector Patterns**: - - Create custom hooks that select specific pieces of context - - Avoid re-renders by consuming only needed context parts - - Use memoization for derived state when absolutely needed - - ```typescript - // Main context hook - export const useFeatureContext = () => useContext(FeatureContext); - - // Selector hooks - export const useFeatureData = () => { - const { data } = useFeatureContext(); - return data; - }; - - export const useFeatureStatus = () => { - const { isLoading, error } = useFeatureContext(); - return { isLoading, error }; - }; - - export const useFeatureActions = () => { - const { actions } = useFeatureContext(); - return actions; - }; - ``` - -2. **Derived State**: - - Use useMemo for computed values based on state when absolutely needed - - Keep computations close to where the state is used - - Memoize selectors that perform expensive calculations - - ```typescript - const useFilteredItems = (filter) => { - const { data } = useFeatureContext(); - - // Memoized derived state - const filteredItems = useMemo(() => { - return data.filter(item => item.category === filter); - }, [data, filter]); - - return filteredItems; - }; - ``` - -## Local vs. Global State Guidelines - -1. **When to Use Local State**: - - UI state (modal open/closed, tabs, accordions) - - Form input values during editing - - Temporary visual states (hover, focus) - - Component-specific loading and error states - - ```typescript - // Good local state examples - const [isOpen, setIsOpen] = useState(false); - const [activeTab, setActiveTab] = useState('details'); - const [formValues, setFormValues] = useState({ name: '', email: '' }); - ``` - -2. **When to Use Context (Global State)**: - - User authentication and profile - - Theme and preferences - - Notifications - - Shopping cart or multi-step wizards - - Feature flags and configuration - - ```typescript - // Examples using context appropriately - const { user, status } = useAuthContext(); - const { theme, toggleTheme } = useThemeContext(); - const { notifications, markAsRead } = useNotifications(); - ``` - -3. **When to Use URL State**: - - Current view or page - - Filters and sorting options - - Search queries - - Detail item IDs - - Anything that should be shareable or bookmarkable - - ```typescript - // URL state examples - const router = useRouter(); - const { tab, sort, filter } = useSearchParams(); - - // Update URL state - router.push(`/dashboard?tab=${newTab}&sort=${sort}`); - ``` - -These patterns ensure consistent, maintainable, and performant state management throughout the ResearchHub application. \ No newline at end of file diff --git a/.cursor/rules/testing-standards.mdc b/.cursor/rules/testing-standards.mdc deleted file mode 100644 index 4d6e9dab5..000000000 --- a/.cursor/rules/testing-standards.mdc +++ /dev/null @@ -1,320 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- - # ResearchHub Testing Standards - -This document outlines the testing standards and best practices used in the ResearchHub codebase. - -## Testing Framework - -ResearchHub uses the following testing stack: - -1. **Jest** - For test running and assertions -2. **React Testing Library** - For testing React components -3. **MSW (Mock Service Worker)** - For mocking API requests -4. **Cypress** - For end-to-end testing - -## Test Types - -1. **Unit Tests**: - - Test individual functions and components in isolation - - Mock all external dependencies - - Focus on testing the logic rather than implementation details - -2. **Integration Tests**: - - Test multiple components or functions working together - - Test interactions between components - - Test API calls with mocked responses using MSW - -3. **End-to-End Tests**: - - Test entire workflows from the user's perspective - - Use Cypress to interact with the application - - Focus on critical user paths - -## Test File Organization - -1. **File Naming**: - - Unit and integration tests: `ComponentName.test.tsx` or `functionName.test.ts` - - End-to-end tests: `feature-name.spec.ts` - -2. **File Location**: - - Co-locate unit and integration tests with the code they're testing - - Place end-to-end tests in a dedicated `cypress/integration` directory - - ``` - components/ - ├── Button/ - │ ├── Button.tsx - │ └── Button.test.tsx - utils/ - ├── formatDate.ts - └── formatDate.test.ts - ``` - -## Unit Testing Standards - -1. **Component Testing**: - - Focus on testing behavior, not implementation details - - Test user interactions (clicks, input changes, etc.) - - Test rendering logic and conditional rendering - - Test accessibility features - - ```tsx - import { render, screen, fireEvent } from '@testing-library/react'; - import { Button } from './Button'; - - describe('Button', () => { - it('renders with correct text', () => { - render(); - expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument(); - }); - - it('calls onClick when clicked', () => { - const handleClick = jest.fn(); - render(); - fireEvent.click(screen.getByRole('button')); - expect(handleClick).toHaveBeenCalledTimes(1); - }); - - it('is disabled when disabled prop is true', () => { - render(); - expect(screen.getByRole('button')).toBeDisabled(); - }); - }); - ``` - -2. **Utility Function Testing**: - - Test all edge cases - - Test with a variety of inputs - - Test error handling - - ```tsx - import { formatCurrency } from './formatCurrency'; - - describe('formatCurrency', () => { - it('formats positive numbers correctly', () => { - expect(formatCurrency(1000)).toBe('$1,000.00'); - expect(formatCurrency(1000.5)).toBe('$1,000.50'); - }); - - it('formats negative numbers correctly', () => { - expect(formatCurrency(-1000)).toBe('-$1,000.00'); - }); - - it('handles zero correctly', () => { - expect(formatCurrency(0)).toBe('$0.00'); - }); - - it('throws an error for non-numeric input', () => { - expect(() => formatCurrency('abc' as any)).toThrow(); - }); - }); - ``` - -3. **Hook Testing**: - - Use `renderHook` from `@testing-library/react-hooks` - - Test initial values, updates, and edge cases - - ```tsx - import { renderHook, act } from '@testing-library/react-hooks'; - import { useCounter } from './useCounter'; - - describe('useCounter', () => { - it('initializes with default value', () => { - const { result } = renderHook(() => useCounter()); - expect(result.current.count).toBe(0); - }); - - it('initializes with provided value', () => { - const { result } = renderHook(() => useCounter(10)); - expect(result.current.count).toBe(10); - }); - - it('increments the counter', () => { - const { result } = renderHook(() => useCounter()); - act(() => { - result.current.increment(); - }); - expect(result.current.count).toBe(1); - }); - }); - ``` - -## Integration Testing Standards - -1. **Component Integration**: - - Test groups of components working together - - Test state changes and their effects on the UI - - Use context providers when necessary - - ```tsx - import { render, screen, fireEvent } from '@testing-library/react'; - import { TodoList } from './TodoList'; - import { TodoProvider } from '@/contexts/TodoContext'; - - describe('TodoList', () => { - it('adds a new todo when form is submitted', () => { - render( - - - - ); - - // Fill in the form - fireEvent.change(screen.getByLabelText(/new todo/i), { - target: { value: 'Test todo' }, - }); - - // Submit the form - fireEvent.click(screen.getByRole('button', { name: /add/i })); - - // Check that the todo was added - expect(screen.getByText('Test todo')).toBeInTheDocument(); - }); - }); - ``` - -2. **API Mocking**: - - Use MSW to mock API requests - - Test success and error scenarios - - Test loading states - - ```tsx - import { rest } from 'msw'; - import { setupServer } from 'msw/node'; - import { render, screen, waitFor } from '@testing-library/react'; - import { UserProfile } from './UserProfile'; - - const server = setupServer( - rest.get('/api/user/:id', (req, res, ctx) => { - return res( - ctx.json({ - id: '123', - name: 'John Doe', - email: 'john@example.com', - }) - ); - }) - ); - - beforeAll(() => server.listen()); - afterEach(() => server.resetHandlers()); - afterAll(() => server.close()); - - describe('UserProfile', () => { - it('renders user data after loading', async () => { - render(); - - // Initially shows loading state - expect(screen.getByText(/loading/i)).toBeInTheDocument(); - - // After loading, shows user data - await waitFor(() => { - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('john@example.com')).toBeInTheDocument(); - }); - }); - - it('handles error state', async () => { - server.use( - rest.get('/api/user/:id', (req, res, ctx) => { - return res(ctx.status(500)); - }) - ); - - render(); - - await waitFor(() => { - expect(screen.getByText(/error/i)).toBeInTheDocument(); - }); - }); - }); - ``` - -## End-to-End Testing Standards - -1. **Test Coverage**: - - Test critical user flows - - Test authentication and authorization - - Test form submissions and validations - - ```tsx - describe('Authentication', () => { - beforeEach(() => { - cy.visit('/login'); - }); - - it('allows users to log in', () => { - cy.intercept('POST', '/api/login', { - body: { - token: 'fake-token', - user: { id: '123', name: 'Test User' }, - }, - }).as('loginRequest'); - - cy.get('input[name="email"]').type('user@example.com'); - cy.get('input[name="password"]').type('password123'); - cy.get('button[type="submit"]').click(); - - cy.wait('@loginRequest'); - cy.url().should('include', '/dashboard'); - cy.contains('Welcome, Test User'); - }); - - it('shows error messages for invalid login', () => { - cy.intercept('POST', '/api/login', { - statusCode: 401, - body: { message: 'Invalid credentials' }, - }).as('loginRequest'); - - cy.get('input[name="email"]').type('user@example.com'); - cy.get('input[name="password"]').type('wrong-password'); - cy.get('button[type="submit"]').click(); - - cy.wait('@loginRequest'); - cy.contains('Invalid credentials'); - cy.url().should('include', '/login'); - }); - }); - ``` - -2. **Test Isolation**: - - Reset state between tests - - Use fixtures for test data - - Clean up after tests - -## Test Coverage Requirements - -1. **Coverage Targets**: - - Unit tests: 85% code coverage - - Integration tests: Critical user flows - - End-to-end tests: Core user journeys - -2. **Critical Areas**: - - Authentication and authorization - - Form validation and submission - - Data fetching and error handling - - Complex business logic - -## Testing Best Practices - -1. **General Best Practices**: - - Write tests before or alongside code (TDD where appropriate) - - Keep tests simple and focused - - Don't test implementation details - - Test behavior, not functions - -2. **Maintainability**: - - Use descriptive test names - - Organize tests logically - - Extract common test setup into helper functions - - Keep tests independent of each other - -3. **Performance**: - - Keep unit tests fast - - Run slow tests (integration, e2e) in CI only - - Use mocks for external services - -These testing standards ensure code quality, prevent regressions, and provide confidence when refactoring or adding new features to the ResearchHub application. \ No newline at end of file diff --git a/.cursor/rules/types-architecture.mdc b/.cursor/rules/types-architecture.mdc deleted file mode 100644 index b79d6d141..000000000 --- a/.cursor/rules/types-architecture.mdc +++ /dev/null @@ -1,304 +0,0 @@ ---- -description: This document outlines the architectural patterns and best practices for TypeScript types in the ResearchHub codebase. -globs: -alwaysApply: false ---- - # ResearchHub Types Architecture - -This document outlines the architectural patterns and best practices for TypeScript types in the ResearchHub codebase. - -## Type Organization - -1. **Domain-specific Files**: - - Organize types by domain in dedicated files (e.g., `feed.ts`, `note.ts`) - - Group related interfaces, types, and enums together - - Export all types needed by consumers - - ```typescript - // feed.ts - export interface FeedEntry { /* ... */ } - export type FeedActionType = 'upvote' | 'comment' | 'create'; - export enum ContentType { /* ... */ } - ``` - -2. **Naming Conventions**: - - Use PascalCase for interfaces, types, and enums - - Use descriptive names that convey purpose and structure - - Add appropriate suffixes to indicate usage (e.g., `Response`, `Params`) - - ```typescript - // Good naming examples - export interface NoteApiResponse { /* ... */ } - export type ContentType = 'paper' | 'post' | 'bounty'; - export enum FundingRequestStatus { /* ... */ } - ``` - -## Interface Structure - -1. **API Response Interfaces**: - - Define interfaces for all API responses - - Include API-specific naming (e.g., snake_case properties) - - Use the `ApiResponse` suffix for clarity - - ```typescript - export interface NoteApiResponse { - id: string; - title: string; - content: string; - created_by: string; - created_at: string; - updated_at: string; - // Other API properties - } - ``` - -2. **Application Model Interfaces**: - - Create client-side model interfaces with camelCase properties - - Include all properties needed by UI components - - Add optional properties for partial data scenarios - - ```typescript - export interface Note { - id: string; - title: string; - content: string; - createdBy: string; - createdAt: Date; - updatedAt: Date; - isEditable?: boolean; - // Other client-side properties - } - ``` - -3. **Extension Patterns**: - - Use interface extension to build upon base interfaces - - Create specialized interfaces for specific use cases - - Avoid deep inheritance hierarchies (prefer composition) - - ```typescript - export interface BaseNote { - id: string; - title: string; - } - - export interface NoteWithContent extends BaseNote { - content: string; - collaborators: User[]; - } - - export interface NoteListItem extends BaseNote { - excerpt: string; - updatedAt: Date; - } - ``` - -## Type Definitions - -1. **Union Types**: - - Use union types for values with a finite set of options - - Define clear, specific union types for related values - - Add documentation for non-obvious union types - - ```typescript - // Simple union of string literals - export type ContentType = 'paper' | 'post' | 'bounty' | 'funding_request'; - - // Union of interfaces - export type Content = Paper | Post | Bounty | FundingRequest; - ``` - -2. **Enums**: - - Use enums for fixed sets of related values - - Prefer string enums for better debugging and serialization - - Document enum values with JSDoc comments - - ```typescript - /** - * Status of a funding request - */ - export enum FundingRequestStatus { - DRAFT = 'draft', - SUBMITTED = 'submitted', - APPROVED = 'approved', - REJECTED = 'rejected', - FUNDED = 'funded', - COMPLETED = 'completed', - } - ``` - -3. **Literal Types**: - - Use literal types for specific, known values - - Combine literals into union types for better type checking - - Use const assertions for object literals when appropriate - - ```typescript - // Literal type with union - type ButtonSize = 'sm' | 'md' | 'lg' | 'xl'; - - // Const assertion for object literal - const ROUTES = { - HOME: '/', - FEED: '/feed', - PROFILE: '/profile', - } as const; - - type Route = typeof ROUTES[keyof typeof ROUTES]; - ``` - -## Type Transformations - -1. **Transformer Pattern**: - - Use transformer functions to convert between API and client models - - Maintain consistent transformation patterns across the application - - Preserve raw data for debugging when needed - - ```typescript - // Using the transformer utility - import { createTransformer } from '../types/transformer'; - - export const transformNote = createTransformer((data) => ({ - id: data.id, - title: data.title, - content: data.content, - createdBy: data.created_by, - createdAt: new Date(data.created_at), - updatedAt: new Date(data.updated_at), - // Transform other properties - })); - ``` - -2. **Partial Types**: - - Use `Partial` for optional update operations - - Define specific input types for partial updates - - Document which fields are required vs. optional - - ```typescript - export interface UpdateNoteParams { - id: string; // Required - title?: string; // Optional update fields - content?: string; - collaborators?: string[]; - } - - // Or using utility types - export type UpdateNoteParams = { - id: string; // Required - } & Partial>; - ``` - -## Type Utilities - -1. **Pick and Omit**: - - Use `Pick` to create a type with a subset of properties - - Use `Omit` to create a type without specific properties - - Combine utility types for complex type transformations - - ```typescript - // Creating a type with only specific properties - type NotePreview = Pick; - - // Creating a type without specific properties - type NoteForCreation = Omit; - ``` - -2. **Type Guards**: - - Implement type guards to narrow types - - Use descriptive function names for type guards - - Return type predicates for better type inference - - ```typescript - // Type guard for Content union type - export function isPaper(content: Content): content is Paper { - return content.type === 'paper'; - } - - // Usage example - if (isPaper(content)) { - // Content is now narrowed to Paper type - console.log(content.doi); // TypeScript knows this property exists - } - ``` - -## Documentation - -1. **JSDoc Comments**: - - Document complex types with JSDoc comments - - Include descriptions for non-obvious properties - - Add examples for complex or unusual types - - ```typescript - /** - * Represents a feed entry in the application - * @property id - Unique identifier for the entry - * @property content - The content object (Paper, Post, etc.) - * @property contentType - The type of content - * @property actions - List of actions performed on this content - */ - export interface FeedEntry { - id: string; - content: Content; - contentType: ContentType; - actions: FeedAction[]; - // Other properties - } - ``` - -2. **Inline Comments**: - - Add inline comments for complex type expressions - - Document the purpose of utility type transformations - - Explain non-obvious type constraints - - ```typescript - // Maps API response fields to client model fields - type UserModelMapping = { - [K in keyof UserApiResponse as CamelCase]: UserApiResponse[K]; - }; - - // Creates a discriminated union based on content type - type ContentByType = { - [K in ContentType]: { type: K } & ContentTypeMapping[K]; - }[ContentType]; - ``` - -## Type Safety - -1. **Strict Type Checking**: - - Enable strict mode in TypeScript configuration - - Avoid use of `any` type - - Use `unknown` instead of `any` for values of uncertain type - - ```typescript - // Avoid this - function processData(data: any): any { - return data.value; - } - - // Prefer this - function processData(data: unknown): number { - if (typeof data === 'object' && data !== null && 'value' in data) { - return typeof data.value === 'number' ? data.value : 0; - } - return 0; - } - ``` - -2. **Nullability**: - - Use optional properties (`?`) instead of union with `null` - - Handle null and undefined values consistently - - Use non-null assertion (`!`) sparingly and only when certain - - ```typescript - // Prefer optional properties - interface User { - id: string; - name: string; - bio?: string; // Better than bio: string | null - } - - // Handle nullability carefully - function getUserName(user?: User): string { - return user?.name ?? 'Unknown User'; - } - ``` - -These patterns ensure consistent, maintainable, and type-safe code throughout the ResearchHub application. \ No newline at end of file diff --git a/.cursor/rules/typescript-standards.mdc b/.cursor/rules/typescript-standards.mdc deleted file mode 100644 index 407f0a1cc..000000000 --- a/.cursor/rules/typescript-standards.mdc +++ /dev/null @@ -1,181 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- - # ResearchHub TypeScript Standards - -This document outlines the TypeScript standards and conventions used in the ResearchHub codebase. - -## Type Definitions - -1. **Interfaces vs Types**: - - Use `interface` for object shapes that may be extended - - Use `type` for unions, primitives, and complex types that won't be extended - - Prefer interfaces for public API definitions - - ```typescript - // Use interface for object shapes that might be extended - interface UserProfile { - id: string; - name: string; - email: string; - } - - // Use type for unions and more complex types - type Status = 'idle' | 'loading' | 'success' | 'error'; - type ButtonVariant = 'primary' | 'secondary' | 'ghost'; - ``` - -2. **Naming Conventions**: - - Use PascalCase for type names and interfaces - - Use descriptive names that indicate the purpose - - Append `Props` to component prop interfaces - - Prefix enum names with 'E' (e.g., `EButtonSize`) - -3. **Organization**: - - Place shared types in the `types/` directory - - Keep component-specific types in the component file - - Group related types together - -## Type Safety - -1. **Strict Type Checking**: - - Enable strict mode in `tsconfig.json` - - Avoid using `any` type when possible - - Use specific types instead of generic `object` type - -2. **Null and Undefined**: - - Use optional chaining (`?.`) for potentially undefined properties - - Use nullish coalescing (`??`) for default values - - Be explicit about nullable types with union types - - ```typescript - function getUser(id?: string): User | null { - if (!id) return null; - // ... - } - - // Handling nullable values - const username = user?.name ?? 'Guest'; - ``` - -3. **Type Assertions**: - - Use type assertions (`as Type`) sparingly - - Prefer type guards to narrow types safely - - Document why type assertions are necessary when used - -4. **Type Guards**: - - Use type predicates for custom type guards - - ```typescript - function isUser(obj: any): obj is User { - return obj && typeof obj === 'object' && 'id' in obj && 'name' in obj; - } - ``` - -## Generic Types - -1. **Using Generics**: - - Use generics to create reusable utility types - - Use descriptive type parameter names (T, U, etc. are acceptable for simple cases) - - Constrain generics when possible using extends - - ```typescript - // Generic function with constraint - function getProperty(obj: T, key: K): T[K] { - return obj[key]; - } - ``` - -2. **Common Generic Patterns**: - - Use `React.FC` for functional components (optional, as explicit return types are preferred) - - Use built-in generic types like `Partial`, `Pick`, and `Omit` - -## API Types - -1. **Request/Response Types**: - - Define specific types for API requests and responses - - Use descriptive names that indicate the purpose - - Keep related request/response types together - - ```typescript - interface GetUserRequest { - userId: string; - } - - interface GetUserResponse { - user: User; - lastLogin: string; - } - ``` - -2. **Data Transformations**: - - Define transformation functions between API and UI models - - Document any assumptions or edge cases - -## Event Handlers - -1. **Event Types**: - - Use specific event types from React - - Define clear callback types for custom events - - ```typescript - // React event handler - const handleClick = (event: React.MouseEvent) => { - // ... - }; - - // Custom event handler - interface TabChangeEvent { - previousIndex: number; - newIndex: number; - } - - type TabChangeHandler = (event: TabChangeEvent) => void; - ``` - -## Error Handling - -1. **Error Types**: - - Define custom error types with meaningful properties - - Use union types to represent different error states - - ```typescript - type APIError = { - code: string; - message: string; - status: number; - }; - - type LoadingState = - | { status: 'idle' } - | { status: 'loading' } - | { status: 'success'; data: T } - | { status: 'error'; error: APIError }; - ``` - -## Documentation - -1. **JSDoc Comments**: - - Use JSDoc comments for functions, interfaces, and complex types - - Document parameters, return types, and exceptions - - Include examples for complex functions - - ```typescript - /** - * Fetches a user by ID from the API - * @param id - The unique identifier of the user - * @returns A promise that resolves to the user or null if not found - * @throws {APIError} When the API request fails - * @example - * ``` - * const user = await fetchUser('123'); - * ``` - */ - async function fetchUser(id: string): Promise { - // ... - } - ``` - -These TypeScript standards ensure code quality, readability, and maintainability throughout the ResearchHub application. \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..ff9077953 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# ResearchHub Web + +Next.js frontend for ResearchHub. All data comes from the Django API in +`researchhub-backend`, reached through `NEXT_PUBLIC_API_URL`. + +## Checks +- Run `npm run lint` and `npm run type-check` before finishing. +- The pre-commit hook runs Prettier, ESLint, and a full type-check, so a commit + fails on a type error anywhere in the repo. +- The Playwright smoke tests in `smoke/` are the only test suite. They run + against a running environment (see `README.md`). +- Add tests only when the user asks for them. + +## Dependencies +- `@tiptap-pro/*` and Font Awesome Pro install from private registries. Their + tokens live in a gitignored `.npmrc` at the repo root, so a fresh clone or git + worktree needs a copy of it before `npm install` works. +- Change dependencies with `npx -y npm@10.7.0 install`. Other npm versions + rewrite `package-lock.json` metadata. + +## Conventions +- Data flows component → hook → service → `ApiClient` (`services/client.ts`), + which attaches the auth token and throws `ApiError`. Services are + `services/*.service.ts` classes of static methods. +- The API returns snake_case. Each domain file in `types/` defines the camelCase + model and a `transform*` function built with `createTransformer` + (`types/transformer.ts`), which keeps the original payload on `.raw`. Services + return transformed models. +- Keep services and transformers to data; display formatting belongs in + components. +- `app/layouts/PageLayout.tsx` is the standard page shell. +- Build UI from `components/ui/` (`Button`, `Tabs`, `BaseModal`, ...) and merge + class names with `cn()` from `utils/styles.ts`. Modals go in + `components/modals/`. +- Icons come from `lucide-react` by default. +- Import with the `@/` alias. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md