EmberlyEmberly Docs

Flicker Development Guide

Complete guide to building, developing, and contributing to Flicker desktop application.

Flicker is built with Tauri 2.0, React 19, and Rust. This guide covers project setup, development workflow, architecture, and contribution guidelines.


Project Overview

AspectDetails
Repositoryhttps://github.com/EmberlyOSS/Flicker
LicenseMIT (Open Source)
Current Versionv0.1.0-alpha (Jan 2, 2026)
FrameworkTauri 2.0 (Cross-platform desktop)
FrontendReact 19 + TypeScript + Tailwind CSS
BackendRust with async Tokio runtime
Package Managerbun (recommended) / npm
PlatformsWindows, macOS, Linux

Architecture Overview

High-Level Structure

Flicker (Tauri App)
├── Frontend (React + TypeScript)
│   ├── UI Components (Pages, Settings, Upload)
│   ├── State Management (Context API)
│   ├── Hooks (useHotkeys, useTheme, useNotifications)
│   └── Styling (Tailwind CSS + custom themes)

├── Tauri Bridge (IPC Communication)
│   └── Serialized JSON commands/events

└── Backend (Rust)
    ├── Tauri Command Handlers
    │   └── Screenshot, Upload, Config
    ├── Common Modules
    │   ├── Emberly API Client
    │   ├── File Operations
    │   ├── Authentication
    │   └── History Management
    └── Platform-Specific Code
        ├── Desktop (Windows, macOS, Linux)
        └── Mobile (Future)

Data Flow

User Hotkey (System)

Tauri Global Shortcut Plugin

Frontend JS Handler (useHotkeys)

Invoke Rust Command: capture_screenshot()

Rust Backend (Desktop Module)

Native Screenshot Capture (OS-specific)

Save to temp file

Invoke: upload_file()

Multipart HTTP POST to Emberly API

Receive UploadResponse with URL

Add to history (localStorage)

Copy URL to clipboard

Show success notification

Prerequisites & Setup

System Requirements

All Platforms:

  • Node.js 18+ (node --version)
  • Rust 1.70+ (rustc --version)
  • Git

Windows:

  • Visual Studio Build Tools or MSVC compiler
  • Windows 10 or later

macOS:

  • Xcode or Command Line Tools (xcode-select --install)
  • macOS 11 or later

Linux:

  • Build essentials: sudo apt install build-essential
  • GTK 3.0+ development files: sudo apt install libgtk-3-dev
  • libssl-dev: sudo apt install libssl-dev

Installation

1. Clone Repository

git clone https://github.com/EmberlyOSS/Flicker.git
cd Flicker

2. Install Node Dependencies

# Using bun (recommended, faster):
curl -fsSL https://bun.sh/install | bash
bun install
 
# Or using npm:
npm install
 
# Or yarn:
yarn install

3. Install Rust (if not already installed)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env

4. Verify Installation

node --version        # Should be v18+
bun --version        # If using bun
npm --version        # If using npm
rustc --version      # Should be 1.70+
cargo --version      # Should be 1.70+

Development Workflow

Starting Development Server

# Main development command
bun run dev
# or
npm run dev
 
# This:
# 1. Starts Vite dev server on localhost:5173
# 2. Compiles Rust backend
# 3. Launches Tauri development window
# 4. Enables hot reload for React components
# 5. Watches Rust files for changes

First startup may take 2-3 minutes as Rust builds. Subsequent runs are faster using incremental compilation.

Development Window

When bun run dev succeeds:

  • Tauri window opens with Flicker app
  • Layout: 900×800 pixels (resizable)
  • DevTools available: F12 or Ctrl+Shift+I
  • Hot reload on save (React components only)
  • Rust changes require app restart

File Structure During Development

Flicker/
├── src/                    ← React frontend (edit & hot reload)
│   ├── components/         ← All React components
│   ├── hooks/             ← Custom React hooks
│   ├── context/           ← Context API state
│   ├── App.tsx            ← Root component
│   └── main.tsx           ← DOM render entry

├── src-tauri/             ← Rust backend
│   ├── src/
│   │   ├── main.rs        ← Entry point
│   │   ├── lib.rs         ← Command handlers
│   │   ├── common/        ← Shared modules
│   │   ├── desktop/       ← Platform code
│   │   └── mobile/        ← Mobile stubs
│   ├── Cargo.toml         ← Rust dependencies
│   └── tauri.conf.json    ← Tauri configuration

