Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .eslintrc.json

This file was deleted.

68 changes: 43 additions & 25 deletions bun.lock

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { defineConfig } from "eslint/config";
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import jsxA11Y from "eslint-plugin-jsx-a11y";

// jsx-a11y ships a native flat-config export, so no FlatCompat/.eslintrc
// bridge is needed here. That bridge (compat.extends("plugin:jsx-a11y/recommended"))
// pulls in @eslint/eslintrc's legacy config loader, which is incompatible
// with ESLint 10's minimatch resolution and crashes on startup.
//
// nextCoreWebVitals already registers the jsx-a11y plugin itself (with a
// 6-rule subset), so extending flatConfigs.recommended wholesale re-registers
// the same plugin and errors ("Cannot redefine plugin"). Merging just its
// `rules` gets the full 34-rule recommended set without the collision.
export default defineConfig([
// `next lint` used to exclude build output automatically; plain `eslint`
// doesn't, so this has to be explicit or it lints into bundled/minified
// output (storybook-static, out, .next) as if it were source.
{
ignores: [".next/**", "out/**", "storybook-static/**", "coverage/**"],
},
{
extends: [...nextCoreWebVitals],
rules: {
...jsxA11Y.flatConfigs.recommended.rules,
},
},
]);
7 changes: 5 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ const nextConfig: NextConfig = {
experimental: {
scrollRestoration: true,
},
// Required for ffmpeg.wasm to load WASM files correctly
// Without this, Next.js might try to process .wasm files and break them
// Required for ffmpeg.wasm to load WASM files correctly.
// Without this, Next.js might try to process .wasm files and break them.
// Next 16 defaults to Turbopack, which ignores this block entirely (and
// errors loudly if it's present without an equivalent `turbopack` config)
// — that's why dev/build scripts in package.json pass `--webpack` explicitly.
webpack: (config) => {
config.resolve.fallback = { fs: false };
return config;
Expand Down
24 changes: 14 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"dev": "next dev --webpack",
"build": "next build --webpack",
"start": "next start",
"lint": "next lint",
"lint": "eslint .",
"test": "vitest",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
Expand All @@ -23,9 +23,9 @@
"jszip": "^3.10.1",
"lottie-web": "^5.12.2",
"lucide-react": "^0.469.0",
"next": "^15.1.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "16.3.4",
"react": "19.2.8",
"react-dom": "19.2.8",
"reframe": ".",
"tailwind-merge": "^3.6.0",
"wasm-feature-detect": "^1.8.0"
Expand All @@ -40,11 +40,11 @@
"@testing-library/user-event": "^14.4.3",
"@types/bun": "^1.3.14",
"@types/node": "^25",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.5",
"chromatic": "^18.5.0",
"eslint": "^9",
"eslint-config-next": "^15.1.8",
"eslint": "9.39.5",
"eslint-config-next": "16.3.4",
"eslint-plugin-jsx-a11y": "^6.10.2",
"jsdom": "^29.1.1",
"postcss": "^8",
Expand All @@ -53,5 +53,9 @@
"tailwindcss": "^3.4.17",
"typescript": "^5",
"vitest": "^4.1.6"
},
"overrides": {
"@types/react": "19.2.18",
"@types/react-dom": "19.2.5"
}
}
12 changes: 8 additions & 4 deletions src/components/AudioSpeedControl.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState, useEffect } from "react";
import { useState } from "react";
import { EditRecipe } from "@/lib/types";
import { SPEED_STEPS } from "@/lib/constants";
import { Volume2, VolumeX, Gauge, AlertTriangle } from "lucide-react";
Expand All @@ -16,10 +16,14 @@ export default function AudioSpeedControl({ recipe, onChange }: Props) {
const parentSpeedIndex = SPEED_STEPS.indexOf(recipe.speed as (typeof SPEED_STEPS)[number]);
const safeParentIndex = parentSpeedIndex === -1 ? 3 : parentSpeedIndex;
const [localSpeedIndex, setLocalSpeedIndex] = useState(safeParentIndex);

useEffect(() => {
// Mirror the parent's speed into local state without an effect: adjust
// state during render when the incoming prop changes (see
// https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes).
const [prevSafeParentIndex, setPrevSafeParentIndex] = useState(safeParentIndex);
if (safeParentIndex !== prevSafeParentIndex) {
setPrevSafeParentIndex(safeParentIndex);
setLocalSpeedIndex(safeParentIndex);
}, [safeParentIndex]);
}
// ----------------------------------------

const getSpeedDescription = (speed: number) => {
Expand Down
5 changes: 1 addition & 4 deletions src/components/ComparisonPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
"use client";

import { useEffect, useRef, useState, useCallback, CSSProperties, RefObject } from "react";
Expand Down Expand Up @@ -209,7 +208,6 @@ export default function ComparisonPreview({ file, recipe, videoRef }: Props) {
>
{/* Left side: Original, unmodified video — clipped to left of slider */}
<div className="absolute inset-0 overflow-hidden" style={{ width: `${sliderPosition}%` }}>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video
ref={leftVideoRef}
className="absolute inset-0 w-full h-full object-contain"
Expand All @@ -232,8 +230,7 @@ export default function ComparisonPreview({ file, recipe, videoRef }: Props) {
className="relative bg-black border border-white/10 overflow-hidden"
style={frameStyle}
>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video ref={rightVideoRef} style={videoStyle} playsInline muted autoPlay loop>
<video ref={rightVideoRef} style={videoStyle} playsInline muted autoPlay loop>
<track kind="captions" />
</video>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/components/ExportOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export default function ExportOverlay({ status, progress, exportStartedAt, onCan

useEffect(() => {
if (status !== "exporting" || !exportStartedAt) {
// Part of the same timer-subscription lifecycle below, not a
// derivable value — elapsedMs tracks wall-clock time via setInterval.
// eslint-disable-next-line react-hooks/set-state-in-effect
setElapsedMs(0);
return;
}
Expand Down
Loading
Loading