Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
93be193
Migrate Recordly UI to HeroUI with docked editor styling
webadderall Sep 19, 2026
3931f23
Refine HeroUI editor spacing, cards and responsive alignment
webadderall Sep 19, 2026
fc89e1d
Restore timeline palette and refine compact wallpaper controls
webadderall Sep 19, 2026
7dba6e3
Refine editor panels and add source-frame timeline filmstrips
webadderall Sep 19, 2026
5cead76
Restore 100 percent webcam roundness and verify recording defaults
webadderall Sep 19, 2026
baea6af
Restore timeline panel height without changing preview padding
webadderall Sep 19, 2026
3e53a8d
Prevent click jitter from shifting clips and restore export spacing
webadderall Sep 19, 2026
af5cd85
Add HeroUI loading skeletons and refine timeline presentation
webadderall Sep 19, 2026
07d06ab
Restore original clip handle colors
webadderall Sep 19, 2026
2b81e4c
feat(cloud): add self-hosted sharing service and web viewer
webadderall Sep 20, 2026
fd919fa
feat(auth): add desktop sign-in and cloud upload foundations
webadderall Sep 20, 2026
04cf058
feat(library): add recording storage, thumbnails and sequence imports
webadderall Sep 20, 2026
e82272f
fix(media): normalize local sources and recover preview loading
webadderall Sep 20, 2026
a40da33
feat(editor): persist clip sequences and synchronize playback
webadderall Sep 20, 2026
3f9e71f
fix(captions): prevent repeated sound labels and preserve speech from…
webadderall Sep 20, 2026
6f3f635
feat(timeline): refine clip editing and place yellow captions above f…
webadderall Sep 20, 2026
bed9f8c
feat(export): prepare edited videos for authenticated cloud sharing
webadderall Sep 20, 2026
c803055
docs(design): add HeroUI component catalogs and implementation notes
webadderall Sep 20, 2026
8331315
feat(ui): integrate recording library and cloud account into HeroUI e…
webadderall Sep 20, 2026
7bcebdb
chore(cloud): point uploads at the local development server
webadderall Sep 20, 2026
4d20066
fix(ci): install FFmpeg required by recording import tests
webadderall Sep 20, 2026
fb3d6b1
fix(lint): resolve caption and cloud integration diagnostics
webadderall Sep 20, 2026
8f2ec69
feat(import): add cancellable media processing with shared Cancel sty…
webadderall Sep 20, 2026
1f72902
fix(preview): refresh paused frames after cursor and effect changes
webadderall Sep 20, 2026
2f90589
fix(project): surface loading failures and validate media sessions
webadderall Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Public client configuration. Never put a Supabase service-role key here.
VITE_SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=sb_publishable_YOUR_KEY
8 changes: 7 additions & 1 deletion .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ jobs:
continue-on-error: true
run: npm run format:check

- name: Test
# Real recording-import tests require the binary skipped by --ignore-scripts.
- name: Install FFmpeg for integration tests
id: ffmpeg
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: npm rebuild ffmpeg-static

- name: Test
if: ${{ !cancelled() && steps.install.outcome == 'success' && steps.ffmpeg.outcome == 'success' }}
run: npm test

