No description
  • TypeScript 68.6%
  • CSS 17.3%
  • JavaScript 13.7%
  • HTML 0.4%
Find a file
2026-05-30 21:37:42 +00:00
build legacy cleanup 2026-05-30 19:49:47 +00:00
electron soft locks and fixes 2026-05-30 21:37:42 +00:00
frontend soft locks and fixes 2026-05-30 21:37:42 +00:00
.gitignore electron 2026-05-30 19:18:34 +00:00
package-lock.json electron 2026-05-30 19:18:34 +00:00
package.json electron 2026-05-30 19:18:34 +00:00
README.md soft locks and fixes 2026-05-30 21:37:42 +00:00

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 — run npm run dist and open the built app. Under npm 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)

  1. Screen / WindowgetDisplayMedia; the main process's setDisplayMediaRequestHandler shows the native OS picker (useSystemPicker: true) and adds audio: '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>.
  2. Mic / cameragetUserMedia. With the camera bubble on, screen (or the cropped region) + camera are composited each frame onto the <canvas> (circular clip) → canvas.captureStream().
  3. Audio — mic + system mixed via a WebAudio MediaStreamAudioDestinationNode.
  4. RecordMediaRecordervideo/webm chunks → 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 in package.jsonbuild.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.