├── index.html             ← HTML entry
├── package.json           ← Frontend dependencies
├── tsconfig.json          ← TypeScript config
├── vite.config.ts         ← Vite build config
└── tailwind.config.js     ← Tailwind theme

Building & Packaging

Development Build

# Full build (React + Rust)
bun run build
 
# Outputs to: src-tauri/target/release/bundle/

Platform-Specific Builds

# Windows (requires Windows)
npm run build -- --target x86_64-pc-windows-msvc
 
# macOS Intel
npm run build -- --target x86_64-apple-darwin
 
# macOS Apple Silicon
npm run build -- --target aarch64-apple-darwin
 
# Linux
npm run build -- --target x86_64-unknown-linux-gnu
 
# Bundle all targets (takes longer)
npm run build

Build Artifacts

After npm run build, installers appear in:

Windows:
  src-tauri/target/release/bundle/msi/        (.msi installer)
  src-tauri/target/release/bundle/nsis/       (.exe installer)

macOS:
  src-tauri/target/release/bundle/dmg/        (.dmg disk image)
  src-tauri/target/release/bundle/macos/      (.app bundle)

Linux:
  src-tauri/target/release/bundle/appimage/   (.AppImage)
  src-tauri/target/release/bundle/deb/        (.deb package)

Code Standards

TypeScript / React

File Structure:

// src/components/MyComponent.tsx
import React, { useState, useEffect } from 'react';
import type { ComponentProps } from '@/types';  // Import types
 
interface MyComponentProps {
  title: string;
  onUpload?: (file: File) => void;
}
 
/**
 * MyComponent - Brief description
 * 
 * @param props Component props
 * @returns Rendered component
 */
export const MyComponent: React.FC<MyComponentProps> = ({
  title,
  onUpload
}) => {
  const [state, setState] = useState<string>('');
 
  useEffect(() => {
    // Side effects
  }, []);
 
  return (
    <div className="space-y-4">
      <h1>{title}</h1>
      {/* JSX */}
    </div>
  );
};

Guidelines:

  • Use functional components with hooks
  • TypeScript strict mode (no any types)
  • Use interfaces for props
  • Document with JSDoc comments
  • Use Tailwind CSS classes
  • Prefer composition over inheritance
  • Keep components focused (single responsibility)

Rust Code

Command Handler Example:

// src-tauri/src/lib.rs
#[tauri::command]
async fn capture_screenshot(
    state: tauri::State<'_, AppState>,
    monitor_index: Option<usize>,
) -> Result<ScreenshotResult, String> {
    match desktop::screenshot::capture_screenshot(monitor_index).await {
        Ok(result) => Ok(result),
        Err(e) => Err(format!("Screenshot failed: {}", e)),
    }
}

Guidelines:

  • Use Result<T, E> for error handling
  • Document with /// doc comments
  • Use async/await with Tokio
  • Proper error messages for frontend
  • Validate inputs (file size, formats)
  • Use modules for organization

Styling with Tailwind

Theme System:

// src/colors.ts - Define theme colors
export const themes = {
  'stranger-things': {
    primary: 'hsl(260 100% 50%)',  // Purple
    success: 'hsl(120 100% 50%)',  // Green
    // ...
  }
};
 
// In component:
<div className="bg-primary text-primary-foreground">
  {/* Tailwind applies CSS variables */}
</div>
 
// CSS variables set by theme hook:
// :root {
//   --primary: hsl(260 100% 50%);
//   --primary-foreground: hsl(0 0% 100%);
// }

Testing

Unit Tests (Coming v0.2.0)

# Run tests
npm run test
 
# Watch mode
npm run test -- --watch
 
# Coverage report
npm run test -- --coverage

Test Structure:

// src/components/__tests__/MyComponent.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MyComponent } from '../MyComponent';
 
describe('MyComponent', () => {
  it('should render with title', () => {
    render(<MyComponent title="Test" />);
    expect(screen.getByText('Test')).toBeInTheDocument();
  });
 
  it('should call onUpload when clicked', () => {
    const onUpload = vi.fn();
    render(<MyComponent onUpload={onUpload} />);
    screen.getByRole('button', { name: /upload/i }).click();
    expect(onUpload).toHaveBeenCalled();
  });
});

