Skip to content

feat: export profiles, safe-area guides, correct Fill preview - #1836

Merged
magic-peach merged 15 commits into
mainfrom
feat/export-profiles-v2
Nov 3, 2026
Merged

magic-peach merged 15 commits into
mainfrom
feat/export-profiles-v2

Conversation

@magic-peach

@magic-peach magic-peach commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #1835 (the redesign). Merge that first, then change this PR's base to main.

Option D: export profiles, ported from the earlier branch (bf2ec0d) onto the redesigned editor. This is Phase 5 item 2, brought forward because you chose C and D together.

What it adds

  • Profiles, in a new "Profile" section at the top of the Output panel:
    • five built-ins: Landscape 1080p, Landscape 720p, Square 1080, Portrait 4 : 5, Portrait 9 : 16;
    • profiles you save, rename and delete, stored in this browser;
    • applying one is a single undo step that keeps your trims and overlays;
    • the section summary shows the matching profile, or "Custom settings".
  • Safe-area guides (90% / 80%) in the preview toolbar. They're preview-only and never exported.
  • The Fill preview now crops like the encoder. src/lib/framing.ts computes the frame from the real source aspect ratio with the encoder's rule, and resolveOutputSize is shared by the worker and the UI.
  • Design notes and limits are in docs/EXPORT_PROFILES.md.

Changes from the original commit

  • Usage telemetry, newly added (one of your Phase 5 rules): a feature_used event for profile apply, save, rename and delete, and for safe-area show and hide. It carries the built-in slug or custom, never a profile's name, which is user text (src/lib/featureTelemetry.ts, with tests). Documented in docs/DATADOG.md.

  • Accessibility: the redesign's Fit and Fill buttons had no accessible name, so they now have explicit labels. The profile buttons already had them: "Square 1080, 1080 × 1080, MP4, Fit".

  • scripts/perf/profiles.js (puppeteer) is replaced by test/e2e/profiles.spec.ts (Playwright, both engines). It applies a profile in the real UI, sets framing and format, shows the safe area, exports and checks the size:

    Profile Framing Format Size checked
    Square 1080 Fill MP4 1080 × 1080 ✓
    Portrait 9 : 16 Fit WebM 1080 × 1920 ✓
    Landscape 720p Fill MKV 1280 × 720 ✓
    Landscape 720p Fit GIF 1280 × 720 ✓
  • The README line from the original commit is left out to avoid clashing with docs: rewrite README; deployment, usage and contributing guides; credits #1833's rewrite. Once both are merged, the feature belongs in the README feature list and docs/USAGE.md.

