- TypeScript 68.6%
- CSS 17.3%
- JavaScript 13.7%
- HTML 0.4%
| build | ||
| electron | ||
| frontend | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
Recly Desktop
A Loom-style menu-bar screen recorder built with Electron + React. Capture is
Chromium-native: getDisplayMedia (screen, with audio: 'loopback' for system
audio), getUserMedia (camera/mic), composited and recorded with MediaRecorder.
Pivoted from Wails → Electron (see the repo-root plan): the Wails v3 alpha couldn't do webview media capture and its secondary-window transparency was broken on macOS. Electron's Chromium renderer does all of this natively — the same shell Loom/Tella use. The Wails Go app (
internal/,services/,main.go,go.mod) is legacy/unused and can be deleted.Backend-free build: recordings save as a local .webm, auth is faked (
test/test), and the upload/share-link states are simulated.
Run (macOS / Linux)
cd desktop
npm install # installs Electron + electron-builder, then the renderer deps
npm start # builds the renderer and launches Electron
# or, with hot reload:
npm run dev
# package a distributable (.dmg on macOS):
npm run dist
First-run onboarding requests Camera + Mic up front and walks you through enabling Screen Recording (System Settings → Privacy, then Relaunch — there's an in-app button). Permissions are pre-flighted so a prompt never appears mid-recording, and Start stays gated until Screen Recording is granted (no soft-lock).
Use a packaged build to test permissions. macOS attributes TCC (camera/mic/screen) correctly only to a signed
.app— runnpm run distand open the built app. Undernpm start/electron .the grant cycle is attributed to the dev binary and won't behave like production.
Sources: Screen / Window use the native macOS picker; Region opens a dim overlay to drag an area, and a red recording frame is drawn around it while you record (just outside the captured pixels, so it isn't in the file).
Layout
desktop/
├── package.json # Electron app manifest + electron-builder config
├── electron/
│ ├── main.cjs # main process: tray (no-dock), transparent panel window,
│ │ # setDisplayMediaRequestHandler (useSystemPicker + loopback),
│ │ # IPC (saveRecording / reveal / permissions / relaunch / resize)
│ └── preload.cjs # contextBridge → window.recly (contextIsolation on)
└── frontend/ # the React renderer (Vite) — reused from the Wails build
├── index.html
├── public/ # recly.css design tokens, fonts, brand SVGs
└── src/
├── App.tsx # the full state machine + all screens (UI reused 1:1)
├── ui.tsx # design-system components (Btn, Icon, Dropdown, …)
├── media.ts # getUserMedia camera self-view + mic level meter (preview)
└── recly/
├── recorder.ts # THE engine: getDisplayMedia + getUserMedia + canvas
│ # composite (camera bubble) + WebAudio mix + MediaRecorder
├── api.ts # window.recly IPC wrapper + device enumeration
└── types.ts
How capture works (renderer)
- Screen / Window —
getDisplayMedia; the main process'ssetDisplayMediaRequestHandlershows the native OS picker (useSystemPicker: true) and addsaudio: 'loopback'for system sound. Region — a transparent fullscreen overlay window lets you drag a rectangle (returned as display fractions); main captures that display without a picker and the recorder crops the stream to the rect on a<canvas>. - Mic / camera —
getUserMedia. With the camera bubble on, screen (or the cropped region) + camera are composited each frame onto the<canvas>(circular clip) →canvas.captureStream(). - Audio — mic + system mixed via a WebAudio
MediaStreamAudioDestinationNode. - Record —
MediaRecorder→video/webmchunks → on stop, the blob is written to~/Movies/Recly/Recly-<timestamp>.webm(temp → atomic rename) and shown in the in-app Review player.
Notes / follow-ups
- Output is webm today; optional mp4 remux (bundled ffmpeg) is a follow-on.
- System audio loopback needs macOS 14.2+ (CoreAudio tap) +
NSAudioCaptureUsageDescription(set inpackage.json→build.mac.extendInfo). - This headless build environment can't launch the GUI (no display); the app is
verified by
npm run build:renderer+ Electron install. Run it on a real machine.