Manual Testing Checklist

Before submitting PR, test:

  • App launches without crashes
  • Screenshots capture correctly
  • Uploads succeed with valid auth
  • Hotkeys work system-wide
  • Settings persist after restart
  • All themes apply correctly
  • UI is responsive
  • No console errors (F12)
  • Notifications display
  • History updates

Architecture Deep Dive

Frontend State Management

AppContext:

interface AppContextType {
  // Authentication
  isLoggedIn: boolean;
  user: UserProfile | null;
  
  // Configuration
  config: AppConfig;
  updateConfig: (updates: Partial<AppConfig>) => void;
  
  // Upload History
  history: UploadHistory[];
  addToHistory: (upload: UploadHistory) => void;
  
  // UI State
  currentPage: 'upload' | 'history' | 'settings' | 'analytics';
  setCurrentPage: (page: string) => void;
  
  // Notifications
  notifications: Notification[];
  addNotification: (notification: Notification) => void;
}

Usage:

import { useAppContext } from '@/context/AppContext';
 
export function MyComponent() {
  const { isLoggedIn, user, addNotification } = useAppContext();
  
  if (!isLoggedIn) return <LoginOverlay />;
  
  return <div>Welcome, {user?.name}!</div>;
}

Tauri Commands (IPC Bridge)

From Frontend (TypeScript):

import { invoke } from '@tauri-apps/api/core';
 
async function uploadFile(filePath: string) {
  try {
    const response = await invoke<UploadResponse>('upload_file', {
      filePath,
      visibility: 'public'
    });
    console.log('Uploaded:', response.url);
  } catch (error) {
    console.error('Upload failed:', error);
  }
}

From Backend (Rust):

#[tauri::command]
async fn upload_file(
    file_path: String,
    visibility: String,
) -> Result<UploadResponse, String> {
    // Implementation
}

Emberly API Integration

Client Location: src-tauri/src/common/emberly_api.rs

Key Endpoints:

impl EmberlyCient {  // Note: typo in actual code
    pub async fn login(email: &str, password: &str) -> Result<LoginResponse>;
    pub async fn upload_file(
        token: &str,
        file: File,
        metadata: FileMetadata,
    ) -> Result<UploadResponse>;
    pub async fn get_profile(token: &str) -> Result<UserProfile>;
}

Usage in upload flow:

let response = EmberlyCient::upload_file(
    token,
    file,
    FileMetadata {
        filename: "screenshot.png".to_string(),
        visibility: "public".to_string(),
    }
).await?;
 
// Response contains shareable URL
println!("URL: {}", response.url);

Key Components

UploadArea

File: src/components/upload/UploadArea.tsx

Purpose: Main UI for file uploads

Features:

  • Drag-and-drop zone
  • File picker button
  • Progress display
  • Error messages
  • Upload preview

Props:

interface UploadAreaProps {
  onUploadStart?: () => void;
  onUploadComplete?: (result: UploadResult) => void;
  onError?: (error: string) => void;
}

ScreenshotToast

File: src/components/overlays/ScreenshotToast.tsx

Purpose: Show upload progress and status

Displays:

  • Upload progress percentage
  • File name being uploaded
  • Success/error state
  • Time elapsed
  • URL for copying

SettingsPage

File: src/components/settings/SettingsPage.tsx

Features:

  • Tabbed interface (Hotkeys, Appearance, Behavior, Capture, Account)
  • Real-time config updates
  • Settings validation
  • Reset to defaults button

Common Development Tasks

Adding a New Setting

1. Define Type in src/types.ts:

export interface AppConfig {
  // ... existing fields
  myNewSetting: string;  // Add here
}

2. Add to Default Config (src/config.ts):

export const DEFAULT_CONFIG: AppConfig = {
  // ... existing
  myNewSetting: 'default_value',
};

3. Create Setting Component (src/components/settings):

// NewSettingComponent.tsx
export function NewSetting() {
  const { config, updateConfig } = useAppContext();
  
  return (
    <input 
      value={config.myNewSetting}
      onChange={(e) => updateConfig({ myNewSetting: e.target.value })}
    />
  );
}

4. Add to Settings Page:

// SettingsPage.tsx - import and add to correct tab
import { NewSetting } from './NewSettingComponent';
 
