Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 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
3933531
fix(captions): preserve fallback audio and speech between microphone …
webadderall Sep 20, 2026
0514303
fix(editor): preserve completed imports and reset export state
webadderall Sep 20, 2026
510ed4b
fix(cloud): scope owner access and protect shared response data
webadderall Sep 20, 2026
c8c162c
fix: prevent caching protected share thumbnails
webadderall Sep 21, 2026
f5c9034
chore: use assertive CodeRabbit reviews
webadderall Sep 21, 2026
ce21f6c
fix: keep captions inside clips and harden timeline editing
webadderall Sep 21, 2026
be60576
fix: preserve media safely during imports and deduplicate sign-in cal…
webadderall Sep 21, 2026
bdc403e
fix: restore shared UI behavior and keyboard accessibility
webadderall Sep 21, 2026
9f89a41
fix: validate cloud password setup and stabilize comment pagination
webadderall Sep 21, 2026
9fe079e
fix: use native HeroUI toasts with copyable errors
webadderall Sep 21, 2026
fc3c69c
chore: exclude standalone design catalogs from CodeRabbit
webadderall Sep 21, 2026
f43d137
fix: preserve caption timing and media lifecycle across failures
webadderall Sep 21, 2026
5c04751
fix: close upload streams and validate cloud auth and ranges
webadderall Sep 21, 2026
ab7fd8b
fix: localize cloud dialogs and correct editor controls
webadderall Sep 21, 2026
709c72b
chore: remove stale Figma capture previews and references
webadderall Sep 21, 2026
1a28908
fix: keep cloud page effects and keyboard handlers current
webadderall Sep 21, 2026
35335d4
refactor: split cloud Worker by responsibility
webadderall Sep 21, 2026
f40b2cb
fix: strengthen cloud sessions, share secrets and password storage
webadderall Sep 21, 2026
10afa9a
fix: retain import rollback until the editor accepts its result
webadderall Sep 21, 2026
40272c8
fix: stop live camera access while in editor mode
webadderall Sep 21, 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
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
reviews:
profile: assertive
auto_review:
enabled: true
base_branches:
Expand Down
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.

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 is shown in the sign-in dialog and requires its provider to be enabled in Supabase. SAML is intentionally hidden until 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

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. Set `OWNER_USER_ID` to the owner’s Supabase user ID. The Worker validates the access token with Supabase and requires that owner identity before accepting API requests. `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.
30 changes: 30 additions & 0 deletions docs/cloud-sharing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Recordly cloud sharing

Recordly's **Export** menu offers downloading the current edit or **Create share link**. The Account button at the bottom of the left toolbar opens sign-in. Sharing renders an MP4 to a temporary file, uploads it to Recordly Share, finalizes its metadata, and removes the temporary file when the dialog closes.

The desktop app defaults to the local development endpoint:

```text
http://localhost:8787/api/upload
```

The endpoint is intentionally not user-configurable. All builds currently use the local service above. Production service integration is planned but is not selected by any build. Publishing requires the user's Recordly access token. No share API secret is exposed in the app.

## Publishing protocol

1. `POST /api/upload` creates a recording and share code.
2. Small files use `PUT /api/upload-data/:shareCode`; files over 50 MB use multipart uploads in retryable 25 MB chunks.
3. `POST /api/metadata/:shareCode` publishes the title and creator notes.
4. The returned `/s/:shareCode` URL opens the complete viewing and feedback page.

The service provides R2 video storage, D1 metadata, range streaming, a responsive viewing page, timestamped comments, reactions, captions, password protection, expiry, and a private video library. See [the service README](../services/recordly-share/worker/README.md) for local and deployment instructions.

## Comments

Anyone with a valid link can watch a public recording and leave timestamped feedback using a display name. No account or sign-in is required. The existing per-IP rate limit and 2,000-character comment limit still apply.

## Third-party licensing

The hosting service is based on MIT-licensed open-source software. Required attribution and the complete license text are preserved in [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) and [the vendored license](../services/recordly-share/LICENSE).

The self-hosted worker is a single-owner library. Set `OWNER_USER_ID` to the owner’s Supabase user ID. Other accounts in the same Supabase project cannot administer the library. Missing owner configuration disables Supabase bearer access.
25 changes: 25 additions & 0 deletions docs/timeline-sequence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Clip sequence timing

Primary footage is an ordered, contiguous sequence. A visual clip separator has no duration. Both sides of a cut at 2.8 seconds represent 2.8 seconds, even if the second clip starts at a different point in the source recording.

- `ClipRegion.startMs/endMs`: edited timeline coordinates.
- `sourceStartMs`: the retained recording in-point. Packing or reordering never derives this from the new timeline position.
- `speed`: source milliseconds consumed per timeline millisecond.
- `clipSequence.ts`: packs trims/deletes/speed changes, handles explicit insertion indices, and maps connected effects and audio anchors.
- `timeline/core/clipPresentation.ts`: maps media time into inset clip rectangles and back. Scrubbing a separator returns its cut timestamp. The playhead does not wait or animate through a separator.

Clip resizing is constrained by available source footage and minimum clip duration, rather than collision with the next clip. Dragging a clip chooses an insertion index rather than looking for empty space. Source-based captions are reprojected from their original cues. Imported audio retains its duration as its anchor moves. Annotation selection is independent of whether the annotation is visible at the current playhead time.

Legacy projects with primary-track gaps are normalized before establishing the loaded undo baseline. Editing and export use the same contiguous clip positions.

## Verification

135 focused unit tests passed for sequence packing/rippling, persistence, drag and resize resolution, visual seam mapping, playback, annotation visibility and deletion shortcuts. All 13 renderer regressions passed for block deletion, dragged annotation selection, background transitions, gap-free trims, clip reordering, and connected annotation/audio undo. These browser checks use fixture media and a mocked Electron bridge.

## Boundary snapping and preview motion

Timeline effects use the same media-to-display mapping as clips and the playhead. Dragging or resizing a zoom, annotation, caption, or audio block snaps within 8 screen pixels of other block boundaries, including clip cuts, at every timeline zoom level. Dragging preserves the block duration. Moving beyond that distance releases the snap. Zoom blocks can span visual separators continuously.

Hover previews and caption placement use the same inverse mapping. Keyframe positions and playhead snapping use media time consistently. Source-video seeks at contiguous cuts preserve camera springs; explicit timeline seeks still snap to the requested frame.

Caption visibility was checked in the real renderer with fixture footage at 1×, 2× and 4×, including source-frame matching, cue visibility boundaries and moving playback. The caption test captures the preview at 2×. This verifies rendering and timing, not speech-recognition accuracy or a packaged export.
Loading
Loading