This file provides guidance to AI coding agents (e.g. Antigravity, Claude Code, Gemini CLI, Cursor, and similar tools) when working with code in this repository.
RClone Manager welcomes AI-assisted contributions, but the expectation is that you, the human submitter, understand every line you propose and have compiled, linted, and tested it against real code — not just generated it. See CONTRIBUTING.md before opening a pull request.
rclone-manager is a cross-platform GUI and web server application for managing Rclone.
- Frontend: Angular (v22+), TypeScript, SCSS, RxJS, Angular Material.
- Backend: Rust, Tauri v2 (
backend/desktop),librcloneC-FFI / Go integration.
- Keep Changes Minimal & Elegant: Work to make the smallest, most effective change possible. Avoid unneeded refactoring, re-ordering of imports, or restyling surrounding code.
- Backwards Compatibility: PRs should preserve existing behavior across desktop, web server (headless), and mobile targets.
- Verify Before Proposing: AI agents must run build, lint, and formatting verification commands before declaring success. Frontend changes must produce a warning-free build (see §Build, Test & Lint Commands).
- Automated Testing & Test Coverage (CRITICAL):
- Just like upstream Rclone and best practices, whenever adding or refactoring business logic, services, utilities, parsers, or mappings, AI agents MUST write or update accompanying automated unit tests.
- Frontend: Unit tests (
*.spec.ts) are placed in the same directory alongside their corresponding implementation files. - Backend: Rust unit tests (
#[cfg(test)]) are placed inside source files or under test modules. - Verify tests cover both normal operation and edge cases (null inputs, empty values, special characters, error paths).
-
Tooltip Usage Rule (CRITICAL)
- DO NOT use Angular Material Tooltips (
matTooltip,MatTooltipModule). - ALWAYS use native HTML
titleattributes for tooltips (e.g.,<button title="Refresh list">...</button>).
- DO NOT use Angular Material Tooltips (
-
Angular Architecture
- Follow existing service and component patterns.
- Use
inject()for dependency injection where applicable. - Keep translation keys organized and run
npm run audit:i18nwhen adding user-facing text.
-
Angular Material
- Do not use
appearencein the material form components (inputs, selects, textareas). Use them as default.
- Do not use
-
URL Opener Links (CRITICAL)
- ALWAYS use standard HTML
<a>tags withhrefattributes for opening URLs (e.g.,<a href="https://..." target="_blank" rel="noopener noreferrer">...</a>or<a matButton="filled" [href]="url" target="_blank">...</a>). - DO NOT use
<button>elements or click handlers to open external web links.OpenerServiceruns a global link interceptor that captures all<a>tags with external protocols (http/https/mailto) and opens them safely in the default system browser or new tab.
- ALWAYS use standard HTML
-
Menu Component Usage Rule (CRITICAL)
- DO NOT use Angular Material Menus (
mat-menu,MatMenuModule,matMenuTriggerFor). - ALWAYS use CDK Menu (
@angular/cdk/menu,CdkMenuModule,[cdkMenuTriggerFor],cdkMenu,cdkMenuItem) styled with.material-context-menuand.menu-itemclasses.
- DO NOT use Angular Material Menus (
-
Internationalization & Backend Error Architecture (CRITICAL)
- Unified Resource Directory: All translation files reside in
resources/i18n/{lang}/(main.json,rclone.json,rclone-providers.json). Do not split or duplicate translation files across other directories. - Frontend Static Asset Serving: Angular loads translations via
MultiFileLoaderfrom static assets (assets/i18n/...mapped inangular.json). - Backend Minimal Memory Footprint: The Rust backend (
backend/core/src/utils/i18n.rs) only readsmain.jsonand caches only keys needed for OS integrations (tray,notification,powerInhibitor,alerts,backendErrors). - Language-Agnostic Backend Errors: Rust code must never pre-translate error messages. Always use
localized_error!("backendErrors.<category>.<key>", ...)orlocalized_success!("backendSuccess.<category>.<key>", ...), producing structured{ key, params }JSON or raw keys forBackendTranslationServiceon the frontend. - Every user-visible string is a key: this includes command results (
UpdateResult.message), notification parameters and the failure text the update helper leaves behind. Do not return hard-coded English from Rust, and add each new key to every language directory underresources/i18n/(npm run audit:i18nmust report no missing keys).
- Unified Resource Directory: All translation files reside in
-
Platform & Tauri Target Abstraction (CRITICAL)
- TauriBaseService &
this.isTauri:- All backend-communicating services should extend
TauriBaseService. - Inside classes extending
TauriBaseService, always use the inheritedthis.isTauriproperty (!isHeadlessMode()) rather than re-importing or callingisHeadlessMode(). - In standalone components/functions where inheritance is not used, import and call
isHeadlessMode()fromapi-client.service. - NEVER use redundant double-checks like
!this.isTauri || isHeadlessMode()or call native Tauri APIs without guarding withthis.isTauri.
- All backend-communicating services should extend
- TauriBaseService &
-
Automated Unit Testing & Spec Maintenance
- New utility functions, data transformers, flag parsers, state machines, and business services should always have corresponding unit test suites.
- Ensure tests cover both happy paths and edge cases (e.g. empty strings, null values, invalid inputs, error handling).
-
Unique ID Generation & Non-Secure HTTP Compatibility (CRITICAL)
- DO NOT use
crypto.randomUUID()or Web Crypto APIs directly in frontend code. They are restricted to secure contexts (HTTPS/localhost) by modern browsers and areundefinedover plain HTTP (e.g., remote IP headless web server access), causingTypeError: crypto.randomUUID is not a functionand blank screens. - ALWAYS use
generatePrefixedId(prefix)fromsrc/app/shared/utilsfor generating unique IDs, request tokens, temporary keys, or job groups (e.g.,generatePrefixedId('qr'),generatePrefixedId('picker'),generatePrefixedId('usr-tpl')).
- DO NOT use
-
Event System & Reactive State Architecture (CRITICAL)
- Frontend Centralization & Strict Typing:
- ALWAYS route frontend event subscriptions through the singleton
EventListenersService(src/app/services/infrastructure/system/event-listeners.service.ts). - DO NOT call
this.listenToEvent(...)or import@tauri-apps/api/eventdirectly in components or domain services. BypassingEventListenersServicecauses duplicate Tauri IPC listeners and separate SSE pipelines. - ALWAYS define event name constants and structured payload interfaces in
src/app/shared/types/events.ts(re-exported from@app/types). Every method inEventListenersServicemust return a strongly-typedObservable<T>.
- ALWAYS route frontend event subscriptions through the singleton
- Backend Event Emission & Headless SSE Parity (Rust):
- All event strings MUST be declared as constants in
backend/core/src/utils/types/events.rswith docstrings identifying emitting sources and consuming listeners. - Any event intended for frontend consumption across desktop and headless modes MUST be included in
SSE_FORWARD_EVENTSinbackend/core/src/utils/types/events.rs. - Internal backend listeners for system integration (tray re-render, power inhibition, engine restarts) MUST be registered inside
backend/core/src/services/event_listener.rs.
- All event strings MUST be declared as constants in
- Frontend Centralization & Strict Typing:
-
Drag & Drop / HTML5 Drag Prohibition in UI (CRITICAL)
- DO NOT use the native HTML5 Drag and Drop API (
draggable="true",(dragstart),(dragover),(drop),event.dataTransfer) for internal UI dragging interactions. - Native HTML5 drag causes severe webview freezes, crashes, dropped payloads, or erratic behavior in Tauri (WebKitGTK on Linux and WebView2 on Windows) and does not support mobile touch gestures natively.
- ALWAYS implement internal Pointer Drag systems using pointer events (
pointerdown,pointermove,pointerup,pointercancel) with floating ghost elements anddocument.elementFromPoint()or bounding box hit-testing. Refer toNautilusDragDropService(src/app/services/ui/nautilus-drag-drop.service.ts) andWorkflowDragDropService(src/app/services/flow/workflow-drag-drop.service.ts) as reference implementations. - Always enforce a pointer movement threshold (e.g. 6–8px) before initiating a drag so that regular
clickand touch tap actions continue to work seamlessly.
- DO NOT use the native HTML5 Drag and Drop API (
-
Dead Code & Compiler Warning Suppression Prohibition (CRITICAL)
- DO NOT use
#[allow(dead_code)],#[allow(unused)], or similar compiler warning suppression attributes to silence unused structs, methods, fields, functions, or imports in backend code. - Every function, struct, field, or method introduced must be actively used or properly integrated.
- If an item is target- or feature-specific, use exact conditional compilation attributes (
#[cfg(feature = "...")]or#[cfg(not(feature = "..."))]) instead of silencing warnings withallow. - Unused code must be removed rather than suppressed. Backend code must compile cleanly with
-D warningsacross all target configurations (Desktop, Web Server, Mobile) without suppressing dead code.
- DO NOT use
-
Design System First, Global SCSS & Anti-Duplication Rule (CRITICAL)
- Never Reinvent Existing Classes (Single Canonical Class Rule): Before adding any new CSS class to a component's
.scssfile, AI agents MUST inspectsrc/custom-theme.scssandsrc/app/styles/. There is exactly ONE canonical global class for each UI primitive. You MUST reuse it instead of inventing a custom class or alias with duplicate styling:- Pills / Badges / Chips: Always use
.app-pill(with.interactive,.is-selected, and semantic color variants.p-primary,.p-accent,.p-orange,.p-yellow,.p-purple,.p-warn,.p-dim). DO NOT create custom.filter-chip,.badge,.status-tag,.job-id-pill,.stat-pill, or.chipclasses. - Action / Icon Buttons: Always use
.action-button(standard 32x32px icon button). DO NOT create custom.action-btn,.icon-btn,.tool-btn,.chip-delete-btn,.action-mini-btn, or.custom-icon-buttonclasses. - Text Truncation (Ellipsis): Always use the single canonical
.truncateclass. DO NOT re-declarewhite-space: nowrap; overflow: hidden; text-overflow: ellipsis;in component SCSS files. - Counter Badges: Always use the single canonical
.count-badgeclass. DO NOT create custom.cat-count-badge,.log-count-badge, or.config-count-badgeclasses. - Empty States: Always use
.empty-state(flex column, centered, withmat-icon,span,p). DO NOT create.no-data,.empty-view,.profile-empty-state, or component-specific empty-state clones. - Loaders / Overlays: Always use
.loading-container(inline spinner) or.loading-overlay(modal absolute overlay from_shared-modal.scss). - Context Menus: Always use
.material-context-menuand.menu-item(see Rule 5). - Scrollbar Concealing: Always use
.hide-scrollbar. DO NOT write custom::-webkit-scrollbar { display: none; }orscrollbar-width: nonein components. - Detail Sections & Rows: Always use
.detail-section,.section-title,.error-entry, and.card-row-item. - Modal Scaffolding: Standard modals should extend or include
src/app/styles/_shared-modal.scssviastyleUrls: ['...', 'path/to/_shared-modal.scss']and use standardheader,main,footer,.form-section, and.section-label.
- Pills / Badges / Chips: Always use
- Global Inherited Resets Prohibition:
html, bodyglobally enforcesuser-select: none;and-webkit-user-drag: none;across the entire application.- DO NOT declare redundant
user-select: none;or-webkit-user-drag: none;in component SCSS files. - ONLY use
user-select: text;oruser-select: all;when explicitly opting-in to allow text selection (e.g. copyable IDs, tokens, paths, or interactive log outputs).
- Design System Tokens & Strict Token Integrity (CRITICAL):
- Never hardcode raw pixel sizes, margins, paddings, or arbitrary hex colors (e.g. Bootstrap
#28a745,#ffc107,#17a2b8or Tailwind#3b82f6,#ef4444). - DO NOT invent CSS variables or alias tokens: Every semantic role maps to exactly ONE canonical variable (1-to-1 token mapping). Defining alias/duplicate variables (e.g.
--text-secondary,--text-muted,--color-danger,--bg-hover,--bg-selected,--bg-elevated-05) is strictly prohibited. - DO NOT write fallbacks into
var()calls (e.g.var(--accent-color, #3b82f6),var(--dim-color, #a0a0a0), orvar(--bg-elevated, rgba(...))). Global tokens insrc/custom-theme.scssare guaranteed to exist at:root. - Always use canonical CSS variables:
- Spacing:
var(--space-xxs)throughvar(--space-2xl) - Border Radius:
var(--radius-xxs)throughvar(--radius-lg) - Typography:
var(--font-size-xs)throughvar(--font-size-3xl),var(--font-mono) - Transitions:
var(--transition-fast),var(--transition-standard) - Elevated Backgrounds:
var(--bg-elevated)(base 2%), andvar(--bg-elevated-1)throughvar(--bg-elevated-4) - Interactive States:
var(--hover-bg-color),var(--selected-bg-color) - Semantic Colors:
var(--window-bg-color),var(--window-fg-color),var(--dim-color)(for all muted/secondary text),var(--accent-color),var(--primary-color),var(--warn-color)(for all errors/danger),var(--yellow),var(--orange),var(--purple),var(--card-bg-color),var(--border-color)
- Spacing:
- Never hardcode raw pixel sizes, margins, paddings, or arbitrary hex colors (e.g. Bootstrap
- Never Reinvent Existing Classes (Single Canonical Class Rule): Before adding any new CSS class to a component's
-
Async Task Spawning Standard (CRITICAL)
- ALWAYS use
crate::utils::spawn(orcrate::utils::spawn_blocking) and standardtokio::task::JoinHandlefor asynchronous background task spawning across backend Rust code. - DO NOT use raw
tokio::spawndirectly in production code: callingtokio::spawnoutside of Tokio worker threads (e.g. insidesetuphooks, GUI thread event listeners, or OS callbacks) panics with"there is no reactor running".crate::utils::spawndynamically registers the Tokio runtime context on the calling thread and returns a nativetokio::task::JoinHandle. - DO NOT use
tauri::async_runtime::spawnortauri::async_runtime::JoinHandle.
- ALWAYS use
-
Zero-Dependency Semantic Version Comparison (Rust)
- DO NOT add external crates (such as
semver) to backend dependencies for version checking and comparisons. - ALWAYS use the built-in, lightweight version comparison functions (
is_version_newer,clean_app_version) fromcrate::utils::version(backend/core/src/utils/version.rs). - DO NOT use raw lexicographical string comparison (
>or<) on version strings, because alphabetical sorting misjudges multi-digit numbers (e.g."0.3.10" < "0.3.4"). The built-in parser comparesmajor.minor.patchnumbers as integers and correctly respects pre-release tags (beta.x). - Whether an installed build may be replaced by a release is decided by
is_update_candidate, notis_version_neweralone: a pre-release build can move to any stable release, so leaving the beta channel never waits for the next stable version. Android is the exception (version codes cannot decrease), and package-managed installs only announce newer versions.
- DO NOT add external crates (such as
-
Default Profile Name (CRITICAL)
- ALWAYS use the shared
DEFAULT_PROFILE_NAMEconstant fromsrc/app/shared/types/remote-config.ts(re-exported from@app/types) when creating, selecting, or resolving a default profile in frontend code. - DO NOT hardcode
'Default'or'default'as a default profile name, define local aliases, or duplicate the constant. For example, usecfg?.app?.backendProfile || DEFAULT_PROFILE_NAME. - Preserve explicitly stored profile names, including legacy lowercase names; use the constant only for the default fallback.
- ALWAYS use the shared
-
Updater & Release Contracts (CRITICAL)
- Release asset names (
server-<os>-<arch>-<version>.zip,portable-…,android-…) are a contract between the release workflows,scripts/create-update-manifest.cjs, the Rust updater and the website's downloads parser (rclone-manager-website/src/app/features/downloads/downloads.utils.tsand its spec). Change them together. - Validation rules exist in both
scripts/create-update-manifest.cjsandbackend/core/src/services/updates/manifest.rs; keep them identical and covered by the shared fixtures underservices/updates/fixtures/. - Never write command-line arguments, passwords or tokens to disk. The update helper receives the launch arguments through its environment.
- Server updates are ZIP archives only;
tar.gzis not used for releases.
- Release asset names (
CI & Automated Workflows (.github/workflows/)
All changes proposed by AI agents must pass the checks enforced by our GitHub Actions workflows:
- .github/workflows/ci.yml: Continuous Integration pipeline verifying TypeScript/Angular lints, Prettier formatting, and Rust Clippy compilation across all target features.
- .github/workflows/docker-build-push.yml: Multi-architecture Docker container build for headless web-server deployment.
- .github/workflows/release-*.yml: Cross-platform release workflows (Linux, Windows, macOS, Android, Portable, Headless).
The commands below mirror the exact validation checks executed in .github/workflows/ci.yml.
# Build the frontend — must complete with ZERO warnings
ng build
# Lint TypeScript and HTML files
npx eslint "**/*.{ts,html}"
# Check Prettier formatting
npx prettier --check "**/*.{ts,html,scss,json}"
# Format frontend code
npx prettier --write "**/*.{ts,html,scss,json}"
# Auto-fix lint & formatting issues across project
npm run fix:all
# Run frontend unit tests
npm run test:ci
# Release metadata and update manifest tests
node --test scripts/create-update-manifest.test.cjs scripts/publish-custom-updater.test.cjs scripts/release-version.test.cjsFrontend Build Rule (CRITICAL): After any frontend change, run
ng buildand verify the output contains no warnings. Warnings (e.g., unused imports, deprecated APIs, template binding issues) must be treated as build failures and fixed before declaring the task complete.
Rust lives in the backend Cargo workspace: core (shared), server (HTTP), and desktop (Tauri/mobile). The following commands run from the repository root.
Note on
LIBRCLONE_SKIP_LINK_CHECK: Whenlibrclone.a/ Go toolchain is not compiled locally, setexport LIBRCLONE_SKIP_LINK_CHECK=1so Clippy can type-check Rust without linking errors.
# 1. Clippy check - Desktop target
cargo clippy --manifest-path backend/Cargo.toml -p rclone-manager --features desktop --no-default-features -- -D warnings
# 2. Clippy check - Web Server target
mkdir -p dist/rclone-manager/browser
cargo clippy --manifest-path backend/Cargo.toml -p rclone-manager-server -- -D warnings
# 3. Clippy check - Mobile target
cargo clippy --manifest-path backend/Cargo.toml -p rclone-manager --features mobile --no-default-features -- -D warnings
# 4. Rust formatting check
cargo fmt --manifest-path backend/Cargo.toml --all -- --check
# 5. Run backend unit tests
cargo test --manifest-path backend/Cargo.toml -p rclone-manager-core --features desktop --lib
cargo test --manifest-path backend/Cargo.toml -p rclone-manager --features desktop --no-default-features --lib
# 6. Updater unit tests (they spawn processes and use file locks, so CI runs them single-threaded)
cargo test --manifest-path backend/Cargo.toml -p rclone-manager-core --features updater --lib services::updates -- --test-threads=1Validate Flatpak AppStream metadata only if backend/desktop/linux/flatpak.metainfo.xml was edited and running on Linux with appstreamcli available; otherwise skip.
# Validate Flatpak AppStream metainfo (runs only on Linux when flatpak.metainfo.xml is modified, skips otherwise)
if [ "$(uname)" = "Linux" ] && command -v appstreamcli &>/dev/null; then
git diff --name-only HEAD | grep -q "flatpak.metainfo.xml" && appstreamcli validate backend/desktop/linux/flatpak.metainfo.xml || true
fi# Launch Tauri dev server
npm run tauri dev
# Feature-specific dev modes
npm run dev:portable
npm run dev:headlessnpm run sync:endpoints: Update endpoint definitions from rclone schemas.npm run sync:providers: Sync rclone provider configurations.npm run sync:flags: Sync rclone CLI flag definitions.npm run audit:i18n: Verify missing translation keys.appstreamcli validate backend/desktop/linux/flatpak.metainfo.xml: Validate Flatpak AppStream metadata (Linux only, when metadata is modified).