export function SettingsPage() {
  return (
    <div>
      {/* ... other sections */}
      <section>
        <h3>My Section</h3>
        <NewSetting />
      </section>
    </div>
  );
}

Adding a New Tauri Command

1. Implement in Rust (src-tauri/src/lib.rs):

#[tauri::command]
async fn my_command(param: String) -> Result<String, String> {
    match do_something(&param) {
        Ok(result) => Ok(result),
        Err(e) => Err(format!("Error: {}", e)),
    }
}

2. Use in React (src/components/):

import { invoke } from '@tauri-apps/api/core';
 
const result = await invoke<string>('my_command', { param: 'value' });

Adding a New Theme

1. Add to src/colors.ts:

export const themes = {
  // ... existing themes
  'my-theme': {
    // 15+ color variables in HSL format
    primary: 'hsl(200 100% 50%)',
    secondary: 'hsl(160 100% 50%)',
    // ... others
  }
};

**2. Theme will automatic appear in Settings → Appearance


Git Workflow

Branch Naming

feature/* - New features      (feature/screenshot-filters)
fix/*     - Bug fixes         (fix/upload-timeout)
docs/*    - Documentation    (docs/api-guide)
refactor/*- Code cleanup     (refactor/component-structure)
perf/*    - Performance      (perf/image-compression)

Commit Messages

feat(upload): add retry logic for failed uploads
fix(hotkeys): prevent duplicate hotkey registration
docs(setup): clarify prerequisites
refactor(screenshots): simplify capture code
perf(ui): optimize component re-renders

Pull Request Process

1. Create feature branch:

git checkout -b feature/my-feature

2. Make changes and test:

bun run dev
# Test thoroughly

3. Commit with clear messages:

git add .
git commit -m "feat(upload): add password protection"

4. Push and create PR:

git push origin feature/my-feature
# Go to GitHub and create Pull Request

5. PR Description:

## Description
What this PR does
 
## Why
Why it was needed
 
## Changes
- Change 1
- Change 2
 
## Testing
How to test it
 
## Screenshots
Before/after (if applicable)

Debugging

Chrome DevTools

Press F12 or Ctrl+Shift+I to open DevTools:

  • Console — View logs and errors
  • Elements — Inspect React components
  • Network — Monitor API calls to Emberly
  • Sources — Debug JavaScript
  • Performance — Profile app performance

Rust Debugging

See Rust logs in console:

RUST_LOG=debug bun run dev

Add debug prints:

println!("Debug: {:?}", my_value);
eprintln!("Error: {}", error);

Common Issues

Hot reload not working:

# Restart dev server
Ctrl+C
bun run dev

Rust compilation error:

# Clean and rebuild
cargo clean
bun run dev

Module not found errors:

# Reinstall dependencies
rm -rf node_modules
bun install

Performance Optimization

React Optimization

// Use React.memo for expensive components
export const MyComponent = React.memo(
  ({ prop }: Props) => {
    return <div>{prop}</div>;
  },
  (prev, next) => prev.prop === next.prop
);
 
// Use useMemo for expensive calculations
const memoizedResult = useMemo(
  () => computeExpensiveValue(data),
  [data]
);
 
// Use useCallback for stable function references
const handleClick = useCallback(() => {
  doSomething();
}, []);

Image Optimization

// Compress before upload
async function compressImage(file: File): Promise<Blob> {
  const canvas = await getCanvasContext(file);
  return new Promise(resolve => {
    canvas.toBlob(resolve, 'image/png', 0.8);  // 80% quality
  });
}

Lazy Loading

// Lazy load heavy components
const SettingsPage = lazy(() => import('./pages/Settings'));
 
export function Router() {
  return (
    <Suspense fallback={<Loading />}>
      <SettingsPage />
    </Suspense>
  );
}

Security Considerations

API Keys & Tokens

  • Never commit tokens to git
  • Use .env files (add to .gitignore)
  • Environment variables: process.env.MY_TOKEN

User Data

  • Passwords transmitted over HTTPS only
  • Tokens stored in secure storage (Tauri plugin)
  • Don't log sensitive data

File Uploads

  • Validate file type before upload
  • Check file size limits
  • Scan for malware (Emberly does this)
  • Throttle upload requests

Resources


Getting Help


Contributing

See Contributing Guide for full guidelines on:

  • Code standards and style
  • Testing requirements
  • PR process
  • Commit message conventions