This file contains coding conventions, workflows, and best practices for AI agents working on the Ryot project. Follow these guidelines to maintain code quality and consistency.
Use tools from the Serena MCP (if available) for faster code navigation and retrieval. It supports Rust and TypeScript.
The project uses turbo for monorepo management. All frontend-related commands (type checking, running tests, etc.) must use turbo commands.
Read apps/docs/src/contributing.md for an overview of the project architecture and common commands.
When running bash commands (git, sed, etc.), always quote paths using single quotes since they often contain special characters.
Example: git add 'path/with-special-chars/file.ts'
Use the gh CLI for GitHub operations. Only make raw API requests when the gh CLI does not support the required functionality. The gh CLI is particularly useful for fetching source code of libraries that the project depends on.
Example: To view a file from the apalis dependency:
gh api -H "Accept: application/vnd.github.raw" repos/apalis-dev/apalis/contents/apalis/src/layers/opentelemetry/mod.rsRun the relevant commands before committing to ensure changes do not break anything:
cargo clippy
yarn turbo run typecheck
yarn turbo run build --filter=@ryot/docsWhen running tests:
- Implement the feature first
- Always ask the user's approval to run tests
- Compile the backend in release mode (
cargo build --release) and then runyarn turbo run test --filter=@ryot/tests
After adding a GraphQL query or mutation to the backend:
- Start the backend server in debug mode in the background (
cargo run) - Run
yarn turbo run backend-graphql --filter=@ryot/generatedto generate frontend types - Stop the backend server after generation completes
This ensures the frontend can use the new query or mutation with proper type safety.
This project uses a forward-only migration approach:
- When adding a migration with a schema change for an existing table, also apply the same change to the migration where that table was first created
- This ensures new Ryot instances have the correct table structure from the start
Name migration files using the pattern: m<YYYYMMDD>_changes_for_issue_<number>
Review existing migration files for examples.
Down migrations are not used since we always roll forward. They should be empty blocks returning Ok(()).
Do not add code comments unless strictly necessary. Prefer self-documenting code with clear variable names, function names, and structure.
Keep code files below 500 lines. If a file exceeds this limit, split it into smaller files using functions, components, or modules to improve readability and maintainability.
React components must use a single props parameter instead of destructured props in function arguments.
Correct:
function MyComponent(props: MyComponentProps) {
return <div>{props.title}</div>;
}Incorrect:
function MyComponent({ title }: MyComponentProps) {
return <div>{title}</div>;
}Do not add explicit return types to functions unless required. TypeScript's type inference is sufficient in most cases.
Explicit return types may be necessary when:
- The inferred return type is too complex or unclear
- Enforcing a specific return type contract is desired
When initializing structs (Rust) or object literals (TypeScript), order fields by ascending line length - shorter lines first, longer lines last. This applies to:
- Rust struct initializations
- TypeScript/JavaScript object literals
- JSX component props
Rust Example:
Ok(MetadataDetails {
people, // shortest
watch_providers,
description: data.overview,
external_identifiers: Some(external_identifiers),
original_language: self.0.get_language_name(data.original_language.clone()),
publish_date: data
.release_date
.clone()
.and_then(|r| convert_string_to_date(&r)), // longer multi-line expressions last
..Default::default() // always at the end
})TypeScript Example:
const notification = {
color: "red",
title: "Invalid action",
message: "Changing preferences is disabled for demo users",
};Exceptions (correctness takes precedence):
..Default::default()in Rust must always be last (language requirement)- Semantic grouping may override length ordering when it improves readability
- Shorthand fields (just the field name) typically come before assignment expressions of similar length:
MyStruct {
name, // shorthand comes first
age: user.age, // assignment of similar length comes after
}When declaring multiple variables in sequence (particularly React hooks), order them by ascending line length:
const navigate = useNavigate();
const isMobile = useIsMobile();
const { startOnboardingTour } = useOnboardingTour();
const isOnboardingTourCompleted = useIsOnboardingTourCompleted();
const markUserOnboardingStatus = useMarkUserOnboardingTourStatus();This pattern applies to:
- React hook calls at the start of components
- Sequential
const/letdeclarations
Return Object Example:
return {
userDetails,
coreDetails,
isDemoInstance,
shouldHaveUmami,
currentColorScheme,
desktopSidebarCollapsed,
userPreferences: userDetails.preferences,
};Note: This pattern does NOT apply to import statements (which follow alphabetical ordering enforced by tooling) or function parameters (which follow semantic ordering).
When asked to create a git commit:
- Read all dirty changes in the repository
- Create logical commits, grouping related changes together
- Create multiple commits as needed for different logical units of work
- Write verbose commit messages that explain the reasoning behind the changes, not just what was changed
Focus on the "why" rather than the "what" in commit messages.