# Main currently has known locale-parity debt covered by PR #710. Keep the
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ electron/native/bin/*/whisper-runtime.json
tmp-*.ps1
.tmp-*.ps1
gpu-export-probe.mp4

# Browser test output
test-results/
playwright-report/
38 changes: 38 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Third-party notices

## Voom

Recordly Share includes code adapted from the Voom project:

- Project: [Voom](https://github.com/aritropaul/voom)
- Adapted component: `services/recordly-share/worker`
- License: MIT
- Copyright © 2026 Aritro Paul
- Upstream reference: `c1c0350ed610b0c9bc4a307fcc6a01aab0f926f9`

Recordly changes include product branding, desktop publishing integration,
authenticated commenting, local-development configuration, and viewer styling.

The Voom software is provided under the following license:

> MIT License
>
> Copyright (c) 2026 Aritro Paul
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
22 changes: 0 additions & 22 deletions components.json

This file was deleted.

7 changes: 7 additions & 0 deletions design-app-catalog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly app component catalog</title><style>html,body,#root{min-width:1440px!important;width:1440px!important;min-height:1000px!important}html,body{height:auto!important;overflow:auto!important}</style></head><body><div id="root"></div><script type="module">
import {installDesktopBridge} from '/tests/ui/bridge.ts';
await installDesktopBridge({addInitScript:async(fn,arg)=>fn(arg)},'filmstrip.mp4');
// Source device rows render against fixtures without opening physical microphones/cameras.
if (navigator.mediaDevices) Object.defineProperty(navigator.mediaDevices, 'getUserMedia', {configurable:true,value:async()=>{throw new DOMException('Design fixture: live device access disabled','NotAllowedError')}});
await import('/src/design-app-catalog.tsx');
</script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
5 changes: 5 additions & 0 deletions design-capture.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly design transfer</title><style>html,body,#root{min-width:1440px!important;width:1440px!important;height:1000px!important;min-height:1000px!important}</style></head><body><div id="root"></div><script type="module">
import {installDesktopBridge} from '/tests/ui/bridge.ts';
await installDesktopBridge({addInitScript:async(fn,arg)=>fn(arg)},'filmstrip.mp4');
await import('/src/main.tsx');
</script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
1 change: 1 addition & 0 deletions design-extra-catalog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly Export and Popup Catalog</title></head><body><div id="root"></div><script type="module" src="/src/design-extra-catalog.tsx"></script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
5 changes: 5 additions & 0 deletions design-hud-branches.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly HUD branch catalog</title><style>html,body,#root{min-width:1440px!important;width:1440px!important;min-height:1000px!important}html,body{height:auto!important;overflow:auto!important}</style></head><body><div id="root"></div><script type="module">
import {installDesktopBridge} from '/tests/ui/bridge.ts';
await installDesktopBridge({addInitScript:async(fn,arg)=>fn(arg)},'filmstrip.mp4');
await import('/src/design-hud-branches.tsx');
</script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
8 changes: 8 additions & 0 deletions design-inspector-catalog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html><head><meta charset="UTF-8"><title>Recordly inspector catalog</title>
<style>html,body,#root{min-width:1440px!important;width:1440px!important;height:auto!important;overflow:visible!important}.inspector-preview .overflow-y-auto{overflow:visible!important;max-height:none!important}.inspector-preview{min-height:500px}.inspector-preview>.card{overflow:visible!important}</style>
</head><body><div id="root"></div><script type="module">
import {installDesktopBridge} from '/tests/ui/bridge.ts';
await installDesktopBridge({addInitScript:async(fn,arg)=>fn(arg)},'filmstrip.mp4');
await import('/src/design-inspector-catalog.tsx');
</script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
1 change: 1 addition & 0 deletions design-library.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly Component Library</title></head><body><div id="root"></div><script type="module" src="/src/design-library.tsx"></script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
1 change: 1 addition & 0 deletions design-preview-menus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly Preview Menus</title></head><body><div id="root"></div><script type="module" src="/src/design-preview-menus.tsx"></script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
1 change: 1 addition & 0 deletions design-timeline-catalog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly Timeline Component Library</title></head><body><div id="root"></div><script type="module" src="/src/design-timeline-catalog.tsx"></script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
5 changes: 5 additions & 0 deletions design-timeline-details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly timeline detail components</title><style>html,body,#root{width:1200px!important;min-width:1200px!important;height:auto!important;overflow:visible!important}</style></head><body><div id="root"></div><script type="module">
import {installDesktopBridge} from '/tests/ui/bridge.ts';
await installDesktopBridge({addInitScript:async(fn,arg)=>fn(arg)},'filmstrip.mp4');
await import('/src/design-timeline-details.tsx');
</script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
27 changes: 27 additions & 0 deletions design-window-capture.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!doctype html>
<html><head><meta charset="UTF-8"><title>Recordly window states</title></head>
<body><div id="root"></div><script type="module">
import {installDesktopBridge} from '/tests/ui/bridge.ts';
await installDesktopBridge({addInitScript:async(fn,arg)=>fn(arg)},'filmstrip.mp4');
const query = new URLSearchParams(location.search);
const windowType = query.get('windowType');
const state = query.get('state') || 'available';
const style = document.createElement('style');
const [width, height] = windowType === 'update-toast' ? [420, 172] : windowType === 'countdown' ? [420, 320] : [1440, 1000];
style.textContent = `html,body,#root{width:${width}px!important;min-width:${width}px!important;height:${height}px!important;min-height:${height}px!important}`;
document.head.append(style);
if (windowType === 'update-toast') {
const phase = state.endsWith('error') ? 'error' : ['downloading','ready'].includes(state) ? state : 'available';
window.electronAPI.getCurrentUpdateToastPayload = async () => ({
version:'1.5.0',
detail: phase === 'error' ? 'Check your connection and try again.' : phase === 'ready' ? 'Your update is ready to install.' : 'A new version of Recordly is available.',
phase, delayMs: 3600000,
isPreview: state === 'preview',
isExperimental: state === 'experimental',
progressPercent: 42, transferredBytes: 44040192, totalBytes: 104857600, bytesPerSecond: 3145728,
primaryAction: state === 'check-error' ? 'retry-check' : 'install-and-restart',
});
}
if (windowType === 'countdown') window.electronAPI.getActiveCountdown = async () => ({success:true, seconds:Number(query.get('seconds') || 3)});
await import('/src/main.tsx');
</script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
5 changes: 5 additions & 0 deletions design-window-catalog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Recordly window catalog</title><style>html,body,#root{width:1440px!important;min-width:1440px!important;height:auto!important;overflow:visible!important}[data-figma-name="HudWindow / Idle"] [style*="100vh"]{height:100%!important;align-items:center!important}[data-figma-name="HudWindow / Idle"] [style*="translate3d"]{transform:none!important}</style></head><body><div id="root"></div><script type="module">
import {installDesktopBridge} from '/tests/ui/bridge.ts';
await installDesktopBridge({addInitScript:async(fn,arg)=>fn(arg)},'filmstrip.mp4');
await import('/src/design-window-catalog.tsx');
</script><script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script></body></html>
92 changes: 92 additions & 0 deletions docs/HEROUI_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# HeroUI UI branch

This migration is isolated on `codex/heroui-ui`, based on `9693ad7` from
`codex/clip-controls`. It lives in the sibling `recordly-heroui` Git worktree.
The original `recordly` checkout and its uncommitted files were left intact.
Nothing has been merged or pushed.

## Try it

From the original checkout:

```sh
cd ../recordly-heroui
npm run dev
```

A fresh checkout needs `npm install` first, including Recordly's normal native
helper installation. The dependency lockfile belongs to this branch.

To go back, close the development app and run `npm run dev` from the original
`recordly` directory. No reset, stash, or file restoration is needed. You can
keep both checkouts while comparing them. To undo just the UI refinement pass,
use `git revert <refinement-commit>`. To undo the entire migration after merging,
revert the branch commits newest-first, including the original `93be193` migration.

## UI approach

The controls use HeroUI React 3.2.6 and its default light/dark theme, following
[the official component demos](https://heroui.com/en/docs/react/components).
This includes buttons, fields, switches, sliders, tabs, tag groups, toggle groups, radios,
selects, modals, popovers, menus, tooltips, color pickers, progress indicators,
skeletons, and toasts. React 19 and Tailwind 4 satisfy HeroUI v3 requirements.
The former Radix, Sonner, and third-party color picker dependencies are removed.

The editor uses a floating inspector card, an open canvas and timeline, aligned
toolbars, consistent spacing, and restrained selection colors. Advanced controls
live behind a per-section switch; changing views preserves project values.
Background types and other exclusive choices use TagGroup. The header follows
native fullscreen state and keeps project titles centered at narrow widths. Floating layers keep one
surface instead of nesting cards and shadows. Timeline blocks retain the original
Recordly palette in both themes. Inspector controls use compact 12–13px text and
32–36px controls, with 14px section titles. Image and video wallpaper grids share
a plus tile for importing and a small remove control on custom tiles, revealed
on hover or keyboard focus. The background gallery slides left/right with tab order.

The clip lane samples real source frames with a bounded cache and one background
video decoder. Sampling follows clip source offsets, speed, and the visible timeline
range; it never seeks the playback element. Zooms use a compact lane with labels
that adapt to block width. The ruler chooses tick density from available width,
and the red playhead has a white centre line. Webcam roundness defaults to 100%;
existing saved values remain intact. New recordings inherit the saved webcam
appearance, and 100% is the maximum squircle radius rather than a circular mask.
Three compact timeline tracks fit before
vertical scrolling. Popovers support outside-click and Escape dismissal and
share one padding layer. Export states retain generous content padding. Timeline
selection ignores pointer jitter up to 4px, including clicks on trim handles,
so selecting a clip cannot introduce a leading gap. Project and preset names truncate inside their rows.
The recorder keeps its compact desktop layout.

The adapters in `src/components/ui` translate existing Recordly state/callback
props to HeroUI APIs. The timeline geometry, crop handles, video canvas, caption
canvas editor, waveform, and native file inputs remain application-specific;
HeroUI does not replace those editing engines. Recording/export/project logic
is retained, with nullable ref types updated for React 19.

## Verification

```sh
npm run typecheck
npm test
npx vite build
npx playwright install chromium
npm run test:ui
```

The production build includes both the renderer and Electron main/preload
bundles. It is not an installer packaging run.

Browser tests use an explicit mocked Electron bridge and a generated six-second
video fixture; they never start a real screen recording. A second changing-frame
fixture verifies that timeline thumbnails decode distinct source frames. They cover control
callbacks and keyboard behavior, modal focus, export settings, presets, cropping,
annotation formatting/undo, project menus, recorder popovers, countdown and update
windows, theme switching, Advanced state, color editing, wallpaper uploads and
keyboard/hover removal, and header/playback
alignment from 800–1440px with and without macOS window controls. Screenshots and failure
traces go to the ignored `test-results/` directory.

`npm run dev:ui` starts only Vite for browser inspection; the component fixture
is at `/tests/ui/controls.html`. The real editor requires Electron or the test
bridge. Native recording, device permissions, and end-to-end media export still
need a desktop smoke test on the target platform.
52 changes: 52 additions & 0 deletions docs/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Recordly authentication setup

Recordly uses Supabase Auth for one shared session across email/password and Google. The desktop application uses PKCE and returns from the system browser through a loopback callback in development and `recordly://auth/callback` in production. X and SAML are intentionally hidden until those providers are configured.

## 1. Create the project

1. Create a Supabase project.
2. In **Project settings → API**, copy the project URL and publishable key.
3. Copy `.env.example` to `.env.local` and fill in both values. Never use the service-role key in the desktop application.
4. In **Authentication → URL configuration**, add `recordly://auth/callback` and `http://127.0.0.1:43821/auth/callback` to the redirect allow list.

Email/password works once email authentication is enabled and a user has been created. Password-reset emails use the same desktop callback.

## 2. Google

1. Create a Web OAuth client in Google Auth Platform.
2. Add Supabase's callback URL, `https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback`, as an authorized redirect URI.
3. Enable Google under **Supabase → Authentication → Sign In / Providers** and paste the Google client ID and secret.
4. Keep the requested scopes to `openid`, email, and profile unless Recordly genuinely needs more.

## 3. X (currently hidden)

1. Create an OAuth 2.0 app in the X Developer Dashboard and enable requesting the user's email.
2. Set its callback URL to `https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback`.
3. Enable X/Twitter OAuth 2.0 in Supabase and paste the client ID and secret.

## 4. SAML SSO (currently hidden)

SAML is configured per customer workspace. Supabase's SAML support requires Pro or above.

1. Enable SAML in the Supabase Auth provider settings.
2. Obtain the customer's IdP metadata URL or metadata XML file.
3. Register the connection and its email domain with the Supabase CLI, for example:

```sh
supabase sso add --type saml --project-ref YOUR_PROJECT_REF \
--metadata-url 'https://customer.example/idp/metadata' \
--domains customer.example
```

The Recordly modal extracts the domain from the entered email address and starts the matching SAML connection.

## 5. Verify locally

Restart `npm run dev` after creating `.env.local`. Open an editor and verify:

1. The account button opens **Sign into Recordly**.
2. Email/password creates a persistent Supabase session.
3. Google opens the system browser and returns to Recordly.
4. Clicking **Create link** while signed out opens this modal; after successful authentication it continues to the share dialog.

Add the same `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` values to the share Worker's secrets or variables. The Worker validates the user's access token with Supabase before accepting an upload. `API_SECRET` is server-side only and remains available for library administration and explicitly enabled local integration tests; it is never entered into or exposed by the desktop app.
Loading
Loading