Checks run locally

  • Vitest: 38 files, 474 tests, including 56 profile unit tests that run every built-in × format through the real argument builder, plus hook and UI tests.
  • Typecheck and lint are clean.
  • Playwright: 28 passed on both engines (6 skipped are the 300 MB clip, run separately on feat(ui): port the editorial redesign; keep exports smooth #1835).
  • Not checked: Safari and Firefox, and the Netlify preview, which only builds once this PR targets main.

magic-peach and others added 14 commits October 21, 2026 10:48
…entation

UI
- One design system on the existing tokens (--bg/--surface/--border/--text/
  --muted unchanged) plus surface-2, border-strong, accent-text, on-accent,
  focus, status, radius and motion tokens; film-red accent used sparingly
- System font stack (no font requests); defines the font-heading/display and
  animate-fade-in utilities that were used everywhere but never existed
- Editor layout: preview first with a toolbar below it, edit tools as
  accordions with value summaries, sticky Output panel with pinned Export
- High-contrast theme is now reachable (toggle + prefers-contrast)
- Clearer upload, export, error/retry and success states; honest format and
  size copy; shared .btn/.choice/.field component classes

Performance
- Remove the universal transition and `transition: all`; reduced-motion support
- Stop re-rendering the editor on every timeupdate; debounce URL sync
- Thumbnails: capped, abortable, seek timeouts, batched, URLs revoked
- Lazy-load Datadog; drop looping Lottie animations; fix privacy-banner CLS
- Worker: dedupe progress, always pass -t (#1289 only patched the unused
  copy), cap encoder threads under cross-origin isolation (core-mt deadlock)
- Restart the worker after a failed export; settle loads on terminate

Telemetry: privacy-safe export/ffmpeg/thumbnail events and duration vitals,
action-name masking, query strings stripped.

Tests: 61 new (175 total). Lighthouse mobile (local build): 86 -> 100,
LCP 3.40s -> 1.12s, CLS 0.096 -> 0. See docs/PERFORMANCE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vercel.json: drop the redundant legacy `routes` block. With it, Vercel
  compiled the header rule after `handle: filesystem`, so no header reached
  static pages (confirmed with an offline `vercel build`). Leave HSTS to
  Vercel's stronger default.
- One FFmpeg command builder (src/lib/ffmpegArgs.ts) used by the worker.
  Sharpness and volume were silently ignored by the copy that actually ran.
- Cap decoder/filter/encoder threads only when the multi-threaded core
  loaded; fall back to the single-threaded core if core-mt fails to load.
- Stall watchdog with worker heartbeats: a wedged export fails with an
  actionable message and retries on the single-threaded core.
- WebM: VP8 + Vorbis. libvpx-vp9 and libopus trap in @ffmpeg/core 0.12.10,
  so WebM exports never succeeded (also on main).
- Fix a hung "Exporting" state on id-less worker errors, a leaked log
  listener, cancel during engine load, and string rejections from wasm traps.
- RUM never starts on localhost/LAN/automated browsers unless a
  non-production env is set; beforeSend scrubs file names, captions, paths
  and query strings from auto-collected events.
- Tests for the real worker command path, deploy config, telemetry env and
  sanitising; scripts/perf/matrix.js and check-headers.mjs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Palette tokens from the reference (near-black, slate, warm brown,
  charcoal, warm ivory); warm brown marks selection, film red is kept for
  Export/Download, playhead, trim range and progress. High contrast intact.
- Self-hosted Cormorant Garamond (next/font, no runtime request) for headings,
  the wordmark and the export percentage; system sans for every control.
- Source / Edit / Output sections with parenthetical markers, hairline
  separators instead of cards, pill buttons, hairline sliders and progress.
- Dark "screen" drop zone, numbered two-stage export dialog with a large
  serif percentage, serif completion panel with a Keep editing action.
- CSS-only motion: masked headline rise, staggered fades, frame settle,
  grid-row disclosures (closed content is visibility:hidden), hover zoom on
  thumbnails. Reduced motion disables all of it; nothing animates the LCP
  element or the <video>.
- Export dialog no longer announces every percent to screen readers.
- Tooling: bench.js detects collapsed panels with checkVisibility(); local
  server serves contact.html for /contact like Vercel. Stabilised a
  flaky render-count test baseline.

Lighthouse (mobile, median of 5): perf 100, LCP 1.10s -> 1.40s, CLS 0,
+78 KB fonts. See docs/PERFORMANCE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Replace the Google Fonts Cormorant Garamond files (variable 300-700,
  Latin-1) with subsets of the styles the UI uses: upright 400-500 variable
  (18 KB, preloaded) and italic 400 static (12 KB), ASCII plus typographic
  punctuation. Built reproducibly by scripts/fonts/build-cormorant.sh from a
  pinned google/fonts commit; OFL included.
- Lighthouse mobile, median of 5 vs e887f10: LCP 1,404 -> 1,253 ms,
  transfer 281 -> 235 KB, CLS 0.
- Remove role="status" from the download panel: completion was announced
  twice and the whole panel (file name field included) read out.
- Align the mobile/tablet export bar with the page gutters.
- docs/PERFORMANCE.md: font audit, corrected LCP element, browser checks,
  limitations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Production (Netlify) is cross-origin isolated, so every browser loads the
multi-threaded core. With FFmpeg choosing its own thread count, core-mt's
32-worker pool deadlocks and every export sits at "Exporting 100%" forever.
Reproduced locally with COOP/COEP for MP4, WebM, portrait and no-audio
inputs; the single-threaded core was unaffected.

Ports the export hardening from the earlier branch (33b2c5a, bf26cf1):
- src/lib/ffmpegArgs.ts: one command builder used by the worker (the old
  copy in ffmpeg.ts was dead code); decoder, filter and encoder threads
  capped at 4 on the multi-threaded core only
- stall watchdog with worker heartbeats: 60 s without activity fails the
  export with ExportStalledError, and later attempts use the
  single-threaded core
- WebM uses VP8 + Vorbis: libvpx-vp9 and libopus trap in @ffmpeg/core
  0.12.10
- SRI hashes for the multi-threaded core files; falls back to the
  single-threaded core if core-mt fails to load
- id-less worker errors, cancel during engine load, and string rejections
  from wasm traps no longer leave the editor stuck

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests

- Live-recorded files (MediaRecorder screen recordings) report an Infinity
  duration; seek to the end to find the real one (src/lib/mediaDuration.ts),
  in the editor and the export. Trim works on them again.
- ffmpegArgs: time values are repaired before use (withSafeTimes), -t only
  for a finite positive length, -shortest for looped music when the length
  is unknown, and a final check that no argument carries Infinity or NaN.
  An infinite speed no longer hangs the audio filter builder.
- Watchdog: besides 60 s of silence, stop exports whose encoded time stops
  changing for 120 s while FFmpeg keeps logging (stuck at 100%), or that run
  far past the expected length. The worker reports encoded time and computes
  progress from it. The message says what happened and to press Retry.
- Telemetry: unknown_duration failure category and stall_reason.
- scripts/make-fixtures.sh (test/fixtures/, gitignored), a static server with
  COOP/COEP (scripts/serve-isolated.mjs), Playwright export tests on the
  multi- and single-threaded engines, and a Tests workflow running Vitest and
  Playwright in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…read default 2

- Threads per stage are min(4, navigator.hardwareConcurrency || 2); the
  fallback for an unreported core count was 4.
- When the multi-threaded engine stalls, exportVideo retries the same export
  (same file and edits) once on the single-threaded engine instead of failing.
  The overlay says so, and RUM gets engine_fallback with the stall reason;
  the attempt's single outcome then carries engine "single" and fell_back.
- A single-threaded stall, or a stall after cancelling, is not retried; the
  error tells the user to press Retry.
- The editor passes the duration it already read to the export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They are untracked local copies (names ending in " 2"), not project files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion-e2e

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	docs/DATADOG.md
#	src/lib/ffmpeg.ts
#	src/lib/tests/exportTelemetry.test.ts
export_cancelled carries stuck: true when the progress percentage hadn't
changed for 60 s, so monitors can count a user giving up on a hang as a
failure instead of an ordinary cancellation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges the editorial "screening room" UI (031c44f, on top of ec05d58)
into the export-fix branch, keeping everything main gained since:

- undo/redo controls (in the Edit section header), the project panel and
  the recovery notice from #1822, restyled to the new design
- overlay settings stay in the undoable edit state; the separate overlay
  localStorage effects from the old UI are dropped
- the engine-fallback notice from #1831 in the new export dialog
- FFmpeg, worker and telemetry code stay at their current versions; the
  old copies in the merged commit are not used
- the hook keeps ExportAttempt telemetry and saved projects, and takes the
  redesign's describeExportError, outputDimensions, debounced URL sync and
  getCurrentTime (no re-render per video frame)

page.tsx takes the redesign's version on purpose: main's nests a second
<main id="main-content"> inside layout.tsx's, and the GitHub link moves
into the header. docs/PERFORMANCE.md and scripts/perf from the old branch
are not brought over (superseded by docs/DATADOG.md and the Playwright
tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The worker wraps the output in a Blob and posts it; building it on the
  page copied the whole file synchronously (1.2 s for a 300 MB export).
- The worker reads the user's File itself; the page no longer holds a copy
  of the input (a 300 MB read caused multi-second GC pauses).
- Export dialog: an estimate of the time left once there is enough signal.
- Preset buttons have accessible names again ("1 is to 1 output ratio,
  Square"); the redesign left them unnamed.
- e2e: a sequence of edits (trim, 9:16, rotate, 2x, mute) checked in the
  output, including a pixel check that the rotation was applied, and a
  smoothness suite: long tasks during export, cancel at both stages with no
  leaked object URLs, and trim/scrub responsiveness on the 300 MB clip.
- Before/after screenshots in docs/assets/ui-redesign/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ported from bf2ec0d onto the redesigned editor (cherry-picked; the
original commit is unchanged):

- Built-in profiles (Landscape 1080p/720p, Square 1080, Portrait 4:5 and
  9:16) plus profiles you save, rename and delete; applying one is a single
  undo step that keeps trims and overlays. Stored in localStorage.
- Safe-area guides (90% / 80%) in the preview; preview-only, never exported.
- The Fill preview crops like the encoder (src/lib/framing.ts), and
  resolveOutputSize is shared by the worker and the UI.
- Anonymous feature usage: feature_used for profile apply/save/rename/
  delete (built-in slug or "custom", never a name) and safe-area show/hide.
- Accessibility: framing buttons get explicit names (the two-line content
  left them unnamed).
- Playwright: four profile × framing × format exports checked by size,
  replacing the old puppeteer script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added level:advanced Advanced level - 55 pts type:design UI/UX design type:docs Documentation type:feature New feature type:testing Testing labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ PR Format Issues — @magic-peach

Please fix the following before your PR can be reviewed:

  • ⚠️ No linked issue found. Add Closes #<issue-number> to your PR description.

Push new commits after fixing — this comment will update automatically.

📖 CONTRIBUTING.md

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

👋 Thanks for your PR, @magic-peach!

Welcome to Reframe — a browser-based video editor built for everyone 🎬

What happens next

  1. 🤖 Automated checks — build & TypeScript typecheck will run automatically
  2. ✅ Vercel preview — a preview deployment will be created (requires maintainer authorization for fork PRs)
  3. 👀 Code review — a maintainer will review your changes
  4. 🚀 Merge — once approved, your PR will be merged!

Quick checklist

  • PR title follows Conventional Commits (e.g. feat: add dark mode)
  • Linked the issue this PR closes (e.g. Closes #123)
  • Tested the changes locally (bun run dev)
  • Build passes (bun run build)

Useful links

Happy coding! 🎉

No content change: main's tree equals d0429cf, which this branch builds on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@magic-peach
magic-peach changed the base branch from feat/editorial-ui to main November 3, 2026 16:11
@magic-peach magic-peach closed this Nov 3, 2026
@magic-peach magic-peach reopened this Nov 3, 2026
@magic-peach
magic-peach marked this pull request as ready for review November 3, 2026 16:19
@magic-peach
magic-peach merged commit 4c1bc79 into main Nov 3, 2026
23 of 29 checks passed
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

level:advanced Advanced level - 55 pts type:design UI/UX design type:docs Documentation type:feature New feature type:testing Testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant