diff options
159 files changed, 5596 insertions, 5221 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f251603f..5edc56fb 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -168,7 +168,7 @@ Working on `apps/desktop`? See [`apps/desktop/README.md`](../apps/desktop/README 2. Make your changes 3. Run the relevant checks for the area you touched: - - Web changes: from `apps/web`, run `bun run lint` and `bunx biome format --write .` + - Web changes: from `apps/web`, run `bun run lint` and `bun run format` - Desktop changes: run `./apps/desktop/script/setup` if your environment isn't set up yet 4. Commit your changes with a descriptive message @@ -176,8 +176,8 @@ Working on `apps/desktop`? See [`apps/desktop/README.md`](../apps/desktop/README ## Code Style -- We use Biome for code formatting and linting -- Run `bunx biome format --write .` from the `apps/web` directory to format code +- We use ESLint for linting and Prettier for formatting +- Run `bun run format` from the `apps/web` directory to format code - Run `bun run lint` from the `apps/web` directory to check for linting issues - Follow the existing code patterns diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c30aecc2..77d2c4f0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,344 +1,342 @@ ----
-applyTo: "**/*.{ts,tsx,js,jsx}"
----
-
-# Project Context
-
-Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
-
-## Key Principles
-
-- Zero configuration required
-- Subsecond performance
-- Maximum type safety
-- AI-friendly code generation
-
-## Before Writing Code
-
-1. Analyze existing patterns in the codebase
-2. Consider edge cases and error scenarios
-3. Follow the rules below strictly
-4. Validate accessibility requirements
-
-## Rules
-
-### Accessibility (a11y)
-
-- Don't use `accessKey` attribute on any HTML element.
-- Don't set `aria-hidden="true"` on focusable elements.
-- Don't add ARIA roles, states, and properties to elements that don't support them.
-- Don't use distracting elements like `<marquee>` or `<blink>`.
-- Only use the `scope` prop on `<th>` elements.
-- Don't assign non-interactive ARIA roles to interactive HTML elements.
-- Make sure label elements have text content and are associated with an input.
-- Don't assign interactive ARIA roles to non-interactive HTML elements.
-- Don't assign `tabIndex` to non-interactive HTML elements.
-- Don't use positive integers for `tabIndex` property.
-- Don't include "image", "picture", or "photo" in img alt prop.
-- Don't use explicit role property that's the same as the implicit/default role.
-- Make static elements with click handlers use a valid role attribute.
-- Always include a `title` element for SVG elements.
-- Give all elements requiring alt text meaningful information for screen readers.
-- Make sure anchors have content that's accessible to screen readers.
-- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`.
-- Include all required ARIA attributes for elements with ARIA roles.
-- Make sure ARIA properties are valid for the element's supported roles.
-- Always include a `type` attribute for button elements.
-- Make elements with interactive roles and handlers focusable.
-- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`).
-- Always include a `lang` attribute on the html element.
-- Always include a `title` attribute for iframe elements.
-- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
-- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
-- Include caption tracks for audio and video elements.
-- Use semantic elements instead of role attributes in JSX.
-- Make sure all anchors are valid and navigable.
-- Ensure all ARIA properties (`aria-*`) are valid.
-- Use valid, non-abstract ARIA roles for elements with ARIA roles.
-- Use valid ARIA state and property values.
-- Use valid values for the `autocomplete` attribute on input elements.
-- Use correct ISO language/country codes for the `lang` attribute.
-
-### Code Complexity and Quality
-
-- Don't use consecutive spaces in regular expression literals.
-- Don't use the `arguments` object.
-- Don't use primitive type aliases or misleading types.
-- Don't use the comma operator.
-- Don't use empty type parameters in type aliases and interfaces.
-- Don't write functions that exceed a given Cognitive Complexity score.
-- Don't nest describe() blocks too deeply in test files.
-- Don't use unnecessary boolean casts.
-- Don't use unnecessary callbacks with flatMap.
-- Use for...of statements instead of Array.forEach.
-- Don't create classes that only have static members (like a static namespace).
-- Don't use this and super in static contexts.
-- Don't use unnecessary catch clauses.
-- Don't use unnecessary constructors.
-- Don't use unnecessary continue statements.
-- Don't export empty modules that don't change anything.
-- Don't use unnecessary escape sequences in regular expression literals.
-- Don't use unnecessary fragments.
-- Don't use unnecessary labels.
-- Don't use unnecessary nested block statements.
-- Don't rename imports, exports, and destructured assignments to the same name.
-- Don't use unnecessary string or template literal concatenation.
-- Don't use String.raw in template literals when there are no escape sequences.
-- Don't use useless case statements in switch statements.
-- Don't use ternary operators when simpler alternatives exist.
-- Don't use useless `this` aliasing.
-- Don't use any or unknown as type constraints.
-- Don't initialize variables to undefined.
-- Don't use the void operators (they're not familiar).
-- Use arrow functions instead of function expressions.
-- Use Date.now() to get milliseconds since the Unix Epoch.
-- Use .flatMap() instead of map().flat() when possible.
-- Use literal property access instead of computed property access.
-- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
-- Use concise optional chaining instead of chained logical expressions.
-- Use regular expression literals instead of the RegExp constructor when possible.
-- Don't use number literal object member names that aren't base 10 or use underscore separators.
-- Remove redundant terms from logical expressions.
-- Use while loops instead of for loops when you don't need initializer and update expressions.
-- Don't pass children as props.
-- Don't reassign const variables.
-- Don't use constant expressions in conditions.
-- Don't use `Math.min` and `Math.max` to clamp values when the result is constant.
-- Don't return a value from a constructor.
-- Don't use empty character classes in regular expression literals.
-- Don't use empty destructuring patterns.
-- Don't call global object properties as functions.
-- Don't declare functions and vars that are accessible outside their block.
-- Make sure builtins are correctly instantiated.
-- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.
-- Don't use variables and function parameters before they're declared.
-- Don't use 8 and 9 escape sequences in string literals.
-- Don't use literal numbers that lose precision.
-
-### React and JSX Best Practices
-
-- Don't use the return value of React.render.
-- Make sure all dependencies are correctly specified in React hooks.
-- Make sure all React hooks are called from the top level of component functions.
-- Don't forget key props in iterators and collection literals.
-- Don't destructure props inside JSX components in Solid projects.
-- Don't define React components inside other components.
-- Don't use event handlers on non-interactive elements.
-- Don't assign to React component props.
-- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
-- Don't use dangerous JSX props.
-- Don't use Array index in keys.
-- Don't insert comments as text nodes.
-- Don't assign JSX properties multiple times.
-- Don't add extra closing tags for components without children.
-- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
-- Watch out for possible "wrong" semicolons inside JSX elements.
-
-### Correctness and Safety
-
-- Don't assign a value to itself.
-- Don't return a value from a setter.
-- Don't compare expressions that modify string case with non-compliant values.
-- Don't use lexical declarations in switch clauses.
-- Don't use variables that haven't been declared in the document.
-- Don't write unreachable code.
-- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass.
-- Don't use control flow statements in finally blocks.
-- Don't use optional chaining where undefined values aren't allowed.
-- Don't have unused function parameters.
-- Don't have unused imports.
-- Don't have unused labels.
-- Don't have unused private class members.
-- Don't have unused variables.
-- Make sure void (self-closing) elements don't have children.
-- Don't return a value from a function with the return type 'void'
-- Use isNaN() when checking for NaN.
-- Make sure "for" loop update clauses move the counter in the right direction.
-- Make sure typeof expressions are compared to valid values.
-- Make sure generator functions contain yield.
-- Don't use await inside loops.
-- Don't use bitwise operators.
-- Don't use expressions where the operation doesn't change the value.
-- Make sure Promise-like statements are handled appropriately.
-- Don't use **dirname and **filename in the global scope.
-- Prevent import cycles.
-- Don't use configured elements.
-- Don't hardcode sensitive data like API keys and tokens.
-- Don't let variable declarations shadow variables from outer scopes.
-- Don't use the TypeScript directive @ts-ignore.
-- Prevent duplicate polyfills from Polyfill.io.
-- Don't use useless backreferences in regular expressions that always match empty strings.
-- Don't use unnecessary escapes in string literals.
-- Don't use useless undefined.
-- Make sure getters and setters for the same property are next to each other in class and object definitions.
-- Make sure object literals are declared consistently (defaults to explicit definitions).
-- Use static Response methods instead of new Response() constructor when possible.
-- Make sure switch-case statements are exhaustive.
-- Make sure the `preconnect` attribute is used when using Google Fonts.
-- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item.
-- Make sure iterable callbacks return consistent values.
-- Use `with { type: "json" }` for JSON module imports.
-- Use numeric separators in numeric literals.
-- Use object spread instead of `Object.assign()` when constructing new objects.
-- Always use the radix argument when using `parseInt()`.
-- Make sure JSDoc comment lines start with a single asterisk, except for the first one.
-- Include a description parameter for `Symbol()`.
-- Don't use spread (`...`) syntax on accumulators.
-- Don't use the `delete` operator.
-- Don't access namespace imports dynamically.
-- Don't use namespace imports.
-- Declare regex literals at the top level.
-- Don't use `target="_blank"` without `rel="noopener"`.
-
-### TypeScript Best Practices
-
-- Don't use TypeScript enums.
-- Don't export imported variables.
-- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
-- Don't use TypeScript namespaces.
-- Don't use non-null assertions with the `!` postfix operator.
-- Don't use parameter properties in class constructors.
-- Don't use user-defined types.
-- Use `as const` instead of literal types and type annotations.
-- Use either `T[]` or `Array<T>` consistently.
-- Initialize each enum member value explicitly.
-- Use `export type` for types.
-- Use `import type` for types.
-- Make sure all enum members are literal values.
-- Don't use TypeScript const enum.
-- Don't declare empty interfaces.
-- Don't let variables evolve into any type through reassignments.
-- Don't use the any type.
-- Don't misuse the non-null assertion operator (!) in TypeScript files.
-- Don't use implicit any type on variable declarations.
-- Don't merge interfaces and classes unsafely.
-- Don't use overload signatures that aren't next to each other.
-- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
-
-### Style and Consistency
-
-- Don't use global `eval()`.
-- Don't use callbacks in asynchronous tests and hooks.
-- Don't use negation in `if` statements that have `else` clauses.
-- Don't use nested ternary expressions.
-- Don't reassign function parameters.
-- This rule lets you specify global variable names you don't want to use in your application.
-- Don't use specified modules when loaded by import or require.
-- Don't use constants whose value is the upper-case version of their name.
-- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
-- Don't use template literals if you don't need interpolation or special-character handling.
-- Don't use `else` blocks when the `if` block breaks early.
-- Don't use yoda expressions.
-- Don't use Array constructors.
-- Use `at()` instead of integer index access.
-- Follow curly brace conventions.
-- Use `else if` instead of nested `if` statements in `else` clauses.
-- Use single `if` statements instead of nested `if` clauses.
-- Use `new` for all builtins except `String`, `Number`, and `Boolean`.
-- Use consistent accessibility modifiers on class properties and methods.
-- Use `const` declarations for variables that are only assigned once.
-- Put default function parameters and optional function parameters last.
-- Include a `default` clause in switch statements.
-- Use the `**` operator instead of `Math.pow`.
-- Use `for-of` loops when you need the index to extract an item from the iterated array.
-- Use `node:assert/strict` over `node:assert`.
-- Use the `node:` protocol for Node.js builtin modules.
-- Use Number properties instead of global ones.
-- Use assignment operator shorthand where possible.
-- Use function types instead of object types with call signatures.
-- Use template literals over string concatenation.
-- Use `new` when throwing an error.
-- Don't throw non-Error values.
-- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
-- Use standard constants instead of approximated literals.
-- Don't assign values in expressions.
-- Don't use async functions as Promise executors.
-- Don't reassign exceptions in catch clauses.
-- Don't reassign class members.
-- Don't compare against -0.
-- Don't use labeled statements that aren't loops.
-- Don't use void type outside of generic or return types.
-- Don't use console.
-- Don't use control characters and escape sequences that match control characters in regular expression literals.
-- Don't use debugger.
-- Don't assign directly to document.cookie.
-- Use `===` and `!==`.
-- Don't use duplicate case labels.
-- Don't use duplicate class members.
-- Don't use duplicate conditions in if-else-if chains.
-- Don't use two keys with the same name inside objects.
-- Don't use duplicate function parameter names.
-- Don't have duplicate hooks in describe blocks.
-- Don't use empty block statements and static blocks.
-- Don't let switch clauses fall through.
-- Don't reassign function declarations.
-- Don't allow assignments to native objects and read-only global variables.
-- Use Number.isFinite instead of global isFinite.
-- Use Number.isNaN instead of global isNaN.
-- Don't assign to imported bindings.
-- Don't use irregular whitespace characters.
-- Don't use labels that share a name with a variable.
-- Don't use characters made with multiple code points in character class syntax.
-- Make sure to use new and constructor properly.
-- Don't use shorthand assign when the variable appears on both sides.
-- Don't use octal escape sequences in string literals.
-- Don't use Object.prototype builtins directly.
-- Don't redeclare variables, functions, classes, and types in the same scope.
-- Don't have redundant "use strict".
-- Don't compare things where both sides are exactly the same.
-- Don't let identifiers shadow restricted names.
-- Don't use sparse arrays (arrays with holes).
-- Don't use template literal placeholder syntax in regular strings.
-- Don't use the then property.
-- Don't use unsafe negation.
-- Don't use var.
-- Don't use with statements in non-strict contexts.
-- Make sure async functions actually use await.
-- Make sure default clauses in switch statements come last.
-- Make sure to pass a message value when creating a built-in error.
-- Make sure get methods always return a value.
-- Use a recommended display strategy with Google Fonts.
-- Make sure for-in loops include an if statement.
-- Use Array.isArray() instead of instanceof Array.
-- Make sure to use the digits argument with Number#toFixed().
-- Make sure to use the "use strict" directive in script files.
-
-### Next.js Specific Rules
-
-- Don't use `<img>` elements in Next.js projects.
-- Don't use `<head>` elements in Next.js projects.
-- Don't import next/document outside of pages/\_document.jsx in Next.js projects.
-- Don't use the next/head module in pages/\_document.js on Next.js projects.
-
-### Testing Best Practices
-
-- Don't use export or module.exports in test files.
-- Don't use focused tests.
-- Make sure the assertion function, like expect, is placed inside an it() function call.
-- Don't use disabled tests.
-
-## Common Tasks
-
-- `npx ultracite init` - Initialize Ultracite in your project
-- `npx ultracite format` - Format and fix code automatically
-- `npx ultracite lint` - Check for issues without fixing
-
-## Example: Error Handling
-
-```typescript
-// ✅ Good: Comprehensive error handling
-try {
- const result = await fetchData();
- return { success: true, data: result };
-} catch (error) {
- console.error("API call failed:", error);
- return { success: false, error: error.message };
-}
-
-// ❌ Bad: Swallowing errors
-try {
- return await fetchData();
-} catch (e) {
- console.log(e);
-}
-```
+--- +applyTo: "**/*.{ts,tsx,js,jsx}" +--- + +# Project Context + +Strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects, enforced via ESLint (linting) and Prettier (formatting). + +## Key Principles + +- Maximum type safety +- AI-friendly code generation + +## Before Writing Code + +1. Analyze existing patterns in the codebase +2. Consider edge cases and error scenarios +3. Follow the rules below strictly +4. Validate accessibility requirements + +## Rules + +### Accessibility (a11y) + +- Don't use `accessKey` attribute on any HTML element. +- Don't set `aria-hidden="true"` on focusable elements. +- Don't add ARIA roles, states, and properties to elements that don't support them. +- Don't use distracting elements like `<marquee>` or `<blink>`. +- Only use the `scope` prop on `<th>` elements. +- Don't assign non-interactive ARIA roles to interactive HTML elements. +- Make sure label elements have text content and are associated with an input. +- Don't assign interactive ARIA roles to non-interactive HTML elements. +- Don't assign `tabIndex` to non-interactive HTML elements. +- Don't use positive integers for `tabIndex` property. +- Don't include "image", "picture", or "photo" in img alt prop. +- Don't use explicit role property that's the same as the implicit/default role. +- Make static elements with click handlers use a valid role attribute. +- Always include a `title` element for SVG elements. +- Give all elements requiring alt text meaningful information for screen readers. +- Make sure anchors have content that's accessible to screen readers. +- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`. +- Include all required ARIA attributes for elements with ARIA roles. +- Make sure ARIA properties are valid for the element's supported roles. +- Always include a `type` attribute for button elements. +- Make elements with interactive roles and handlers focusable. +- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`). +- Always include a `lang` attribute on the html element. +- Always include a `title` attribute for iframe elements. +- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`. +- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`. +- Include caption tracks for audio and video elements. +- Use semantic elements instead of role attributes in JSX. +- Make sure all anchors are valid and navigable. +- Ensure all ARIA properties (`aria-*`) are valid. +- Use valid, non-abstract ARIA roles for elements with ARIA roles. +- Use valid ARIA state and property values. +- Use valid values for the `autocomplete` attribute on input elements. +- Use correct ISO language/country codes for the `lang` attribute. + +### Code Complexity and Quality + +- Don't use consecutive spaces in regular expression literals. +- Don't use the `arguments` object. +- Don't use primitive type aliases or misleading types. +- Don't use the comma operator. +- Don't use empty type parameters in type aliases and interfaces. +- Don't write functions that exceed a given Cognitive Complexity score. +- Don't nest describe() blocks too deeply in test files. +- Don't use unnecessary boolean casts. +- Don't use unnecessary callbacks with flatMap. +- Use for...of statements instead of Array.forEach. +- Don't create classes that only have static members (like a static namespace). +- Don't use this and super in static contexts. +- Don't use unnecessary catch clauses. +- Don't use unnecessary constructors. +- Don't use unnecessary continue statements. +- Don't export empty modules that don't change anything. +- Don't use unnecessary escape sequences in regular expression literals. +- Don't use unnecessary fragments. +- Don't use unnecessary labels. +- Don't use unnecessary nested block statements. +- Don't rename imports, exports, and destructured assignments to the same name. +- Don't use unnecessary string or template literal concatenation. +- Don't use String.raw in template literals when there are no escape sequences. +- Don't use useless case statements in switch statements. +- Don't use ternary operators when simpler alternatives exist. +- Don't use useless `this` aliasing. +- Don't use any or unknown as type constraints. +- Don't initialize variables to undefined. +- Don't use the void operators (they're not familiar). +- Use arrow functions instead of function expressions. +- Use Date.now() to get milliseconds since the Unix Epoch. +- Use .flatMap() instead of map().flat() when possible. +- Use literal property access instead of computed property access. +- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work. +- Use concise optional chaining instead of chained logical expressions. +- Use regular expression literals instead of the RegExp constructor when possible. +- Don't use number literal object member names that aren't base 10 or use underscore separators. +- Remove redundant terms from logical expressions. +- Use while loops instead of for loops when you don't need initializer and update expressions. +- Don't pass children as props. +- Don't reassign const variables. +- Don't use constant expressions in conditions. +- Don't use `Math.min` and `Math.max` to clamp values when the result is constant. +- Don't return a value from a constructor. +- Don't use empty character classes in regular expression literals. +- Don't use empty destructuring patterns. +- Don't call global object properties as functions. +- Don't declare functions and vars that are accessible outside their block. +- Make sure builtins are correctly instantiated. +- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors. +- Don't use variables and function parameters before they're declared. +- Don't use 8 and 9 escape sequences in string literals. +- Don't use literal numbers that lose precision. + +### React and JSX Best Practices + +- Don't use the return value of React.render. +- Make sure all dependencies are correctly specified in React hooks. +- Make sure all React hooks are called from the top level of component functions. +- Don't forget key props in iterators and collection literals. +- Don't destructure props inside JSX components in Solid projects. +- Don't define React components inside other components. +- Don't use event handlers on non-interactive elements. +- Don't assign to React component props. +- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element. +- Don't use dangerous JSX props. +- Don't use Array index in keys. +- Don't insert comments as text nodes. +- Don't assign JSX properties multiple times. +- Don't add extra closing tags for components without children. +- Use `<>...</>` instead of `<Fragment>...</Fragment>`. +- Watch out for possible "wrong" semicolons inside JSX elements. + +### Correctness and Safety + +- Don't assign a value to itself. +- Don't return a value from a setter. +- Don't compare expressions that modify string case with non-compliant values. +- Don't use lexical declarations in switch clauses. +- Don't use variables that haven't been declared in the document. +- Don't write unreachable code. +- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass. +- Don't use control flow statements in finally blocks. +- Don't use optional chaining where undefined values aren't allowed. +- Don't have unused function parameters. +- Don't have unused imports. +- Don't have unused labels. +- Don't have unused private class members. +- Don't have unused variables. +- Make sure void (self-closing) elements don't have children. +- Don't return a value from a function with the return type 'void' +- Use isNaN() when checking for NaN. +- Make sure "for" loop update clauses move the counter in the right direction. +- Make sure typeof expressions are compared to valid values. +- Make sure generator functions contain yield. +- Don't use await inside loops. +- Don't use bitwise operators. +- Don't use expressions where the operation doesn't change the value. +- Make sure Promise-like statements are handled appropriately. +- Don't use **dirname and **filename in the global scope. +- Prevent import cycles. +- Don't use configured elements. +- Don't hardcode sensitive data like API keys and tokens. +- Don't let variable declarations shadow variables from outer scopes. +- Don't use the TypeScript directive @ts-ignore. +- Prevent duplicate polyfills from Polyfill.io. +- Don't use useless backreferences in regular expressions that always match empty strings. +- Don't use unnecessary escapes in string literals. +- Don't use useless undefined. +- Make sure getters and setters for the same property are next to each other in class and object definitions. +- Make sure object literals are declared consistently (defaults to explicit definitions). +- Use static Response methods instead of new Response() constructor when possible. +- Make sure switch-case statements are exhaustive. +- Make sure the `preconnect` attribute is used when using Google Fonts. +- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item. +- Make sure iterable callbacks return consistent values. +- Use `with { type: "json" }` for JSON module imports. +- Use numeric separators in numeric literals. +- Use object spread instead of `Object.assign()` when constructing new objects. +- Always use the radix argument when using `parseInt()`. +- Make sure JSDoc comment lines start with a single asterisk, except for the first one. +- Include a description parameter for `Symbol()`. +- Don't use spread (`...`) syntax on accumulators. +- Don't use the `delete` operator. +- Don't access namespace imports dynamically. +- Don't use namespace imports. +- Declare regex literals at the top level. +- Don't use `target="_blank"` without `rel="noopener"`. + +### TypeScript Best Practices + +- Don't use TypeScript enums. +- Don't export imported variables. +- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions. +- Don't use TypeScript namespaces. +- Don't use non-null assertions with the `!` postfix operator. +- Don't use parameter properties in class constructors. +- Don't use user-defined types. +- Use `as const` instead of literal types and type annotations. +- Use either `T[]` or `Array<T>` consistently. +- Initialize each enum member value explicitly. +- Use `export type` for types. +- Use `import type` for types. +- Make sure all enum members are literal values. +- Don't use TypeScript const enum. +- Don't declare empty interfaces. +- Don't let variables evolve into any type through reassignments. +- Don't use the any type. +- Don't misuse the non-null assertion operator (!) in TypeScript files. +- Don't use implicit any type on variable declarations. +- Don't merge interfaces and classes unsafely. +- Don't use overload signatures that aren't next to each other. +- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces. + +### Style and Consistency + +- Don't use global `eval()`. +- Don't use callbacks in asynchronous tests and hooks. +- Don't use negation in `if` statements that have `else` clauses. +- Don't use nested ternary expressions. +- Don't reassign function parameters. +- This rule lets you specify global variable names you don't want to use in your application. +- Don't use specified modules when loaded by import or require. +- Don't use constants whose value is the upper-case version of their name. +- Use `String.slice()` instead of `String.substr()` and `String.substring()`. +- Don't use template literals if you don't need interpolation or special-character handling. +- Don't use `else` blocks when the `if` block breaks early. +- Don't use yoda expressions. +- Don't use Array constructors. +- Use `at()` instead of integer index access. +- Follow curly brace conventions. +- Use `else if` instead of nested `if` statements in `else` clauses. +- Use single `if` statements instead of nested `if` clauses. +- Use `new` for all builtins except `String`, `Number`, and `Boolean`. +- Use consistent accessibility modifiers on class properties and methods. +- Use `const` declarations for variables that are only assigned once. +- Put default function parameters and optional function parameters last. +- Include a `default` clause in switch statements. +- Use the `**` operator instead of `Math.pow`. +- Use `for-of` loops when you need the index to extract an item from the iterated array. +- Use `node:assert/strict` over `node:assert`. +- Use the `node:` protocol for Node.js builtin modules. +- Use Number properties instead of global ones. +- Use assignment operator shorthand where possible. +- Use function types instead of object types with call signatures. +- Use template literals over string concatenation. +- Use `new` when throwing an error. +- Don't throw non-Error values. +- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`. +- Use standard constants instead of approximated literals. +- Don't assign values in expressions. +- Don't use async functions as Promise executors. +- Don't reassign exceptions in catch clauses. +- Don't reassign class members. +- Don't compare against -0. +- Don't use labeled statements that aren't loops. +- Don't use void type outside of generic or return types. +- Don't use console. +- Don't use control characters and escape sequences that match control characters in regular expression literals. +- Don't use debugger. +- Don't assign directly to document.cookie. +- Use `===` and `!==`. +- Don't use duplicate case labels. +- Don't use duplicate class members. +- Don't use duplicate conditions in if-else-if chains. +- Don't use two keys with the same name inside objects. +- Don't use duplicate function parameter names. +- Don't have duplicate hooks in describe blocks. +- Don't use empty block statements and static blocks. +- Don't let switch clauses fall through. +- Don't reassign function declarations. +- Don't allow assignments to native objects and read-only global variables. +- Use Number.isFinite instead of global isFinite. +- Use Number.isNaN instead of global isNaN. +- Don't assign to imported bindings. +- Don't use irregular whitespace characters. +- Don't use labels that share a name with a variable. +- Don't use characters made with multiple code points in character class syntax. +- Make sure to use new and constructor properly. +- Don't use shorthand assign when the variable appears on both sides. +- Don't use octal escape sequences in string literals. +- Don't use Object.prototype builtins directly. +- Don't redeclare variables, functions, classes, and types in the same scope. +- Don't have redundant "use strict". +- Don't compare things where both sides are exactly the same. +- Don't let identifiers shadow restricted names. +- Don't use sparse arrays (arrays with holes). +- Don't use template literal placeholder syntax in regular strings. +- Don't use the then property. +- Don't use unsafe negation. +- Don't use var. +- Don't use with statements in non-strict contexts. +- Make sure async functions actually use await. +- Make sure default clauses in switch statements come last. +- Make sure to pass a message value when creating a built-in error. +- Make sure get methods always return a value. +- Use a recommended display strategy with Google Fonts. +- Make sure for-in loops include an if statement. +- Use Array.isArray() instead of instanceof Array. +- Make sure to use the digits argument with Number#toFixed(). +- Make sure to use the "use strict" directive in script files. + +### Next.js Specific Rules + +- Don't use `<img>` elements in Next.js projects. +- Don't use `<head>` elements in Next.js projects. +- Don't import next/document outside of pages/\_document.jsx in Next.js projects. +- Don't use the next/head module in pages/\_document.js on Next.js projects. + +### Testing Best Practices + +- Don't use export or module.exports in test files. +- Don't use focused tests. +- Make sure the assertion function, like expect, is placed inside an it() function call. +- Don't use disabled tests. + +## Common Tasks + +- `npx ultracite init` - Initialize Ultracite in your project +- `npx ultracite format` - Format and fix code automatically +- `npx ultracite lint` - Check for issues without fixing + +## Example: Error Handling + +```typescript +// ✅ Good: Comprehensive error handling +try { + const result = await fetchData(); + return { success: true, data: result }; +} catch (error) { + console.error("API call failed:", error); + return { success: false, error: error.message }; +} + +// ❌ Bad: Swallowing errors +try { + return await fetchData(); +} catch (e) { + console.log(e); +} +``` diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..13be308b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.next +node_modules +dist +build +target +rust/wasm/pkg diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..c9590876 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "useTabs": true +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 187722fb..2fc093b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,14 +7,7 @@ "editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.codeActionsOnSave": {
- "source.fixAll.biome": "explicit",
- "source.organizeImports.biome": "explicit"
+ "source.fixAll.eslint": "explicit"
},
- "emmet.showExpandedAbbreviation": "never",
- "[typescriptreact]": {
- "editor.defaultFormatter": "biomejs.biome"
- },
- "[typescript]": {
- "editor.defaultFormatter": "biomejs.biome"
- }
+ "emmet.showExpandedAbbreviation": "never"
}
@@ -21,17 +21,3 @@ Each app is a frontend that calls into Rust. Logic is never duplicated between a - Read components before using them. They may already apply classes, which affects what you need to pass and how to override them. -### TypeScript - -Function signatures should make the call site readable and let the function evolve without breaking callers. Positional parameters fail both: `formatTime(30, 24)` hides which number is which, and adding, removing, or reordering an argument silently breaks every caller whose types happen to still line up. A single destructured object fixes both at once - each argument names itself at the call site, and the shape can grow without churn. So signatures default to one object parameter: - -```tsx -// ❌ meaning depends on order; the shape can't evolve without touching every caller -function formatTime(seconds: number, fps: number) { ... } - -// ✅ each argument names itself; fields can be added, reordered, or made optional freely -function formatTime({ seconds, fps }: { seconds: number; fps: number }) { ... } -``` - -The one real exception is type predicates (`element is VideoElement`) — the language requires a positional subject, so the reasoning above doesn't get to apply. - diff --git a/apps/web/package.json b/apps/web/package.json index bbc3ad0a..90cd9fb5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -9,9 +9,9 @@ "start": "next start", "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview", "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy", - "lint": "biome check src/", - "lint:fix": "biome check src/ --write", - "format": "biome format src/ --write", + "lint": "eslint src --ext .ts,.tsx", + "lint:fix": "eslint src --ext .ts,.tsx --fix", + "format": "prettier src --write", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:push:local": "cross-env NODE_ENV=development drizzle-kit push", diff --git a/apps/web/src/actions/components/shortcuts-dialog.tsx b/apps/web/src/actions/components/shortcuts-dialog.tsx index cd9c0242..7f7d8a87 100644 --- a/apps/web/src/actions/components/shortcuts-dialog.tsx +++ b/apps/web/src/actions/components/shortcuts-dialog.tsx @@ -51,10 +51,10 @@ export function ShortcutsDialog({ const keyString = getKeybindingString(e);
if (keyString) {
- const conflict = validateKeybinding(
- keyString,
- recordingShortcut.action,
- );
+ const conflict = validateKeybinding({
+ key: keyString,
+ action: recordingShortcut.action,
+ });
if (conflict) {
toast.error(
`Key "${keyString}" is already bound to "${conflict.existingAction}"`,
@@ -68,7 +68,10 @@ export function ShortcutsDialog({ removeKeybinding(key);
}
- updateKeybinding(keyString, recordingShortcut.action);
+ updateKeybinding({
+ key: keyString,
+ action: recordingShortcut.action,
+ });
setIsRecording(false);
setRecordingShortcut(null);
diff --git a/apps/web/src/actions/definitions.ts b/apps/web/src/actions/definitions.ts index 6f0ba700..986510ec 100644 --- a/apps/web/src/actions/definitions.ts +++ b/apps/web/src/actions/definitions.ts @@ -1,7 +1,4 @@ -import type { - KeybindingConfig, - ShortcutKey, -} from "@/actions/keybinding"; +import type { ShortcutKey } from "@/actions/keybinding"; import type { TActionWithOptionalArgs } from "./types"; export type TActionCategory = @@ -155,33 +152,36 @@ export const ACTIONS = { export type TAction = keyof typeof ACTIONS; -const ACTION_DEFAULT_SHORTCUTS = { - "toggle-play": ["space", "k"], - "seek-forward": ["l"], - "seek-backward": ["j"], - "frame-step-forward": ["right"], - "frame-step-backward": ["left"], - "jump-forward": ["shift+right"], - "jump-backward": ["shift+left"], - "goto-start": ["home", "enter"], - "goto-end": ["end"], - split: ["s"], - "split-left": ["q"], - "split-right": ["w"], - "delete-selected": ["backspace", "delete"], - "copy-selected": ["ctrl+c"], - "paste-copied": ["ctrl+v"], - "toggle-snapping": ["n"], - "select-all": ["ctrl+a"], - "cancel-interaction": ["escape"], - "duplicate-selected": ["ctrl+d"], - undo: ["ctrl+z"], - redo: ["ctrl+shift+z", "ctrl+y"], -} as const satisfies Partial<Record<TActionWithOptionalArgs, readonly ShortcutKey[]>>; +const ACTION_DEFAULT_SHORTCUTS = [ + ["toggle-play", ["space", "k"]], + ["seek-forward", ["l"]], + ["seek-backward", ["j"]], + ["frame-step-forward", ["right"]], + ["frame-step-backward", ["left"]], + ["jump-forward", ["shift+right"]], + ["jump-backward", ["shift+left"]], + ["goto-start", ["home", "enter"]], + ["goto-end", ["end"]], + ["split", ["s"]], + ["split-left", ["q"]], + ["split-right", ["w"]], + ["delete-selected", ["backspace", "delete"]], + ["copy-selected", ["ctrl+c"]], + ["paste-copied", ["ctrl+v"]], + ["toggle-snapping", ["n"]], + ["select-all", ["ctrl+a"]], + ["cancel-interaction", ["escape"]], + ["duplicate-selected", ["ctrl+d"]], + ["undo", ["ctrl+z"]], + ["redo", ["ctrl+shift+z", "ctrl+y"]], +] as const satisfies ReadonlyArray< + readonly [TActionWithOptionalArgs, readonly ShortcutKey[]] +>; -const ACTION_DEFAULT_SHORTCUTS_BY_ACTION: Partial< - Record<TAction, readonly ShortcutKey[]> -> = ACTION_DEFAULT_SHORTCUTS; +const ACTION_DEFAULT_SHORTCUTS_BY_ACTION = new Map< + TAction, + readonly ShortcutKey[] +>(ACTION_DEFAULT_SHORTCUTS); export function getActionDefinition({ action, @@ -190,18 +190,19 @@ export function getActionDefinition({ }): TActionDefinition { return { ...ACTIONS[action], - defaultShortcuts: ACTION_DEFAULT_SHORTCUTS_BY_ACTION[action], + defaultShortcuts: ACTION_DEFAULT_SHORTCUTS_BY_ACTION.get(action), }; } -export function getDefaultShortcuts(): KeybindingConfig { - const shortcuts: KeybindingConfig = {}; +export function getDefaultShortcuts(): Map< + ShortcutKey, + TActionWithOptionalArgs +> { + const shortcuts = new Map<ShortcutKey, TActionWithOptionalArgs>(); - for (const [action, defaultShortcuts] of Object.entries( - ACTION_DEFAULT_SHORTCUTS, - ) as Array<[TActionWithOptionalArgs, readonly ShortcutKey[]]>) { + for (const [action, defaultShortcuts] of ACTION_DEFAULT_SHORTCUTS) { for (const shortcut of defaultShortcuts) { - shortcuts[shortcut] = action; + shortcuts.set(shortcut, action); } } diff --git a/apps/web/src/actions/keybinding.ts b/apps/web/src/actions/keybinding.ts index 65ef4538..aeec2fd6 100644 --- a/apps/web/src/actions/keybinding.ts +++ b/apps/web/src/actions/keybinding.ts @@ -1,79 +1,43 @@ -import type { TActionWithOptionalArgs } from "./types";
-
-/**
- * Alt is also regarded as macOS OPTION (⌥) key
- * Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
- */
-export type ModifierKeys =
- | "ctrl"
- | "alt"
- | "shift"
- | "ctrl+shift"
- | "alt+shift"
- | "ctrl+alt"
- | "ctrl+alt+shift";
-
-export type Key =
- | "a"
- | "b"
- | "c"
- | "d"
- | "e"
- | "f"
- | "g"
- | "h"
- | "i"
- | "j"
- | "k"
- | "l"
- | "m"
- | "n"
- | "o"
- | "p"
- | "q"
- | "r"
- | "s"
- | "t"
- | "u"
- | "v"
- | "w"
- | "x"
- | "y"
- | "z"
- | "0"
- | "1"
- | "2"
- | "3"
- | "4"
- | "5"
- | "6"
- | "7"
- | "8"
- | "9"
- | "up"
- | "down"
- | "left"
- | "right"
- | "/"
- | "?"
- | "."
- | "enter"
- | "tab"
- | "space"
- | "escape"
- | "esc"
- | "backspace"
- | "delete"
- | "home"
- | "end";
-/* eslint-enable */
-
-export type ModifierBasedShortcutKey = `${ModifierKeys}+${Key}`;
-// Singular keybindings (these will be disabled when an input-ish area has been focused)
-export type SingleCharacterShortcutKey = `${Key}`;
-
-export type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey;
-
-export type KeybindingConfig = {
- [key in ShortcutKey]?: TActionWithOptionalArgs;
-};
+import type { TActionWithOptionalArgs } from "./types"; + +/** + * Alt is also regarded as macOS OPTION (⌥) key + * Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!) + */ +export type ModifierKeys = + | "ctrl" + | "alt" + | "shift" + | "ctrl+shift" + | "alt+shift" + | "ctrl+alt" + | "ctrl+alt+shift"; + +const KEYS = [ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", + "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", + "u", "v", "w", "x", "y", "z", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "up", "down", "left", "right", + "/", "?", ".", + "enter", "tab", "space", "escape", "esc", + "backspace", "delete", "home", "end", +] as const; + +export type Key = (typeof KEYS)[number]; + +const KEY_SET: ReadonlySet<string> = new Set(KEYS); + +export function isKey(value: string): value is Key { + return KEY_SET.has(value); +} + +export type ModifierBasedShortcutKey = `${ModifierKeys}+${Key}`; +// Singular keybindings (these will be disabled when an input-ish area has been focused) +export type SingleCharacterShortcutKey = `${Key}`; + +export type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey; + +export type KeybindingConfig = { + [key in ShortcutKey]?: TActionWithOptionalArgs; +}; diff --git a/apps/web/src/actions/keybindings-store.ts b/apps/web/src/actions/keybindings-store.ts index c0a1a5e2..8f344487 100644 --- a/apps/web/src/actions/keybindings-store.ts +++ b/apps/web/src/actions/keybindings-store.ts @@ -6,11 +6,15 @@ import type { TActionWithOptionalArgs } from "@/actions"; import { getDefaultShortcuts } from "@/actions"; import { isTypableDOMElement } from "@/utils/browser"; import { isAppleDevice } from "@/utils/platform"; -import type { KeybindingConfig, ShortcutKey } from "@/actions/keybinding"; +import type { + Key, + KeybindingConfig, + ModifierKeys, + ShortcutKey, +} from "@/actions/keybinding"; +import { isKey } from "@/actions/keybinding"; import { runMigrations, CURRENT_VERSION } from "./keybindings/migrations"; -const defaultKeybindings: KeybindingConfig = getDefaultShortcuts(); - export interface KeybindingConflict { key: ShortcutKey; existingAction: TActionWithOptionalArgs; @@ -18,38 +22,57 @@ export interface KeybindingConflict { } interface KeybindingsState { - keybindings: KeybindingConfig; + keybindings: Map<ShortcutKey, TActionWithOptionalArgs>; isCustomized: boolean; overlayDepth: number; openOverlayIds: string[]; isLoadingProject: boolean; isRecording: boolean; - updateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => void; + updateKeybinding: (params: { + key: ShortcutKey; + action: TActionWithOptionalArgs; + }) => void; removeKeybinding: (key: ShortcutKey) => void; resetToDefaults: () => void; importKeybindings: (config: KeybindingConfig) => void; - exportKeybindings: () => KeybindingConfig; + exportKeybindings: () => Record<string, TActionWithOptionalArgs>; openOverlay: (overlayId: string) => void; closeOverlay: (overlayId: string) => void; setLoadingProject: (loading: boolean) => void; setIsRecording: (isRecording: boolean) => void; - validateKeybinding: ( - key: ShortcutKey, - action: TActionWithOptionalArgs, - ) => KeybindingConflict | null; + validateKeybinding: (params: { + key: ShortcutKey; + action: TActionWithOptionalArgs; + }) => KeybindingConflict | null; getKeybindingsForAction: (action: TActionWithOptionalArgs) => ShortcutKey[]; getKeybindingString: (ev: KeyboardEvent) => ShortcutKey | null; } +type PersistedState = { + keybindings: Record<string, TActionWithOptionalArgs>; + isCustomized: boolean; +}; + function isDOMElement(element: EventTarget | null): element is HTMLElement { return element instanceof HTMLElement; } +function isPersistedState(value: unknown): value is PersistedState { + if (!value || typeof value !== "object") return false; + if (!("keybindings" in value) || !("isCustomized" in value)) return false; + const { keybindings, isCustomized } = value; + return ( + typeof keybindings === "object" && + keybindings !== null && + typeof isCustomized === "boolean" + ); +} + export const useKeybindingsStore = create<KeybindingsState>()( persist( (set, get) => ({ - keybindings: { ...defaultKeybindings }, + keybindings: getDefaultShortcuts(), isCustomized: false, overlayDepth: 0, openOverlayIds: [], @@ -61,10 +84,9 @@ export const useKeybindingsStore = create<KeybindingsState>()( const openOverlayIds = s.openOverlayIds.includes(overlayId) ? s.openOverlayIds : [...s.openOverlayIds, overlayId]; - const nextOverlayDepth = openOverlayIds.length; return { openOverlayIds, - overlayDepth: nextOverlayDepth, + overlayDepth: openOverlayIds.length, }; }), closeOverlay: (overlayId) => @@ -72,35 +94,32 @@ export const useKeybindingsStore = create<KeybindingsState>()( const openOverlayIds = s.openOverlayIds.filter( (id) => id !== overlayId, ); - const nextOverlayDepth = openOverlayIds.length; return { openOverlayIds, - overlayDepth: nextOverlayDepth, + overlayDepth: openOverlayIds.length, }; }), setLoadingProject: (loading) => { set({ isLoadingProject: loading }); }, - updateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => { + updateKeybinding: ({ key, action }) => { set((state) => { - const newKeybindings = { ...state.keybindings }; - newKeybindings[key] = action; - + const next = new Map(state.keybindings); + next.set(key, action); return { - keybindings: newKeybindings, + keybindings: next, isCustomized: true, }; }); }, - removeKeybinding: (key: ShortcutKey) => { + removeKeybinding: (key) => { set((state) => { - const newKeybindings = { ...state.keybindings }; - delete newKeybindings[key]; - + const next = new Map(state.keybindings); + next.delete(key); return { - keybindings: newKeybindings, + keybindings: next, isCustomized: true, }; }); @@ -108,34 +127,35 @@ export const useKeybindingsStore = create<KeybindingsState>()( resetToDefaults: () => { set({ - keybindings: { ...defaultKeybindings }, + keybindings: getDefaultShortcuts(), isCustomized: false, }); }, - importKeybindings: (config: KeybindingConfig) => { - for (const [key] of Object.entries(config)) { + importKeybindings: (config) => { + const next = new Map<ShortcutKey, TActionWithOptionalArgs>(); + for (const [key, action] of Object.entries(config)) { if (typeof key !== "string" || key.length === 0) { throw new Error(`Invalid key format: ${key}`); } + if (action !== undefined) { + // Public type's keys are `ShortcutKey`; trust the caller's typing. + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + next.set(key as ShortcutKey, action); + } } set({ - keybindings: { ...config }, + keybindings: next, isCustomized: true, }); }, exportKeybindings: () => { - return get().keybindings; + return Object.fromEntries(get().keybindings); }, - validateKeybinding: ( - key: ShortcutKey, - action: TActionWithOptionalArgs, - ) => { - const { keybindings } = get(); - const existingAction = keybindings[key]; - + validateKeybinding: ({ key, action }) => { + const existingAction = get().keybindings.get(key); if (existingAction && existingAction !== action) { return { key, @@ -143,33 +163,45 @@ export const useKeybindingsStore = create<KeybindingsState>()( newAction: action, }; } - return null; }, - setIsRecording: (isRecording: boolean) => { + setIsRecording: (isRecording) => { set({ isRecording }); }, - getKeybindingsForAction: (action: TActionWithOptionalArgs) => { - const { keybindings } = get(); - return Object.keys(keybindings).filter( - (key) => keybindings[key as ShortcutKey] === action, - ) as ShortcutKey[]; + getKeybindingsForAction: (action) => { + const result: ShortcutKey[] = []; + for (const [key, mapped] of get().keybindings) { + if (mapped === action) result.push(key); + } + return result; }, - getKeybindingString: (ev: KeyboardEvent) => { - return generateKeybindingString(ev) as ShortcutKey | null; - }, + getKeybindingString: (ev) => generateKeybindingString(ev), }), { name: "opencut-keybindings", version: CURRENT_VERSION, - partialize: (state) => ({ - keybindings: state.keybindings, + partialize: (state): PersistedState => ({ + keybindings: Object.fromEntries(state.keybindings), isCustomized: state.isCustomized, }), migrate: (persisted, version) => runMigrations({ state: persisted, fromVersion: version }), + merge: (persisted, current) => { + if (!isPersistedState(persisted)) return current; + const entries = Object.entries(persisted.keybindings); + // Persistence boundary: keys are normalized by the migration chain. + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const typedEntries = entries as Array< + [ShortcutKey, TActionWithOptionalArgs] + >; + return { + ...current, + keybindings: new Map(typedEntries), + isCustomized: persisted.isCustomized, + }; + }, }, ), ); @@ -184,68 +216,59 @@ function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null { if ( modifierKey === "shift" && isDOMElement(target) && - isTypableDOMElement({ element: target as HTMLElement }) + isTypableDOMElement({ element: target }) ) { return null; } - return `${modifierKey}+${key}` as ShortcutKey; + return `${modifierKey}+${key}`; } - if ( - isDOMElement(target) && - isTypableDOMElement({ element: target as HTMLElement }) - ) + if (isDOMElement(target) && isTypableDOMElement({ element: target })) { return null; + } - return `${key}` as ShortcutKey; + return key; } -function getPressedKey(ev: KeyboardEvent): string | null { - const key = (ev.key ?? "").toLowerCase(); +function getPressedKey(ev: KeyboardEvent): Key | null { + const raw = (ev.key ?? "").toLowerCase(); const code = ev.code ?? ""; - if (code === "Space" || key === " " || key === "spacebar" || key === "space") + if (code === "Space" || raw === " " || raw === "spacebar" || raw === "space") return "space"; - if (key.startsWith("arrow")) return key.slice(5); - - if (key === "escape") return "escape"; - if (key === "tab") return "tab"; - if (key === "home") return "home"; - if (key === "end") return "end"; - if (key === "delete") return "delete"; - if (key === "backspace") return "backspace"; + if (raw === "arrowup") return "up"; + if (raw === "arrowdown") return "down"; + if (raw === "arrowleft") return "left"; + if (raw === "arrowright") return "right"; if (code.startsWith("Key")) { const letter = code.slice(3).toLowerCase(); - if (letter.length === 1 && letter >= "a" && letter <= "z") return letter; + if (isKey(letter)) return letter; } - // Use physical key position for AZERTY and other non-QWERTY layouts + // Use physical key position for AZERTY and other non-QWERTY layouts. if (code.startsWith("Digit")) { const digit = code.slice(5); - if (digit.length === 1 && digit >= "0" && digit <= "9") return digit; + if (isKey(digit)) return digit; } - const isDigit = key.length === 1 && key >= "0" && key <= "9"; - if (isDigit) return key; - - if (key === "/" || key === "." || key === "enter") return key; - + if (isKey(raw)) return raw; return null; } -function getActiveModifier(ev: KeyboardEvent): string | null { - const modifierKeys = { - ctrl: isAppleDevice() ? ev.metaKey : ev.ctrlKey, - alt: ev.altKey, - shift: ev.shiftKey, - }; +function getActiveModifier(ev: KeyboardEvent): ModifierKeys | null { + const ctrl = isAppleDevice() ? ev.metaKey : ev.ctrlKey; + const alt = ev.altKey; + const shift = ev.shiftKey; - const activeModifier = Object.keys(modifierKeys) - .filter((key) => modifierKeys[key as keyof typeof modifierKeys]) - .join("+"); - - return activeModifier === "" ? null : activeModifier; + if (ctrl && alt && shift) return "ctrl+alt+shift"; + if (ctrl && alt) return "ctrl+alt"; + if (ctrl && shift) return "ctrl+shift"; + if (alt && shift) return "alt+shift"; + if (ctrl) return "ctrl"; + if (alt) return "alt"; + if (shift) return "shift"; + return null; } diff --git a/apps/web/src/actions/keybindings/__tests__/persistence.test.ts b/apps/web/src/actions/keybindings/__tests__/persistence.test.ts new file mode 100644 index 00000000..bcccc6a4 --- /dev/null +++ b/apps/web/src/actions/keybindings/__tests__/persistence.test.ts @@ -0,0 +1,144 @@ +import { + afterEach, + beforeEach, + describe, + expect, + mock, + test, +} from "bun:test"; +import { + decodePersistedKeybindingsState, + migratePersistedKeybindingsState, + parseImportedKeybindings, + serializeKeybindingsState, +} from "../persistence"; + +describe("keybinding persistence", () => { + let warnSpy: ReturnType<typeof mock>; + let originalWarn: typeof console.warn; + + beforeEach(() => { + originalWarn = console.warn; + warnSpy = mock(() => {}); + console.warn = warnSpy; + }); + + afterEach(() => { + console.warn = originalWarn; + }); + + test("migrates legacy persisted keybindings before decoding them", () => { + const migrated = migratePersistedKeybindingsState({ + state: { + keybindings: { + s: "split-selected", + "ctrl+v": "paste-selected", + }, + isCustomized: true, + }, + fromVersion: 2, + }); + + const decoded = decodePersistedKeybindingsState({ state: migrated }); + expect(decoded).not.toBeNull(); + if (!decoded) throw new Error("Expected migrated keybindings to decode"); + + expect(decoded.isCustomized).toBe(true); + expect(decoded.keybindings.get("s")).toBe("split"); + expect(decoded.keybindings.get("ctrl+v")).toBe("paste-copied"); + expect(decoded.keybindings.get("escape")).toBe("cancel-interaction"); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + test("filters invalid persisted entries at the boundary and warns", () => { + const decoded = decodePersistedKeybindingsState({ + state: { + keybindings: { + space: "toggle-play", + "shift+bogus": "toggle-play", + "ctrl+v": "not-an-action", + }, + isCustomized: false, + }, + }); + + expect(decoded).not.toBeNull(); + if (!decoded) throw new Error("Expected persisted keybindings to decode"); + + expect(Array.from(decoded.keybindings.entries())).toEqual([ + ["space", "toggle-play"], + ]); + expect(warnSpy).toHaveBeenCalledTimes(1); + }); + + test("returns null and warns when persisted shape is unrecognizable", () => { + const decoded = decodePersistedKeybindingsState({ state: "garbage" }); + expect(decoded).toBeNull(); + expect(warnSpy).toHaveBeenCalledTimes(1); + }); + + test("round-trips actions that have no default shortcut", () => { + // `stop-playback` is a valid `TActionWithOptionalArgs` but is not in the + // defaults table; the validator must still accept it. + const serialized = serializeKeybindingsState({ + keybindings: new Map([ + ["space", "toggle-play"], + ["x", "stop-playback"], + ["b", "toggle-bookmark"], + ]), + isCustomized: true, + }); + + const decoded = decodePersistedKeybindingsState({ state: serialized }); + expect(decoded).not.toBeNull(); + if (!decoded) throw new Error("Expected round-tripped keybindings"); + + expect(decoded.keybindings.get("space")).toBe("toggle-play"); + expect(decoded.keybindings.get("x")).toBe("stop-playback"); + expect(decoded.keybindings.get("b")).toBe("toggle-bookmark"); + expect(warnSpy).not.toHaveBeenCalled(); + }); +}); + +describe("parseImportedKeybindings", () => { + test("accepts a valid configuration", () => { + const result = parseImportedKeybindings({ + config: { + space: "toggle-play", + x: "stop-playback", + }, + }); + + expect(result.get("space")).toBe("toggle-play"); + expect(result.get("x")).toBe("stop-playback"); + }); + + test("throws on non-object input", () => { + expect(() => parseImportedKeybindings({ config: null })).toThrow( + /JSON object/, + ); + expect(() => parseImportedKeybindings({ config: [] })).toThrow( + /JSON object/, + ); + }); + + test("throws on non-string action values", () => { + expect(() => + parseImportedKeybindings({ config: { space: 42 } }), + ).toThrow(/expected string/); + }); + + test("throws on invalid shortcut keys", () => { + expect(() => + parseImportedKeybindings({ + config: { "shift+bogus": "toggle-play" }, + }), + ).toThrow(/shift\+bogus/); + }); + + test("throws on invalid actions", () => { + expect(() => + parseImportedKeybindings({ config: { space: "not-an-action" } }), + ).toThrow(/not-an-action/); + }); +}); diff --git a/apps/web/src/actions/keybindings/migrations/v2-to-v3.ts b/apps/web/src/actions/keybindings/migrations/v2-to-v3.ts index 0171c5a5..81fd86a7 100644 --- a/apps/web/src/actions/keybindings/migrations/v2-to-v3.ts +++ b/apps/web/src/actions/keybindings/migrations/v2-to-v3.ts @@ -1,13 +1,8 @@ -import type { KeybindingConfig, ShortcutKey } from "@/actions/keybinding";
-import type { TActionWithOptionalArgs } from "@/actions";
-
-interface V2State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
+import { getPersistedKeybindingsState } from "../persisted-state";
export function v2ToV3({ state }: { state: unknown }): unknown {
- const v2 = state as V2State;
+ const v2 = getPersistedKeybindingsState({ state });
+ if (!v2) return state;
const renames: Record<string, string> = {
"split-selected": "split",
@@ -17,8 +12,9 @@ export function v2ToV3({ state }: { state: unknown }): unknown { const migrated = { ...v2.keybindings };
for (const [key, action] of Object.entries(migrated)) {
- if (action && renames[action]) {
- migrated[key as ShortcutKey] = renames[action] as TActionWithOptionalArgs;
+ const renamedAction = action ? renames[action] : undefined;
+ if (renamedAction) {
+ migrated[key] = renamedAction;
}
}
diff --git a/apps/web/src/actions/keybindings/migrations/v3-to-v4.ts b/apps/web/src/actions/keybindings/migrations/v3-to-v4.ts index 8541071b..3724f61c 100644 --- a/apps/web/src/actions/keybindings/migrations/v3-to-v4.ts +++ b/apps/web/src/actions/keybindings/migrations/v3-to-v4.ts @@ -1,14 +1,8 @@ -import type { TActionWithOptionalArgs } from "@/actions";
-import type { ShortcutKey } from "@/actions/keybinding";
-import type { KeybindingConfig } from "@/actions/keybinding";
-
-interface V3State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
+import { getPersistedKeybindingsState } from "../persisted-state";
export function v3ToV4({ state }: { state: unknown }): unknown {
- const v3 = state as V3State;
+ const v3 = getPersistedKeybindingsState({ state });
+ if (!v3) return state;
const renames: Record<string, string> = {
"paste-selected": "paste-copied",
@@ -16,8 +10,9 @@ export function v3ToV4({ state }: { state: unknown }): unknown { const migrated = { ...v3.keybindings };
for (const [key, action] of Object.entries(migrated)) {
- if (action && renames[action]) {
- migrated[key as ShortcutKey] = renames[action] as TActionWithOptionalArgs;
+ const renamedAction = action ? renames[action] : undefined;
+ if (renamedAction) {
+ migrated[key] = renamedAction;
}
}
diff --git a/apps/web/src/actions/keybindings/migrations/v4-to-v5.ts b/apps/web/src/actions/keybindings/migrations/v4-to-v5.ts index f6937c39..bb60b044 100644 --- a/apps/web/src/actions/keybindings/migrations/v4-to-v5.ts +++ b/apps/web/src/actions/keybindings/migrations/v4-to-v5.ts @@ -1,12 +1,8 @@ -import type { KeybindingConfig } from "@/actions/keybinding";
-
-interface V4State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
+import { getPersistedKeybindingsState } from "../persisted-state";
export function v4ToV5({ state }: { state: unknown }): unknown {
- const v4 = state as V4State;
+ const v4 = getPersistedKeybindingsState({ state });
+ if (!v4) return state;
const keybindings = { ...v4.keybindings };
if (!keybindings.escape) {
diff --git a/apps/web/src/actions/keybindings/migrations/v5-to-v6.ts b/apps/web/src/actions/keybindings/migrations/v5-to-v6.ts index 09b44307..cb572aac 100644 --- a/apps/web/src/actions/keybindings/migrations/v5-to-v6.ts +++ b/apps/web/src/actions/keybindings/migrations/v5-to-v6.ts @@ -1,12 +1,8 @@ -import type { KeybindingConfig } from "@/actions/keybinding";
-
-interface V5State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
+import { getPersistedKeybindingsState } from "../persisted-state";
export function v5ToV6({ state }: { state: unknown }): unknown {
- const v5 = state as V5State;
+ const v5 = getPersistedKeybindingsState({ state });
+ if (!v5) return state;
const keybindings = { ...v5.keybindings };
if (keybindings.escape === "deselect-all") {
diff --git a/apps/web/src/actions/keybindings/migrations/v6-to-v7.ts b/apps/web/src/actions/keybindings/migrations/v6-to-v7.ts index 46a99ac1..9c05db23 100644 --- a/apps/web/src/actions/keybindings/migrations/v6-to-v7.ts +++ b/apps/web/src/actions/keybindings/migrations/v6-to-v7.ts @@ -1,16 +1,12 @@ -import type { KeybindingConfig } from "@/actions/keybinding"; - -interface V6State { - keybindings: KeybindingConfig; - isCustomized: boolean; -} +import { getPersistedKeybindingsState } from "../persisted-state"; export function v6ToV7({ state }: { state: unknown }): unknown { - const v6 = state as V6State; + const v6 = getPersistedKeybindingsState({ state }); + if (!v6) return state; const keybindings = { ...v6.keybindings }; - for (const key of Object.keys(keybindings) as Array<keyof KeybindingConfig>) { - if (keybindings[key] === ("split-element" as never)) { + for (const [key, action] of Object.entries(keybindings)) { + if (action === "split-element") { keybindings[key] = "split"; } } diff --git a/apps/web/src/actions/keybindings/persisted-state.ts b/apps/web/src/actions/keybindings/persisted-state.ts new file mode 100644 index 00000000..57d83f6d --- /dev/null +++ b/apps/web/src/actions/keybindings/persisted-state.ts @@ -0,0 +1,37 @@ +export type PersistedKeybindingConfig = Record<string, string | undefined>; + +export interface PersistedKeybindingsState { + keybindings: PersistedKeybindingConfig; + isCustomized: boolean; +} + +function isRecord(value: unknown): value is Record<string, unknown> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export function getPersistedKeybindingsState({ + state, +}: { + state: unknown; +}): PersistedKeybindingsState | null { + if (!isRecord(state)) return null; + + const { keybindings, isCustomized } = state; + if (!isRecord(keybindings) || typeof isCustomized !== "boolean") { + return null; + } + + const normalizedKeybindings: PersistedKeybindingConfig = {}; + for (const [key, action] of Object.entries(keybindings)) { + if (action !== undefined && typeof action !== "string") { + return null; + } + + normalizedKeybindings[key] = action; + } + + return { + keybindings: normalizedKeybindings, + isCustomized, + }; +} diff --git a/apps/web/src/actions/keybindings/persistence.ts b/apps/web/src/actions/keybindings/persistence.ts new file mode 100644 index 00000000..ced44bd0 --- /dev/null +++ b/apps/web/src/actions/keybindings/persistence.ts @@ -0,0 +1,119 @@ +import type { ShortcutKey } from "@/actions/keybinding"; +import { isShortcutKey } from "@/actions/keybinding"; +import type { TActionWithOptionalArgs } from "@/actions"; +import { isActionWithOptionalArgs } from "@/actions"; +import { runMigrations } from "./migrations"; +import { + getPersistedKeybindingsState, + type PersistedKeybindingsState, +} from "./persisted-state"; + +export interface DecodedKeybindingsState { + keybindings: Map<ShortcutKey, TActionWithOptionalArgs>; + isCustomized: boolean; +} + +export function serializeKeybindingsState({ + keybindings, + isCustomized, +}: DecodedKeybindingsState): PersistedKeybindingsState { + return { + keybindings: Object.fromEntries(keybindings), + isCustomized, + }; +} + +export function migratePersistedKeybindingsState({ + state, + fromVersion, +}: { + state: unknown; + fromVersion: number; +}): unknown { + return runMigrations({ state, fromVersion }); +} + +/** + * Decode a persisted/migrated keybindings blob into the in-memory shape. + * + * Lossy by design: invalid entries are dropped and a warning is emitted, so the + * user falls back to (mostly) sensible defaults instead of a broken store. + * Returns `null` if the top-level shape is unrecognizable, in which case the + * caller should keep its current state. + */ +export function decodePersistedKeybindingsState({ + state, +}: { + state: unknown; +}): DecodedKeybindingsState | null { + const persisted = getPersistedKeybindingsState({ state }); + if (!persisted) { + console.warn( + "[keybindings] Persisted state has unexpected shape; keeping current keybindings.", + state, + ); + return null; + } + + const keybindings = new Map<ShortcutKey, TActionWithOptionalArgs>(); + const dropped: Array<{ key: string; action: string | undefined }> = []; + for (const [key, action] of Object.entries(persisted.keybindings)) { + if (action === undefined) continue; + if (!isShortcutKey(key) || !isActionWithOptionalArgs(action)) { + dropped.push({ key, action }); + continue; + } + + keybindings.set(key, action); + } + + if (dropped.length > 0) { + console.warn( + "[keybindings] Dropped invalid persisted entries:", + dropped, + ); + } + + return { + keybindings, + isCustomized: persisted.isCustomized, + }; +} + +/** + * Parse a user-supplied keybindings configuration (typically the output of + * `JSON.parse` on an imported file). + * + * Strict by design: throws on the first invalid entry so the caller can surface + * the failure to the user instead of silently producing a half-applied import. + * Accepts `unknown` because the input has already crossed a trust boundary. + */ +export function parseImportedKeybindings({ + config, +}: { + config: unknown; +}): Map<ShortcutKey, TActionWithOptionalArgs> { + if (typeof config !== "object" || config === null || Array.isArray(config)) { + throw new Error("Imported keybindings must be a JSON object"); + } + + const result = new Map<ShortcutKey, TActionWithOptionalArgs>(); + for (const [key, action] of Object.entries(config)) { + if (action === undefined) continue; + if (typeof action !== "string") { + throw new Error( + `Invalid action for "${key}": expected string, got ${typeof action}`, + ); + } + if (!isShortcutKey(key)) { + throw new Error(`Invalid shortcut key: ${JSON.stringify(key)}`); + } + if (!isActionWithOptionalArgs(action)) { + throw new Error( + `Invalid action for "${key}": ${JSON.stringify(action)}`, + ); + } + result.set(key, action); + } + return result; +} diff --git a/apps/web/src/actions/registry.ts b/apps/web/src/actions/registry.ts index e69b3b9f..ece14eaf 100644 --- a/apps/web/src/actions/registry.ts +++ b/apps/web/src/actions/registry.ts @@ -11,6 +11,7 @@ import type { type ActionHandler = (arg: unknown, trigger?: TInvocationTrigger) => void; const boundActions: Partial<Record<TAction, ActionHandler[]>> = {}; +// eslint-disable-next-line opencut/prefer-object-params -- action registries read best as (action, handler). export function bindAction<A extends TAction>( action: A, handler: TActionFunc<A>, @@ -24,6 +25,7 @@ export function bindAction<A extends TAction>( } } +// eslint-disable-next-line opencut/prefer-object-params -- action registries read best as (action, handler). export function unbindAction<A extends TAction>( action: A, handler: TActionFunc<A>, @@ -52,6 +54,7 @@ type InvokeActionFunc = { ): void; }; +// eslint-disable-next-line opencut/prefer-object-params -- dispatchers conventionally separate action, payload, and trigger. export const invokeAction: InvokeActionFunc = <A extends TAction>( action: A, args?: TArgOfAction<A>, diff --git a/apps/web/src/actions/types.ts b/apps/web/src/actions/types.ts index 41258c7b..38627b95 100644 --- a/apps/web/src/actions/types.ts +++ b/apps/web/src/actions/types.ts @@ -1,44 +1,44 @@ -import type { MutableRefObject } from "react";
-import type { TAction } from "./definitions";
-
-export type { TAction };
-
-export type TActionArgsMap = {
- "seek-forward": { seconds: number } | undefined;
- "seek-backward": { seconds: number } | undefined;
- "jump-forward": { seconds: number } | undefined;
- "jump-backward": { seconds: number } | undefined;
- "remove-media-asset": { projectId: string; assetId: string };
- "remove-media-assets": { projectId: string; assetIds: string[] };
-};
-
-type TKeysWithValueUndefined<T> = {
- [K in keyof T]: undefined extends T[K] ? K : never;
-}[keyof T];
-
-export type TActionWithArgs = keyof TActionArgsMap;
-
-export type TActionWithOptionalArgs =
- | TActionWithNoArgs
- | TKeysWithValueUndefined<TActionArgsMap>;
-
-export type TActionWithNoArgs = Exclude<TAction, TActionWithArgs>;
-
-export type TArgOfAction<A extends TAction> = A extends TActionWithArgs
- ? TActionArgsMap[A]
- : undefined;
-
-export type TActionFunc<A extends TAction> = A extends TActionWithArgs
- ? (arg: TArgOfAction<A>, trigger?: TInvocationTrigger) => void
- : (_?: undefined, trigger?: TInvocationTrigger) => void;
-
-export type TInvocationTrigger = "keypress" | "mouseclick";
-
-export type TBoundActionList = {
- [A in TAction]?: Array<TActionFunc<A>>;
-};
-
-export type TActionHandlerOptions =
- | MutableRefObject<boolean>
- | boolean
- | undefined;
+import type { MutableRefObject } from "react"; +import type { TAction } from "./definitions"; + +export type { TAction }; + +export type TActionArgsMap = { + "seek-forward": { seconds: number } | undefined; + "seek-backward": { seconds: number } | undefined; + "jump-forward": { seconds: number } | undefined; + "jump-backward": { seconds: number } | undefined; + "remove-media-asset": { projectId: string; assetId: string }; + "remove-media-assets": { projectId: string; assetIds: string[] }; +}; + +type TKeysWithValueUndefined<T> = { + [K in keyof T]: undefined extends T[K] ? K : never; +}[keyof T]; + +export type TActionWithArgs = keyof TActionArgsMap; + +export type TActionWithOptionalArgs = + | TActionWithNoArgs + | TKeysWithValueUndefined<TActionArgsMap>; + +export type TActionWithNoArgs = Exclude<TAction, TActionWithArgs>; + +export type TArgOfAction<A extends TAction> = A extends TActionWithArgs + ? TActionArgsMap[A] + : undefined; + +export type TActionFunc<A extends TAction> = A extends TActionWithArgs + ? (arg: TArgOfAction<A>, trigger?: TInvocationTrigger) => void + : (_?: undefined, trigger?: TInvocationTrigger) => void; + +export type TInvocationTrigger = "keypress" | "mouseclick"; + +export type TBoundActionList = { + [A in TAction]?: Array<TActionFunc<A>>; +}; + +export type TActionHandlerOptions = + | MutableRefObject<boolean> + | boolean + | undefined; diff --git a/apps/web/src/actions/use-action-handler.ts b/apps/web/src/actions/use-action-handler.ts index a8bb0054..d4a37b50 100644 --- a/apps/web/src/actions/use-action-handler.ts +++ b/apps/web/src/actions/use-action-handler.ts @@ -8,6 +8,7 @@ import type { } from "@/actions"; import { bindAction, unbindAction } from "@/actions"; +// eslint-disable-next-line opencut/prefer-object-params -- action subscriptions read best as (action, handler, isActive). export function useActionHandler<A extends TAction>( action: A, handler: TActionFunc<A>, diff --git a/apps/web/src/actions/use-editor-actions.ts b/apps/web/src/actions/use-editor-actions.ts index 5e90a6a1..73300c6e 100644 --- a/apps/web/src/actions/use-editor-actions.ts +++ b/apps/web/src/actions/use-editor-actions.ts @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef } from "react"; +import { useEffect, useState } from "react"; import { useTimelineStore } from "@/timeline/timeline-store"; import { useActionHandler } from "@/actions/use-action-handler"; import { useEditor } from "@/editor/use-editor"; @@ -25,6 +25,7 @@ import { clearActiveScope, type ScopeEntry, } from "@/selection/scope"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; export function useEditorActions() { const editor = useEditor(); @@ -36,47 +37,34 @@ export function useEditorActions() { const toggleSnapping = useTimelineStore((s) => s.toggleSnapping); const rippleEditingEnabled = useTimelineStore((s) => s.rippleEditingEnabled); const toggleRippleEditing = useTimelineStore((s) => s.toggleRippleEditing); - const hasTimelineSelectionRef = useRef(false); - const clearTimelineSelectionRef = useRef(() => {}); - const clearTimelineActiveSelectionRef = useRef(() => {}); - const timelineScopeRef = useRef<ScopeEntry | null>(null); const hasTimelineSelection = selectedElements.length > 0 || selectedKeyframes.length > 0 || selectedMaskPointSelection !== null; - - hasTimelineSelectionRef.current = hasTimelineSelection; - clearTimelineSelectionRef.current = () => { + const hasTimelineSelectionRef = useCommittedRef(hasTimelineSelection); + const clearTimelineSelectionRef = useCommittedRef(() => { editor.selection.clearSelection(); - }; - clearTimelineActiveSelectionRef.current = () => { + }); + const clearTimelineActiveSelectionRef = useCommittedRef(() => { editor.selection.clearMostSpecificSelection(); - }; - - if (!timelineScopeRef.current) { - timelineScopeRef.current = { - hasSelection: () => hasTimelineSelectionRef.current, - clear: () => { - clearTimelineSelectionRef.current(); - }, - clearActive: () => { - clearTimelineActiveSelectionRef.current(); - }, - }; - } + }); + const [timelineScope] = useState<ScopeEntry>(() => ({ + hasSelection: () => hasTimelineSelectionRef.current, + clear: () => { + clearTimelineSelectionRef.current(); + }, + clearActive: () => { + clearTimelineActiveSelectionRef.current(); + }, + })); useEffect(() => { if (!hasTimelineSelection) { return; } - const timelineScope = timelineScopeRef.current; - if (!timelineScope) { - return; - } - return activateScope({ entry: timelineScope }); - }, [hasTimelineSelection]); + }, [hasTimelineSelection, timelineScope]); useActionHandler( "toggle-play", diff --git a/apps/web/src/actions/use-keybindings.ts b/apps/web/src/actions/use-keybindings.ts index c129795c..e9d78982 100644 --- a/apps/web/src/actions/use-keybindings.ts +++ b/apps/web/src/actions/use-keybindings.ts @@ -33,7 +33,7 @@ export function useKeybindingsListener() { const isTextInput = activeElement instanceof HTMLElement && isTypableDOMElement({ element: activeElement }); - const boundAction = binding ? keybindings[binding] : undefined; + const boundAction = binding ? keybindings.get(binding) : undefined; if (normalizedKey === "escape" && isTextInput) { activeElement.blur(); diff --git a/apps/web/src/actions/use-keyboard-shortcuts-help.ts b/apps/web/src/actions/use-keyboard-shortcuts-help.ts index 7681e664..89a17887 100644 --- a/apps/web/src/actions/use-keyboard-shortcuts-help.ts +++ b/apps/web/src/actions/use-keyboard-shortcuts-help.ts @@ -39,27 +39,21 @@ export function useKeyboardShortcutsHelp() { const { keybindings } = useKeybindingsStore(); const shortcuts = useMemo(() => { - const result: KeyboardShortcut[] = []; - const actionToKeys: Partial<Record<TActionWithOptionalArgs, string[]>> = {}; + const actionToKeys = new Map<TActionWithOptionalArgs, string[]>(); - for (const [key, action] of Object.entries(keybindings) as Array< - [string, TActionWithOptionalArgs | undefined] - >) { - if (action) { - if (!actionToKeys[action]) { - actionToKeys[action] = []; - } - actionToKeys[action].push(formatKey({ key })); + for (const [key, action] of keybindings) { + const existing = actionToKeys.get(action); + if (existing) { + existing.push(formatKey({ key })); + } else { + actionToKeys.set(action, [formatKey({ key })]); } } - for (const [action, keys] of Object.entries(actionToKeys) as Array< - [TActionWithOptionalArgs, string[]] - >) { + const result: KeyboardShortcut[] = []; + for (const [action, keys] of actionToKeys) { const actionDef = ACTIONS[action]; - if (!actionDef) { - continue; - } + if (!actionDef) continue; result.push({ id: action, keys, diff --git a/apps/web/src/animation/property-registry.ts b/apps/web/src/animation/property-registry.ts index aacb9972..bca9d82e 100644 --- a/apps/web/src/animation/property-registry.ts +++ b/apps/web/src/animation/property-registry.ts @@ -32,7 +32,11 @@ export interface AnimationPropertyDefinition { supportsElement: ({ element }: { element: TimelineElement }) => boolean; getValue: ({ element }: { element: TimelineElement }) => AnimationValue | null; coerceValue: ({ value }: { value: AnimationValue }) => AnimationValue | null; - setValue: ({ + // Apply `value` to `element` for this property. Coerces the value through + // `coerceValue` and verifies element support; returns `element` unchanged + // if either fails. Cannot be bypassed — there is no kind-narrow `setValue` + // on the public surface, so callers can't apply an unvalidated value. + applyValue: ({ element, value, }: { @@ -94,7 +98,13 @@ function createNumberPropertyDefinition({ numericRange?: NumericSpec; supportsElement: AnimationPropertyDefinition["supportsElement"]; getValue: AnimationPropertyDefinition["getValue"]; - setValue: AnimationPropertyDefinition["setValue"]; + setValue: ({ + element, + value, + }: { + element: TimelineElement; + value: number; + }) => TimelineElement; }): AnimationPropertyDefinition { return { kind: "number", @@ -102,12 +112,51 @@ function createNumberPropertyDefinition({ numericRanges: numericRange ? { value: numericRange } : undefined, supportsElement, getValue, - coerceValue: ({ value }) => - coerceNumberValue({ - value, - numericRange, - }), - setValue, + coerceValue: ({ value }) => coerceNumberValue({ value, numericRange }), + applyValue: ({ element, value }) => { + if (!supportsElement({ element })) { + return element; + } + const coerced = coerceNumberValue({ value, numericRange }); + if (coerced === null) { + return element; + } + return setValue({ element, value: coerced }); + }, + }; +} + +function createColorPropertyDefinition({ + supportsElement, + getValue, + setValue, +}: { + supportsElement: AnimationPropertyDefinition["supportsElement"]; + getValue: AnimationPropertyDefinition["getValue"]; + setValue: ({ + element, + value, + }: { + element: TimelineElement; + value: string; + }) => TimelineElement; +}): AnimationPropertyDefinition { + return { + kind: "color", + defaultInterpolation: "linear", + supportsElement, + getValue, + coerceValue: ({ value }) => coerceColorValue({ value }), + applyValue: ({ element, value }) => { + if (!supportsElement({ element })) { + return element; + } + const coerced = coerceColorValue({ value }); + if (coerced === null) { + return element; + } + return setValue({ element, value: coerced }); + }, }; } @@ -126,7 +175,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< ...element, transform: { ...element.transform, - position: { ...element.transform.position, x: value as number }, + position: { ...element.transform.position, x: value }, }, } : element, @@ -142,7 +191,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< ...element, transform: { ...element.transform, - position: { ...element.transform.position, y: value as number }, + position: { ...element.transform.position, y: value }, }, } : element, @@ -156,7 +205,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< isVisualElement(element) ? { ...element, - transform: { ...element.transform, scaleX: value as number }, + transform: { ...element.transform, scaleX: value }, } : element, }), @@ -169,7 +218,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< isVisualElement(element) ? { ...element, - transform: { ...element.transform, scaleY: value as number }, + transform: { ...element.transform, scaleY: value }, } : element, }), @@ -182,7 +231,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< isVisualElement(element) ? { ...element, - transform: { ...element.transform, rotate: value as number }, + transform: { ...element.transform, rotate: value }, } : element, }), @@ -192,9 +241,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< getValue: ({ element }) => isVisualElement(element) ? element.opacity : null, setValue: ({ element, value }) => - isVisualElement(element) - ? { ...element, opacity: value as number } - : element, + isVisualElement(element) ? { ...element, opacity: value } : element, }), volume: createNumberPropertyDefinition({ numericRange: { min: VOLUME_DB_MIN, max: VOLUME_DB_MAX, step: 0.01 }, @@ -202,36 +249,26 @@ const ANIMATION_PROPERTY_REGISTRY: Record< getValue: ({ element }) => canElementHaveAudio(element) ? element.volume ?? 0 : null, setValue: ({ element, value }) => - canElementHaveAudio(element) - ? { ...element, volume: value as number } - : element, + canElementHaveAudio(element) ? { ...element, volume: value } : element, }), - color: { - kind: "color", - defaultInterpolation: "linear", + color: createColorPropertyDefinition({ supportsElement: ({ element }) => element.type === "text", getValue: ({ element }) => (element.type === "text" ? element.color : null), - coerceValue: ({ value }) => coerceColorValue({ value }), setValue: ({ element, value }) => - element.type === "text" - ? { ...element, color: value as string } - : element, - }, - "background.color": { - kind: "color", - defaultInterpolation: "linear", + element.type === "text" ? { ...element, color: value } : element, + }), + "background.color": createColorPropertyDefinition({ supportsElement: ({ element }) => element.type === "text", getValue: ({ element }) => element.type === "text" ? element.background.color : null, - coerceValue: ({ value }) => coerceColorValue({ value }), setValue: ({ element, value }) => element.type === "text" ? { ...element, - background: { ...element.background, color: value as string }, + background: { ...element.background, color: value }, } : element, - }, + }), "background.paddingX": createNumberPropertyDefinition({ numericRange: { min: 0, step: 1 }, supportsElement: ({ element }) => element.type === "text", @@ -243,7 +280,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< element.type === "text" ? { ...element, - background: { ...element.background, paddingX: value as number }, + background: { ...element.background, paddingX: value }, } : element, }), @@ -258,7 +295,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< element.type === "text" ? { ...element, - background: { ...element.background, paddingY: value as number }, + background: { ...element.background, paddingY: value }, } : element, }), @@ -273,7 +310,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< element.type === "text" ? { ...element, - background: { ...element.background, offsetX: value as number }, + background: { ...element.background, offsetX: value }, } : element, }), @@ -288,7 +325,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< element.type === "text" ? { ...element, - background: { ...element.background, offsetY: value as number }, + background: { ...element.background, offsetY: value }, } : element, }), @@ -307,7 +344,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record< element.type === "text" ? { ...element, - background: { ...element.background, cornerRadius: value as number }, + background: { ...element.background, cornerRadius: value }, } : element, }), @@ -362,11 +399,7 @@ export function withElementBaseValueForProperty({ value: AnimationValue; }): TimelineElement { const definition = getAnimationPropertyDefinition({ propertyPath }); - const coercedValue = definition.coerceValue({ value }); - if (coercedValue === null || !definition.supportsElement({ element })) { - return element; - } - return definition.setValue({ element, value: coercedValue }); + return definition.applyValue({ element, value }); } export function getDefaultInterpolationForProperty({ diff --git a/apps/web/src/app/brand/page.tsx b/apps/web/src/app/brand/page.tsx index 94e3f3e1..9728cbc7 100644 --- a/apps/web/src/app/brand/page.tsx +++ b/apps/web/src/app/brand/page.tsx @@ -190,7 +190,7 @@ export default function BrandPage() { </div> <div className="flex flex-col gap-3"> - <h2 className="font-semibold text-lg">What's not allowed</h2> + <h2 className="font-semibold text-lg">What's not allowed</h2> <ul className="text-muted-foreground text-base flex flex-col gap-2 leading-relaxed"> {[ "Using OpenCut in the name of your product, service, or domain.", diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index b3d40a37..680dfff4 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -130,8 +130,14 @@ function EditorLayout() { direction="vertical" className="size-full gap-[0.18rem]" onLayout={(sizes) => { - setPanel("mainContent", sizes[0] ?? panels.mainContent); - setPanel("timeline", sizes[1] ?? panels.timeline); + setPanel({ + panel: "mainContent", + size: sizes[0] ?? panels.mainContent, + }); + setPanel({ + panel: "timeline", + size: sizes[1] ?? panels.timeline, + }); }} > <ResizablePanel @@ -144,9 +150,12 @@ function EditorLayout() { direction="horizontal" className="size-full gap-[0.19rem] px-3" onLayout={(sizes) => { - setPanel("tools", sizes[0] ?? panels.tools); - setPanel("preview", sizes[1] ?? panels.preview); - setPanel("properties", sizes[2] ?? panels.properties); + setPanel({ panel: "tools", size: sizes[0] ?? panels.tools }); + setPanel({ panel: "preview", size: sizes[1] ?? panels.preview }); + setPanel({ + panel: "properties", + size: sizes[2] ?? panels.properties, + }); }} > <ResizablePanel diff --git a/apps/web/src/app/privacy/page.tsx b/apps/web/src/app/privacy/page.tsx index 6362146e..17775147 100644 --- a/apps/web/src/app/privacy/page.tsx +++ b/apps/web/src/app/privacy/page.tsx @@ -57,7 +57,10 @@ export default function PrivacyPage() { content is tracked </li> <li>You can clear local data from your browser at any time</li> - <li>We don't sell or share your data with anyone (we don't even have it)</li> + <li> + We don't sell or share your data with anyone (we don't + even have it) + </li> </ol> <p className="mt-4"> Questions? Email us at{" "} @@ -172,7 +175,7 @@ export default function PrivacyPage() { <a href={SOCIAL_LINKS.github} target="_blank" - rel="noopener" + rel="noopener noreferrer" className="text-primary hover:underline" > GitHub @@ -189,7 +192,7 @@ export default function PrivacyPage() { <a href={`${SOCIAL_LINKS.github}/issues`} target="_blank" - rel="noopener" + rel="noopener noreferrer" className="text-primary hover:underline" > GitHub repository @@ -205,7 +208,7 @@ export default function PrivacyPage() { <a href={SOCIAL_LINKS.x} target="_blank" - rel="noopener" + rel="noopener noreferrer" className="text-primary hover:underline" > X (Twitter) diff --git a/apps/web/src/app/terms/page.tsx b/apps/web/src/app/terms/page.tsx index 0845892c..5f2e0420 100644 --- a/apps/web/src/app/terms/page.tsx +++ b/apps/web/src/app/terms/page.tsx @@ -51,9 +51,13 @@ export default function TermsPage() { Free for personal and commercial use with no watermarks or restrictions </li> - <li>You're responsible for how you use it - don't break the law</li> <li> - Service provided "as is" - we can't guarantee perfect uptime + You're responsible for how you use it - don't break + the law + </li> + <li> + Service provided "as is" - we can't guarantee + perfect uptime </li> <li> Open source means you can review our code and self-host if @@ -109,8 +113,8 @@ export default function TermsPage() { </li> </ul> <p> - You're responsible for how you use OpenCut and the content you create. - Don't use it for anything illegal in your jurisdiction. + You're responsible for how you use OpenCut and the content you + create. Don't use it for anything illegal in your jurisdiction. </p> </section> @@ -127,8 +131,8 @@ export default function TermsPage() { <h2 className="text-2xl font-semibold">Service</h2> <p> OpenCut does not currently require an account. The service is provided - "as is" without warranties. While we strive for reliability, we can't - guarantee uninterrupted service. + "as is" without warranties. While we strive for + reliability, we can't guarantee uninterrupted service. </p> </section> @@ -146,7 +150,7 @@ export default function TermsPage() { <a href={SOCIAL_LINKS.github} target="_blank" - rel="noopener" + rel="noopener noreferrer" className="text-primary hover:underline" > GitHub @@ -161,12 +165,12 @@ export default function TermsPage() { OpenCut is provided free of charge. To the extent permitted by law: </p> <ul className="list-disc space-y-2 pl-6"> - <li>We're not liable for any loss of data or content</li> + <li>We're not liable for any loss of data or content</li> <li> Projects are stored in your browser and may be lost if you clear browser data </li> - <li>We're not responsible for how you use the service</li> + <li>We're not responsible for how you use the service</li> <li>Our liability is limited to the maximum extent allowed by law</li> </ul> <p> @@ -180,7 +184,7 @@ export default function TermsPage() { <h2 className="text-2xl font-semibold">Service Changes</h2> <p>We may update OpenCut and these terms:</p> <ul className="list-disc space-y-2 pl-6"> - <li>We'll notify you of significant changes to these terms</li> + <li>We'll notify you of significant changes to these terms</li> <li>Continued use means you accept any updates</li> <li>You can always self-host an older version if you prefer</li> <li>Major changes will be discussed with the community on GitHub</li> @@ -203,7 +207,7 @@ export default function TermsPage() { <a href={`${SOCIAL_LINKS.github}/issues`} target="_blank" - rel="noopener" + rel="noopener noreferrer" className="text-primary hover:underline" > GitHub repository @@ -219,7 +223,7 @@ export default function TermsPage() { <a href={SOCIAL_LINKS.x} target="_blank" - rel="noopener" + rel="noopener noreferrer" className="text-primary hover:underline" > X (Twitter) diff --git a/apps/web/src/clipboard/handlers/elements.ts b/apps/web/src/clipboard/handlers/elements.ts index b2f5f2c3..2297b493 100644 --- a/apps/web/src/clipboard/handlers/elements.ts +++ b/apps/web/src/clipboard/handlers/elements.ts @@ -35,7 +35,7 @@ export const ElementsClipboardHandler = { }; }, - paste(entry, context) { + paste({ entry, context }) { if (entry.items.length === 0) { return null; } diff --git a/apps/web/src/clipboard/handlers/index.ts b/apps/web/src/clipboard/handlers/index.ts index 29d4d1d0..a042c3dd 100644 --- a/apps/web/src/clipboard/handlers/index.ts +++ b/apps/web/src/clipboard/handlers/index.ts @@ -45,5 +45,5 @@ export function buildPasteClipboardCommand({ const handler = clipboardHandlers[entry.type] as ClipboardHandler< typeof entry.type >; - return handler.paste(entry as never, context); + return handler.paste({ entry: entry as never, context }); } diff --git a/apps/web/src/clipboard/handlers/keyframes.ts b/apps/web/src/clipboard/handlers/keyframes.ts index cb0c0e65..06879e3e 100644 --- a/apps/web/src/clipboard/handlers/keyframes.ts +++ b/apps/web/src/clipboard/handlers/keyframes.ts @@ -156,7 +156,10 @@ export const KeyframesClipboardHandler = { }; }, - paste(entry, { selectedElements, time }) { + paste({ + entry, + context: { selectedElements, time }, + }) { const targetElement = selectedElements[0]; if (!targetElement || entry.items.length === 0) { return null; diff --git a/apps/web/src/clipboard/types.ts b/apps/web/src/clipboard/types.ts index 562f9ac7..1d469a30 100644 --- a/apps/web/src/clipboard/types.ts +++ b/apps/web/src/clipboard/types.ts @@ -69,10 +69,10 @@ export interface ClipboardHandler<TType extends ClipboardEntryType> { type: TType; canCopy(context: CopyContext): boolean; copy(context: CopyContext): ClipboardEntryByType[TType] | null; - paste( - entry: ClipboardEntryByType[TType], - context: PasteContext, - ): Command | null; + paste(args: { + entry: ClipboardEntryByType[TType]; + context: PasteContext; + }): Command | null; } export type ClipboardHandlerMap = { diff --git a/apps/web/src/commands/media/add-media-asset.ts b/apps/web/src/commands/media/add-media-asset.ts index d22241a2..629bc3ee 100644 --- a/apps/web/src/commands/media/add-media-asset.ts +++ b/apps/web/src/commands/media/add-media-asset.ts @@ -16,14 +16,22 @@ export class AddMediaAssetCommand extends Command { private previousProjectFps: FrameRate | null = null; private appliedProjectFps: FrameRate | null = null; - constructor( - private projectId: string, - private asset: Omit<MediaAsset, "id">, - ) { + constructor({ + projectId, + asset, + }: { + projectId: string; + asset: Omit<MediaAsset, "id">; + }) { super(); + this.projectId = projectId; + this.asset = asset; this.assetId = generateUUID(); } + private projectId: string; + private asset: Omit<MediaAsset, "id">; + execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); this.savedAssets = [...editor.media.getAssets()]; @@ -117,7 +125,14 @@ export class AddMediaAssetCommand extends Command { const activeProject = editor.project.getActiveOrNull(); if (!activeProject) return; - if (!this.appliedProjectFps || !frameRatesEqual(activeProject.settings.fps, this.appliedProjectFps)) return; + if ( + !this.appliedProjectFps || + !frameRatesEqual({ + a: activeProject.settings.fps, + b: this.appliedProjectFps, + }) + ) + return; const highestRemainingVideoFps = getHighestImportedVideoFps({ mediaAssets: editor.media.getAssets(), diff --git a/apps/web/src/commands/media/remove-media-asset.ts b/apps/web/src/commands/media/remove-media-asset.ts index 8da62181..38ff79fe 100644 --- a/apps/web/src/commands/media/remove-media-asset.ts +++ b/apps/web/src/commands/media/remove-media-asset.ts @@ -13,13 +13,21 @@ export class RemoveMediaAssetCommand extends Command { private savedTracks: SceneTracks | null = null; private removedAsset: MediaAsset | null = null; - constructor( - private projectId: string, - private assetId: string, - ) { + constructor({ + projectId, + assetId, + }: { + projectId: string; + assetId: string; + }) { super(); + this.projectId = projectId; + this.assetId = assetId; } + private projectId: string; + private assetId: string; + execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); const assets = editor.media.getAssets(); diff --git a/apps/web/src/commands/scene/create-scene.ts b/apps/web/src/commands/scene/create-scene.ts index b554fe90..3776467d 100644 --- a/apps/web/src/commands/scene/create-scene.ts +++ b/apps/web/src/commands/scene/create-scene.ts @@ -7,13 +7,21 @@ export class CreateSceneCommand extends Command { private savedScenes: TScene[] | null = null; private createdScene: TScene | null = null; - constructor( - private name: string, - private isMain: boolean = false, - ) { + constructor({ + name, + isMain = false, + }: { + name: string; + isMain?: boolean; + }) { super(); + this.name = name; + this.isMain = isMain; } + private name: string; + private isMain: boolean; + execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); this.savedScenes = [...editor.scenes.getScenes()]; diff --git a/apps/web/src/commands/scene/move-bookmark.ts b/apps/web/src/commands/scene/move-bookmark.ts index f3628b28..45b7bda5 100644 --- a/apps/web/src/commands/scene/move-bookmark.ts +++ b/apps/web/src/commands/scene/move-bookmark.ts @@ -8,13 +8,21 @@ import type { MediaTime } from "@/wasm"; export class MoveBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; - constructor( - private fromTime: MediaTime, - private toTime: MediaTime, - ) { + constructor({ + fromTime, + toTime, + }: { + fromTime: MediaTime; + toTime: MediaTime; + }) { super(); + this.fromTime = fromTime; + this.toTime = toTime; } + private fromTime: MediaTime; + private toTime: MediaTime; + execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); const activeScene = editor.scenes.getActiveScene(); diff --git a/apps/web/src/commands/scene/remove-bookmark.ts b/apps/web/src/commands/scene/remove-bookmark.ts index bff5c035..95403e0e 100644 --- a/apps/web/src/commands/scene/remove-bookmark.ts +++ b/apps/web/src/commands/scene/remove-bookmark.ts @@ -6,11 +6,11 @@ import { getFrameTime, removeBookmarkFromArray, } from "@/timeline/bookmarks/index"; -import type { MediaTime } from "@/wasm"; +import { type MediaTime, ZERO_MEDIA_TIME } from "@/wasm"; export class RemoveBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; - private frameTime: MediaTime = 0 as MediaTime; + private frameTime: MediaTime = ZERO_MEDIA_TIME; constructor(private time: MediaTime) { super(); diff --git a/apps/web/src/commands/scene/rename-scene.ts b/apps/web/src/commands/scene/rename-scene.ts index 19d6bfd8..8bc8724b 100644 --- a/apps/web/src/commands/scene/rename-scene.ts +++ b/apps/web/src/commands/scene/rename-scene.ts @@ -7,13 +7,21 @@ export class RenameSceneCommand extends Command { private savedScenes: TScene[] | null = null; private previousName: string | null = null; - constructor( - private sceneId: string, - private newName: string, - ) { + constructor({ + sceneId, + newName, + }: { + sceneId: string; + newName: string; + }) { super(); + this.sceneId = sceneId; + this.newName = newName; } + private sceneId: string; + private newName: string; + execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); const scenes = editor.scenes.getScenes(); diff --git a/apps/web/src/commands/scene/toggle-bookmark.ts b/apps/web/src/commands/scene/toggle-bookmark.ts index 9f318f80..31751be4 100644 --- a/apps/web/src/commands/scene/toggle-bookmark.ts +++ b/apps/web/src/commands/scene/toggle-bookmark.ts @@ -6,11 +6,11 @@ import { getFrameTime, toggleBookmarkInArray, } from "@/timeline/bookmarks/index"; -import type { MediaTime } from "@/wasm"; +import { type MediaTime, ZERO_MEDIA_TIME } from "@/wasm"; export class ToggleBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; - private frameTime: MediaTime = 0 as MediaTime; + private frameTime: MediaTime = ZERO_MEDIA_TIME; constructor(private time: MediaTime) { super(); diff --git a/apps/web/src/commands/scene/update-bookmark.ts b/apps/web/src/commands/scene/update-bookmark.ts index 238bb3de..7208abc4 100644 --- a/apps/web/src/commands/scene/update-bookmark.ts +++ b/apps/web/src/commands/scene/update-bookmark.ts @@ -11,13 +11,21 @@ import type { MediaTime } from "@/wasm"; export class UpdateBookmarkCommand extends Command { private savedScenes: TScene[] | null = null; - constructor( - private time: MediaTime, - private updates: Partial<Omit<Bookmark, "time">>, - ) { + constructor({ + time, + updates, + }: { + time: MediaTime; + updates: Partial<Omit<Bookmark, "time">>; + }) { super(); + this.time = time; + this.updates = updates; } + private time: MediaTime; + private updates: Partial<Omit<Bookmark, "time">>; + execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); const activeScene = editor.scenes.getActiveScene(); diff --git a/apps/web/src/commands/timeline/track/add-track.ts b/apps/web/src/commands/timeline/track/add-track.ts index 6b58b687..90becf13 100644 --- a/apps/web/src/commands/timeline/track/add-track.ts +++ b/apps/web/src/commands/timeline/track/add-track.ts @@ -11,14 +11,22 @@ export class AddTrackCommand extends Command { private trackId: string; private savedState: SceneTracks | null = null; - constructor( - private type: TrackType, - private index?: number, - ) { + constructor({ + type, + index, + }: { + type: TrackType; + index?: number; + }) { super(); + this.type = type; + this.index = index; this.trackId = generateUUID(); } + private type: TrackType; + private index?: number; + execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); this.savedState = editor.scenes.getActiveScene().tracks; diff --git a/apps/web/src/commands/timeline/tracks-snapshot.ts b/apps/web/src/commands/timeline/tracks-snapshot.ts index 5b6d871a..8e55e602 100644 --- a/apps/web/src/commands/timeline/tracks-snapshot.ts +++ b/apps/web/src/commands/timeline/tracks-snapshot.ts @@ -3,13 +3,21 @@ import type { SceneTracks } from "@/timeline"; import { EditorCore } from "@/core"; export class TracksSnapshotCommand extends Command { - constructor( - private before: SceneTracks, - private after: SceneTracks, - ) { + constructor({ + before, + after, + }: { + before: SceneTracks; + after: SceneTracks; + }) { super(); + this.before = before; + this.after = after; } + private before: SceneTracks; + private after: SceneTracks; + execute(): CommandResult | undefined { EditorCore.getInstance().timeline.updateTracks(this.after); return undefined; diff --git a/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx index 1539d305..b412de7b 100644 --- a/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx +++ b/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx @@ -93,7 +93,7 @@ interface AssetsPanelStore { setMediaViewMode: (mode: MediaViewMode) => void; mediaSortBy: MediaSortKey; mediaSortOrder: MediaSortOrder; - setMediaSort: (key: MediaSortKey, order: MediaSortOrder) => void; + setMediaSort: (args: { key: MediaSortKey; order: MediaSortOrder }) => void; } export const useAssetsPanelStore = create<AssetsPanelStore>()( @@ -109,7 +109,7 @@ export const useAssetsPanelStore = create<AssetsPanelStore>()( setMediaViewMode: (mode) => set({ mediaViewMode: mode }), mediaSortBy: "name", mediaSortOrder: "asc", - setMediaSort: (key, order) => + setMediaSort: ({ key, order }) => set({ mediaSortBy: key, mediaSortOrder: order }), }), { diff --git a/apps/web/src/components/editor/panels/assets/views/assets.tsx b/apps/web/src/components/editor/panels/assets/views/assets.tsx index c071fdc7..00cb015f 100644 --- a/apps/web/src/components/editor/panels/assets/views/assets.tsx +++ b/apps/web/src/components/editor/panels/assets/views/assets.tsx @@ -139,9 +139,12 @@ export function MediaView() { const handleSort = ({ key }: { key: MediaSortKey }) => { if (mediaSortBy === key) { - setMediaSort(key, mediaSortOrder === "asc" ? "desc" : "asc"); + setMediaSort({ + key, + order: mediaSortOrder === "asc" ? "desc" : "asc", + }); } else { - setMediaSort(key, "asc"); + setMediaSort({ key, order: "asc" }); } }; diff --git a/apps/web/src/components/editor/panels/assets/views/settings/index.tsx b/apps/web/src/components/editor/panels/assets/views/settings/index.tsx index bf3a1c16..5edbeab3 100644 --- a/apps/web/src/components/editor/panels/assets/views/settings/index.tsx +++ b/apps/web/src/components/editor/panels/assets/views/settings/index.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRef, useState } from "react"; +import { useState } from "react"; import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; import { Select, @@ -34,6 +34,10 @@ import type { TCanvasSize } from "@/project/types"; type SettingsView = "project-info" | "background"; +function isSettingsView(value: string): value is SettingsView { + return value === "project-info" || value === "background"; +} + const PRESET_LABELS: Record<string, string> = { "1:1": "1:1", "16:9": "16:9", @@ -73,23 +77,20 @@ function useCanvasDimensionDraft({ value: number; onCommit: (value: number) => void; }) { - const pendingValueRef = useRef(value); - const syncedValueRef = useRef(value); - - if (syncedValueRef.current !== value) { - syncedValueRef.current = value; - pendingValueRef.current = value; - } + const [pendingValue, setPendingValue] = useState(value); return usePropertyDraft({ displayValue: formatCanvasDimension({ value }), parse: (input) => parseCanvasDimension({ input }), + onStartEditing: () => { + setPendingValue(value); + }, onPreview: (nextValue) => { - pendingValueRef.current = nextValue; + setPendingValue(nextValue); }, onCommit: () => { - if (pendingValueRef.current !== value) { - onCommit(pendingValueRef.current); + if (pendingValue !== value) { + onCommit(pendingValue); } }, }); @@ -212,7 +213,14 @@ export function SettingsView() { contentClassName="px-0" scrollClassName="pt-0" actions={ - <Tabs value={view} onValueChange={(v) => setView(v as SettingsView)}> + <Tabs + value={view} + onValueChange={(value) => { + if (isSettingsView(value)) { + setView(value); + } + }} + > <TabsList> <TabsTrigger value="project-info">Project info</TabsTrigger> <TabsTrigger value="background">Background</TabsTrigger> diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts index f6216208..91d29fd7 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts @@ -1,96 +1,96 @@ -import { useEditor } from "@/editor/use-editor";
-import {
- getKeyframeAtTime,
- hasKeyframesForPath,
- upsertElementKeyframe,
-} from "@/animation";
-import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
-import type { TimelineElement } from "@/timeline";
-import type { MediaTime } from "@/wasm";
-
-export function useKeyframedColorProperty({
- trackId,
- elementId,
- animations,
- propertyPath,
- localTime,
- isPlayheadWithinElementRange,
- resolvedColor,
- buildBaseUpdates,
-}: {
- trackId: string;
- elementId: string;
- animations: ElementAnimations | undefined;
- propertyPath: AnimationPropertyPath;
- localTime: MediaTime;
- isPlayheadWithinElementRange: boolean;
- resolvedColor: string;
- buildBaseUpdates: ({ value }: { value: string }) => Partial<TimelineElement>;
-}) {
- const editor = useEditor();
-
- const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath });
- const keyframeAtTime = isPlayheadWithinElementRange
- ? getKeyframeAtTime({ animations, propertyPath, time: localTime })
- : null;
- const keyframeIdAtTime = keyframeAtTime?.id ?? null;
- const isKeyframedAtTime = keyframeAtTime !== null;
- const shouldUseAnimatedChannel =
- hasAnimatedKeyframes && isPlayheadWithinElementRange;
-
- const onChange = ({ color }: { color: string }) => {
- if (shouldUseAnimatedChannel) {
- editor.timeline.previewElements({
- updates: [
- {
- trackId,
- elementId,
- updates: {
- animations: upsertElementKeyframe({
- animations,
- propertyPath,
- time: localTime,
- value: color,
- }),
- },
- },
- ],
- });
- return;
- }
-
- editor.timeline.previewElements({
- updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }],
- });
- };
-
- const onChangeEnd = () => editor.timeline.commitPreview();
-
- const toggleKeyframe = () => {
- if (!isPlayheadWithinElementRange) {
- return;
- }
-
- if (keyframeIdAtTime) {
- editor.timeline.removeKeyframes({
- keyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }],
- });
- return;
- }
-
- editor.timeline.upsertKeyframes({
- keyframes: [
- { trackId, elementId, propertyPath, time: localTime, value: resolvedColor },
- ],
- });
- };
-
- return {
- isKeyframedAtTime,
- hasAnimatedKeyframes,
- keyframeIdAtTime,
- onChange,
- onChangeEnd,
- toggleKeyframe,
- };
-}
+import { useEditor } from "@/editor/use-editor"; +import { + getKeyframeAtTime, + hasKeyframesForPath, + upsertElementKeyframe, +} from "@/animation"; +import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; +import type { TimelineElement } from "@/timeline"; +import type { MediaTime } from "@/wasm"; + +export function useKeyframedColorProperty({ + trackId, + elementId, + animations, + propertyPath, + localTime, + isPlayheadWithinElementRange, + resolvedColor, + buildBaseUpdates, +}: { + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + propertyPath: AnimationPropertyPath; + localTime: MediaTime; + isPlayheadWithinElementRange: boolean; + resolvedColor: string; + buildBaseUpdates: ({ value }: { value: string }) => Partial<TimelineElement>; +}) { + const editor = useEditor(); + + const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const onChange = ({ color }: { color: string }) => { + if (shouldUseAnimatedChannel) { + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { + animations: upsertElementKeyframe({ + animations, + propertyPath, + time: localTime, + value: color, + }), + }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }], + }); + }; + + const onChangeEnd = () => editor.timeline.commitPreview(); + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) { + return; + } + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, time: localTime, value: resolvedColor }, + ], + }); + }; + + return { + isKeyframedAtTime, + hasAnimatedKeyframes, + keyframeIdAtTime, + onChange, + onChangeEnd, + toggleKeyframe, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts index d52f7d97..610c2253 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts @@ -1,175 +1,175 @@ -import { useEditor } from "@/editor/use-editor";
-import {
- getKeyframeAtTime,
- hasKeyframesForPath,
- upsertElementKeyframe,
-} from "@/animation";
-import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types";
-import type { TimelineElement } from "@/timeline";
-import { snapToStep } from "@/utils/math";
-import { usePropertyDraft } from "./use-property-draft";
-import type { MediaTime } from "@/wasm";
-
-export function useKeyframedNumberProperty({
- trackId,
- elementId,
- animations,
- propertyPath,
- localTime,
- isPlayheadWithinElementRange,
- displayValue,
- parse,
- valueAtPlayhead,
- step,
- buildBaseUpdates,
- buildAdditionalKeyframes,
-}: {
- trackId: string;
- elementId: string;
- animations: ElementAnimations | undefined;
- propertyPath: AnimationPropertyPath;
- localTime: MediaTime;
- isPlayheadWithinElementRange: boolean;
- displayValue: string;
- parse: (input: string) => number | null;
- valueAtPlayhead: number;
- step?: number;
- buildBaseUpdates: ({ value }: { value: number }) => Partial<TimelineElement>;
- buildAdditionalKeyframes?: ({
- value,
- }: { value: number }) => Array<{ propertyPath: AnimationPropertyPath; value: number }>;
-}) {
- const editor = useEditor();
- const snapValue = (value: number) =>
- step != null ? snapToStep({ value, step }) : value;
-
- const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath });
- const keyframeAtTime = isPlayheadWithinElementRange
- ? getKeyframeAtTime({ animations, propertyPath, time: localTime })
- : null;
- const keyframeIdAtTime = keyframeAtTime?.id ?? null;
- const isKeyframedAtTime = keyframeAtTime !== null;
- const shouldUseAnimatedChannel =
- hasAnimatedKeyframes && isPlayheadWithinElementRange;
-
- const previewValue = ({ value }: { value: number }) => {
- const nextValue = snapValue(value);
- if (shouldUseAnimatedChannel) {
- const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? [];
- const updatedAnimations = [
- { propertyPath, value: nextValue },
- ...additionalKeyframes,
- ].reduce(
- (currentAnimations, keyframe) =>
- upsertElementKeyframe({
- animations: currentAnimations,
- propertyPath: keyframe.propertyPath,
- time: localTime,
- value: keyframe.value,
- }),
- animations,
- );
- editor.timeline.previewElements({
- updates: [
- {
- trackId,
- elementId,
- updates: { animations: updatedAnimations },
- },
- ],
- });
- return;
- }
-
- editor.timeline.previewElements({
- updates: [
- {
- trackId,
- elementId,
- updates: buildBaseUpdates({ value: nextValue }),
- },
- ],
- });
- };
-
- const propertyDraft = usePropertyDraft({
- displayValue,
- parse: (input) => {
- const parsedValue = parse(input);
- return parsedValue === null ? null : snapValue(parsedValue);
- },
- onPreview: (value) => previewValue({ value }),
- onCommit: () => editor.timeline.commitPreview(),
- });
-
- const toggleKeyframe = () => {
- if (!isPlayheadWithinElementRange) {
- return;
- }
-
- if (keyframeIdAtTime) {
- editor.timeline.removeKeyframes({
- keyframes: [
- {
- trackId,
- elementId,
- propertyPath,
- keyframeId: keyframeIdAtTime,
- },
- ],
- });
- return;
- }
-
- editor.timeline.upsertKeyframes({
- keyframes: [
- {
- trackId,
- elementId,
- propertyPath,
- time: localTime,
- value: snapValue(valueAtPlayhead),
- },
- ],
- });
- };
-
- const commitValue = ({ value }: { value: number }) => {
- const nextValue = snapValue(value);
- if (shouldUseAnimatedChannel) {
- const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? [];
- editor.timeline.upsertKeyframes({
- keyframes: [
- { trackId, elementId, propertyPath, time: localTime, value: nextValue },
- ...additionalKeyframes.map((keyframe) => ({
- trackId,
- elementId,
- propertyPath: keyframe.propertyPath,
- time: localTime,
- value: keyframe.value,
- })),
- ],
- });
- return;
- }
-
- editor.timeline.updateElements({
- updates: [
- {
- trackId,
- elementId,
- patch: buildBaseUpdates({ value: nextValue }),
- },
- ],
- });
- };
-
- return {
- ...propertyDraft,
- hasAnimatedKeyframes,
- isKeyframedAtTime,
- keyframeIdAtTime,
- toggleKeyframe,
- commitValue,
- };
-}
+import { useEditor } from "@/editor/use-editor"; +import { + getKeyframeAtTime, + hasKeyframesForPath, + upsertElementKeyframe, +} from "@/animation"; +import type { AnimationPropertyPath, ElementAnimations } from "@/animation/types"; +import type { TimelineElement } from "@/timeline"; +import { snapToStep } from "@/utils/math"; +import { usePropertyDraft } from "./use-property-draft"; +import type { MediaTime } from "@/wasm"; + +export function useKeyframedNumberProperty({ + trackId, + elementId, + animations, + propertyPath, + localTime, + isPlayheadWithinElementRange, + displayValue, + parse, + valueAtPlayhead, + step, + buildBaseUpdates, + buildAdditionalKeyframes, +}: { + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + propertyPath: AnimationPropertyPath; + localTime: MediaTime; + isPlayheadWithinElementRange: boolean; + displayValue: string; + parse: (input: string) => number | null; + valueAtPlayhead: number; + step?: number; + buildBaseUpdates: ({ value }: { value: number }) => Partial<TimelineElement>; + buildAdditionalKeyframes?: ({ + value, + }: { value: number }) => Array<{ propertyPath: AnimationPropertyPath; value: number }>; +}) { + const editor = useEditor(); + const snapValue = (value: number) => + step != null ? snapToStep({ value, step }) : value; + + const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const previewValue = ({ value }: { value: number }) => { + const nextValue = snapValue(value); + if (shouldUseAnimatedChannel) { + const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? []; + const updatedAnimations = [ + { propertyPath, value: nextValue }, + ...additionalKeyframes, + ].reduce( + (currentAnimations, keyframe) => + upsertElementKeyframe({ + animations: currentAnimations, + propertyPath: keyframe.propertyPath, + time: localTime, + value: keyframe.value, + }), + animations, + ); + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { animations: updatedAnimations }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: buildBaseUpdates({ value: nextValue }), + }, + ], + }); + }; + + const propertyDraft = usePropertyDraft({ + displayValue, + parse: (input) => { + const parsedValue = parse(input); + return parsedValue === null ? null : snapValue(parsedValue); + }, + onPreview: (value) => previewValue({ value }), + onCommit: () => editor.timeline.commitPreview(), + }); + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) { + return; + } + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + keyframeId: keyframeIdAtTime, + }, + ], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + time: localTime, + value: snapValue(valueAtPlayhead), + }, + ], + }); + }; + + const commitValue = ({ value }: { value: number }) => { + const nextValue = snapValue(value); + if (shouldUseAnimatedChannel) { + const additionalKeyframes = buildAdditionalKeyframes?.({ value: nextValue }) ?? []; + editor.timeline.upsertKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, time: localTime, value: nextValue }, + ...additionalKeyframes.map((keyframe) => ({ + trackId, + elementId, + propertyPath: keyframe.propertyPath, + time: localTime, + value: keyframe.value, + })), + ], + }); + return; + } + + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId, + patch: buildBaseUpdates({ value: nextValue }), + }, + ], + }); + }; + + return { + ...propertyDraft, + hasAnimatedKeyframes, + isKeyframedAtTime, + keyframeIdAtTime, + toggleKeyframe, + commitValue, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts b/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts index 0e7ae6a7..7e8f6e40 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts @@ -1,4 +1,4 @@ -import { useReducer, useRef } from "react";
+import { useState } from "react";
import { evaluateMathExpression } from "@/utils/math";
function looksLikeExpression({ input }: { input: string }): boolean {
@@ -14,59 +14,56 @@ export function usePropertyDraft<T>({ parse,
onPreview,
onCommit,
+ onStartEditing,
supportsExpressions = true,
}: {
displayValue: string;
parse: (input: string) => T | null;
onPreview: (value: T) => void;
onCommit: () => void;
+ onStartEditing?: () => void;
supportsExpressions?: boolean;
}) {
- const [, forceRender] = useReducer(
- (renderVersion: number) => renderVersion + 1,
- 0,
- );
- const isEditing = useRef(false);
- const draft = useRef("");
+ const [isEditing, setIsEditing] = useState(false);
+ const [draft, setDraft] = useState("");
return {
- displayValue: isEditing.current ? draft.current : sourceDisplay,
+ displayValue: isEditing ? draft : sourceDisplay,
scrubTo: (value: number) => {
const parsed = parse(String(value));
if (parsed !== null) onPreview(parsed);
},
commitScrub: onCommit,
onFocus: () => {
- isEditing.current = true;
- draft.current = sourceDisplay;
- forceRender();
+ setIsEditing(true);
+ setDraft(sourceDisplay);
+ onStartEditing?.();
},
onChange: (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
- draft.current = event.target.value;
- forceRender();
+ const nextDraft = event.target.value;
+ setDraft(nextDraft);
- const parsed = parse(event.target.value);
+ const parsed = parse(nextDraft);
if (parsed !== null) {
onPreview(parsed);
}
},
- onBlur: () => {
- if (
- supportsExpressions &&
- looksLikeExpression({ input: draft.current })
- ) {
- const evaluated = evaluateMathExpression({ input: draft.current });
+ onBlur: (
+ event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>,
+ ) => {
+ const nextDraft = event.target.value;
+ if (supportsExpressions && looksLikeExpression({ input: nextDraft })) {
+ const evaluated = evaluateMathExpression({ input: nextDraft });
if (evaluated !== null) {
const parsed = parse(String(evaluated));
if (parsed !== null) onPreview(parsed);
}
}
onCommit();
- isEditing.current = false;
- draft.current = "";
- forceRender();
+ setIsEditing(false);
+ setDraft("");
},
};
}
diff --git a/apps/web/src/components/editor/panels/properties/index.tsx b/apps/web/src/components/editor/panels/properties/index.tsx index 52fa42b5..d946027d 100644 --- a/apps/web/src/components/editor/panels/properties/index.tsx +++ b/apps/web/src/components/editor/panels/properties/index.tsx @@ -71,7 +71,12 @@ export function PropertiesPanel() { <Button variant={tab.id === activeTab.id ? "secondary" : "ghost"} size="icon" - onClick={() => setActiveTab(element.type, tab.id)} + onClick={() => + setActiveTab({ + elementType: element.type, + tabId: tab.id, + }) + } aria-label={tab.label} className={cn( "shrink-0", diff --git a/apps/web/src/components/editor/panels/properties/stores/properties-store.ts b/apps/web/src/components/editor/panels/properties/stores/properties-store.ts index bd57d4b0..f6304349 100644 --- a/apps/web/src/components/editor/panels/properties/stores/properties-store.ts +++ b/apps/web/src/components/editor/panels/properties/stores/properties-store.ts @@ -2,17 +2,18 @@ import { create } from "zustand"; interface PropertiesState {
activeTabPerType: Record<string, string>;
- setActiveTab: (elementType: string, tabId: string) => void;
+ setActiveTab: (args: { elementType: string; tabId: string }) => void;
isTransformScaleLocked: boolean;
- setTransformScaleLocked: (locked: boolean) => void;
+ setTransformScaleLocked: (args: { locked: boolean }) => void;
}
export const usePropertiesStore = create<PropertiesState>()((set) => ({
activeTabPerType: {},
- setActiveTab: (elementType, tabId) =>
+ setActiveTab: ({ elementType, tabId }) =>
set((state) => ({
activeTabPerType: { ...state.activeTabPerType, [elementType]: tabId },
})),
isTransformScaleLocked: false,
- setTransformScaleLocked: (locked) => set({ isTransformScaleLocked: locked }),
+ setTransformScaleLocked: ({ locked }) =>
+ set({ isTransformScaleLocked: locked }),
}));
diff --git a/apps/web/src/core/index.ts b/apps/web/src/core/index.ts index 3e55e1d6..0977666f 100644 --- a/apps/web/src/core/index.ts +++ b/apps/web/src/core/index.ts @@ -39,7 +39,7 @@ export class EditorCore { this.project = new ProjectManager(this); this.media = new MediaManager(this); this.renderer = new RendererManager(this); - this.save = new SaveManager(this); + this.save = new SaveManager({ editor: this }); this.audio = new AudioManager(this); this.selection = new SelectionManager(this); this.clipboard = new ClipboardManager(this); diff --git a/apps/web/src/core/managers/media-manager.ts b/apps/web/src/core/managers/media-manager.ts index 553d1557..69da6e80 100644 --- a/apps/web/src/core/managers/media-manager.ts +++ b/apps/web/src/core/managers/media-manager.ts @@ -68,9 +68,17 @@ export class MediaManager { const command = uniqueIds.length === 1 - ? new RemoveMediaAssetCommand(projectId, uniqueIds[0]) + ? new RemoveMediaAssetCommand({ + projectId, + assetId: uniqueIds[0], + }) : new BatchCommand( - uniqueIds.map((id) => new RemoveMediaAssetCommand(projectId, id)), + uniqueIds.map((id) => + new RemoveMediaAssetCommand({ + projectId, + assetId: id, + }), + ), ); this.editor.command.execute({ command }); diff --git a/apps/web/src/core/managers/save-manager.ts b/apps/web/src/core/managers/save-manager.ts index b3c894d4..8cb2275a 100644 --- a/apps/web/src/core/managers/save-manager.ts +++ b/apps/web/src/core/managers/save-manager.ts @@ -12,13 +12,18 @@ export class SaveManager { private saveTimer: ReturnType<typeof setTimeout> | null = null;
private unsubscribeHandlers: Array<() => void> = [];
- constructor(
- private editor: EditorCore,
- { debounceMs = 800 }: SaveManagerOptions = {},
- ) {
+ constructor({
+ editor,
+ debounceMs = 800,
+ }: {
+ editor: EditorCore;
+ } & SaveManagerOptions) {
+ this.editor = editor;
this.debounceMs = debounceMs;
}
+ private editor: EditorCore;
+
start(): void {
if (this.unsubscribeHandlers.length > 0) return;
diff --git a/apps/web/src/core/managers/scenes-manager.ts b/apps/web/src/core/managers/scenes-manager.ts index c9558a6d..a4c364ce 100644 --- a/apps/web/src/core/managers/scenes-manager.ts +++ b/apps/web/src/core/managers/scenes-manager.ts @@ -41,7 +41,7 @@ export class ScenesManager { throw new Error("No active project"); } - const command = new CreateSceneCommand(name, isMain); + const command = new CreateSceneCommand({ name, isMain }); this.editor.command.execute({ command }); return command.getSceneId(); } @@ -77,7 +77,10 @@ export class ScenesManager { throw new Error("No active project"); } - const command = new RenameSceneCommand(sceneId, name); + const command = new RenameSceneCommand({ + sceneId, + newName: name, + }); this.editor.command.execute({ command }); } @@ -138,7 +141,7 @@ export class ScenesManager { time: MediaTime; updates: Partial<Omit<Bookmark, "time">>; }): Promise<void> { - const command = new UpdateBookmarkCommand(time, updates); + const command = new UpdateBookmarkCommand({ time, updates }); this.editor.command.execute({ command }); } @@ -149,7 +152,7 @@ export class ScenesManager { fromTime: MediaTime; toTime: MediaTime; }): Promise<void> { - const command = new MoveBookmarkCommand(fromTime, toTime); + const command = new MoveBookmarkCommand({ fromTime, toTime }); this.editor.command.execute({ command }); } diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts index 8fbfdb08..91a4eeef 100644 --- a/apps/web/src/core/managers/timeline-manager.ts +++ b/apps/web/src/core/managers/timeline-manager.ts @@ -73,7 +73,7 @@ export class TimelineManager { constructor(private editor: EditorCore) {} addTrack({ type, index }: { type: TrackType; index?: number }): string { - const command = new AddTrackCommand(type, index); + const command = new AddTrackCommand({ type, index }); this.editor.command.execute({ command }); return command.getTrackId(); } @@ -751,7 +751,10 @@ export class TimelineManager { } const afterTracks = this.previewTracks ?? this.applyPreviewOverlay(committedTracks); - const command = new TracksSnapshotCommand(committedTracks, afterTracks); + const command = new TracksSnapshotCommand({ + before: committedTracks, + after: afterTracks, + }); this.editor.command.push({ command }); this.previewOverlay.clear(); this.previewTracks = null; diff --git a/apps/web/src/editor/panel-store.ts b/apps/web/src/editor/panel-store.ts index 2cb768a1..5a2bd0f4 100644 --- a/apps/web/src/editor/panel-store.ts +++ b/apps/web/src/editor/panel-store.ts @@ -14,7 +14,7 @@ export type PanelId = keyof PanelSizes; interface PanelState {
panels: PanelSizes;
- setPanel: (panel: PanelId, size: number) => void;
+ setPanel: (args: { panel: PanelId; size: number }) => void;
setPanels: (sizes: Partial<PanelSizes>) => void;
resetPanels: () => void;
}
@@ -23,7 +23,7 @@ export const usePanelStore = create<PanelState>()( persist(
(set) => ({
...PANEL_CONFIG,
- setPanel: (panel, size) =>
+ setPanel: ({ panel, size }) =>
set((state) => ({
panels: {
...state.panels,
diff --git a/apps/web/src/editor/use-editor.ts b/apps/web/src/editor/use-editor.ts index ce48e036..6380d470 100644 --- a/apps/web/src/editor/use-editor.ts +++ b/apps/web/src/editor/use-editor.ts @@ -1,7 +1,15 @@ import { useCallback, useMemo, useRef, useSyncExternalStore } from "react"; import { EditorCore } from "@/core"; -function isShallowEqual(a: unknown, b: unknown): boolean { +const SNAPSHOT_UNSET = Symbol("snapshotUnset"); + +function isShallowEqual({ + a, + b, +}: { + a: unknown; + b: unknown; +}): boolean { if (Object.is(a, b)) return true; if (!Array.isArray(a) || !Array.isArray(b)) return false; if (a.length !== b.length) return false; @@ -16,10 +24,7 @@ export function useEditor<T>( selector?: (editor: EditorCore) => T, ): EditorCore | T { const editor = useMemo(() => EditorCore.getInstance(), []); - const selectorRef = useRef(selector); - selectorRef.current = selector; - - const snapshotCacheRef = useRef<unknown>(undefined); + const snapshotCacheRef = useRef<T | typeof SNAPSHOT_UNSET>(SNAPSHOT_UNSET); const subscribeAll = useCallback( (onChange: () => void) => { @@ -43,18 +48,29 @@ export function useEditor<T>( [editor], ); - const getSnapshot = useCallback(() => { - const next = selectorRef.current ? selectorRef.current(editor) : editor; - if (isShallowEqual(snapshotCacheRef.current, next)) { + const getSnapshot = useCallback((): EditorCore | T => { + if (!selector) { + return editor; + } + + const next = selector(editor); + if ( + snapshotCacheRef.current !== SNAPSHOT_UNSET && + isShallowEqual({ + a: snapshotCacheRef.current, + b: next, + }) + ) { return snapshotCacheRef.current; } + snapshotCacheRef.current = next; return next; - }, [editor]); + }, [editor, selector]); return useSyncExternalStore( selector ? subscribeAll : subscribeNone, getSnapshot, getSnapshot, - ) as EditorCore | T; + ); } diff --git a/apps/web/src/effects/definitions/index.ts b/apps/web/src/effects/definitions/index.ts index edc8c5e2..511df042 100644 --- a/apps/web/src/effects/definitions/index.ts +++ b/apps/web/src/effects/definitions/index.ts @@ -8,6 +8,9 @@ export function registerDefaultEffects(): void { if (effectsRegistry.has(definition.type)) { continue; } - effectsRegistry.register(definition.type, definition); + effectsRegistry.register({ + key: definition.type, + definition, + }); } } diff --git a/apps/web/src/fps/utils.ts b/apps/web/src/fps/utils.ts index e30e263a..c2a57fbb 100644 --- a/apps/web/src/fps/utils.ts +++ b/apps/web/src/fps/utils.ts @@ -22,7 +22,13 @@ export function frameRateToFloat(rate: FrameRate): number { return rate.numerator / rate.denominator; } -export function frameRatesEqual(a: FrameRate, b: FrameRate): boolean { +export function frameRatesEqual({ + a, + b, +}: { + a: FrameRate; + b: FrameRate; +}): boolean { return a.numerator === b.numerator && a.denominator === b.denominator; } @@ -38,14 +44,17 @@ export function floatToFrameRate(fps: number): FrameRate { const ARBITRARY_DENOMINATOR = 1_000_000; const scaledNumerator = Math.round(fps * ARBITRARY_DENOMINATOR); - const divisor = gcd(scaledNumerator, ARBITRARY_DENOMINATOR); + const divisor = gcd({ + left: scaledNumerator, + right: ARBITRARY_DENOMINATOR, + }); return { numerator: scaledNumerator / divisor, denominator: ARBITRARY_DENOMINATOR / divisor, }; } -function gcd(left: number, right: number): number { +function gcd({ left, right }: { left: number; right: number }): number { let a = Math.abs(left); let b = Math.abs(right); while (b !== 0) { diff --git a/apps/web/src/graphics/components/graphic-tab.tsx b/apps/web/src/graphics/components/graphic-tab.tsx index 13cd7b84..31c4a86d 100644 --- a/apps/web/src/graphics/components/graphic-tab.tsx +++ b/apps/web/src/graphics/components/graphic-tab.tsx @@ -1,239 +1,239 @@ -"use client";
-
-import { useRef } from "react";
-import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead";
-import {
- useKeyframedParamProperty,
- type KeyframedParamPropertyResult,
-} from "@/components/editor/panels/properties/hooks/use-keyframed-param-property";
-import type { ParamDefinition, ParamValues } from "@/params";
-import type { GraphicElement } from "@/timeline";
-import { graphicsRegistry, registerDefaultGraphics, resolveGraphicElementParamsAtTime } from "@/graphics";
-import { useElementPreview } from "@/timeline/hooks/use-element-preview";
-import { useEditor } from "@/editor/use-editor";
-import {
- Section,
- SectionContent,
- SectionFields,
- SectionHeader,
- SectionTitle,
-} from "@/components/section";
-import { PropertyParamField } from "@/components/editor/panels/properties/components/property-param-field";
-import { Button } from "@/components/ui/button";
-import { HugeiconsIcon } from "@hugeicons/react";
-import { MinusSignIcon, PlusSignIcon } from "@hugeicons/core-free-icons";
-import { cn } from "@/utils/ui";
-import type { MediaTime } from "@/wasm";
-
-registerDefaultGraphics();
-
-const DEFAULT_STROKE_WIDTH = 2;
-
-export function GraphicTab({
- element,
- trackId,
-}: {
- element: GraphicElement;
- trackId: string;
-}) {
- const definition = graphicsRegistry.get(element.definitionId);
- const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({
- startTime: element.startTime,
- duration: element.duration,
- });
- const { renderElement } = useElementPreview({
- trackId,
- elementId: element.id,
- fallback: element,
- });
-
- const liveElement = renderElement as GraphicElement;
- const resolvedParams = resolveGraphicElementParamsAtTime({
- element: liveElement,
- localTime,
- });
-
- const shapeParams = definition.params.filter((p) => p.group !== "stroke");
- const hasStrokeParams = definition.params.some((p) => p.group === "stroke");
-
- return (
- <div className="flex flex-col">
- <Section collapsible sectionKey={`${element.id}:graphic`}>
- <SectionHeader>
- <SectionTitle>{definition.name}</SectionTitle>
- </SectionHeader>
- <SectionContent>
- <SectionFields>
- {shapeParams.map((param) => (
- <AnimatedGraphicParamField
- key={param.key}
- param={param}
- trackId={trackId}
- element={liveElement}
- localTime={localTime}
- isPlayheadWithinElementRange={isPlayheadWithinElementRange}
- resolvedParams={resolvedParams}
- />
- ))}
- </SectionFields>
- </SectionContent>
- </Section>
- {hasStrokeParams && <StrokeSection element={element} trackId={trackId} />}
- </div>
- );
-}
-
-function StrokeSection({
- element,
- trackId,
-}: {
- element: GraphicElement;
- trackId: string;
-}) {
- const editor = useEditor();
- const definition = graphicsRegistry.get(element.definitionId);
- const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({
- startTime: element.startTime,
- duration: element.duration,
- });
- const { renderElement } = useElementPreview({
- trackId,
- elementId: element.id,
- fallback: element,
- });
-
- const liveElement = renderElement as GraphicElement;
- const resolvedParams = resolveGraphicElementParamsAtTime({
- element: liveElement,
- localTime,
- });
- const strokeParams = definition.params.filter((p) => p.group === "stroke");
- const lastStrokeWidth = useRef(DEFAULT_STROKE_WIDTH);
- const isStrokeEnabled = Number(element.params.strokeWidth ?? 0) > 0;
-
- const toggleStroke = () => {
- if (isStrokeEnabled) {
- lastStrokeWidth.current = Number(
- element.params.strokeWidth ?? DEFAULT_STROKE_WIDTH,
- );
- editor.timeline.updateElements({
- updates: [
- {
- trackId,
- elementId: element.id,
- patch: { params: { ...element.params, strokeWidth: 0 } },
- },
- ],
- });
- } else {
- editor.timeline.updateElements({
- updates: [
- {
- trackId,
- elementId: element.id,
- patch: {
- params: {
- ...element.params,
- strokeWidth: lastStrokeWidth.current,
- },
- },
- },
- ],
- });
- }
- };
-
- return (
- <Section
- collapsible
- defaultOpen={isStrokeEnabled}
- sectionKey={`${element.id}:stroke`}
- >
- <SectionHeader
- trailing={
- <Button
- variant="ghost"
- size="icon"
- onClick={(event) => {
- event.stopPropagation();
- toggleStroke();
- }}
- >
- <HugeiconsIcon
- icon={isStrokeEnabled ? MinusSignIcon : PlusSignIcon}
- strokeWidth={1}
- />
- </Button>
- }
- >
- <SectionTitle>Stroke</SectionTitle>
- </SectionHeader>
- <SectionContent
- className={cn(!isStrokeEnabled && "pointer-events-none opacity-50")}
- >
- <SectionFields>
- {strokeParams.map((param) => (
- <AnimatedGraphicParamField
- key={param.key}
- param={param}
- trackId={trackId}
- element={liveElement}
- localTime={localTime}
- isPlayheadWithinElementRange={isPlayheadWithinElementRange}
- resolvedParams={resolvedParams}
- />
- ))}
- </SectionFields>
- </SectionContent>
- </Section>
- );
-}
-
-function AnimatedGraphicParamField({
- param,
- trackId,
- element,
- localTime,
- isPlayheadWithinElementRange,
- resolvedParams,
-}: {
- key?: string;
- param: ParamDefinition;
- trackId: string;
- element: GraphicElement;
- localTime: MediaTime;
- isPlayheadWithinElementRange: boolean;
- resolvedParams: ParamValues;
-}) {
- const animatedParam: KeyframedParamPropertyResult = useKeyframedParamProperty(
- {
- param,
- trackId,
- elementId: element.id,
- animations: element.animations,
- localTime,
- isPlayheadWithinElementRange,
- resolvedValue: resolvedParams[param.key] ?? param.default,
- buildBaseUpdates: ({ value }) => ({
- params: {
- ...element.params,
- [param.key]: value,
- },
- }),
- },
- );
-
- return (
- <PropertyParamField
- param={param}
- value={resolvedParams[param.key] ?? param.default}
- onPreview={animatedParam.onPreview}
- onCommit={animatedParam.onCommit}
- keyframe={{
- isActive: animatedParam.isKeyframedAtTime,
- isDisabled: !isPlayheadWithinElementRange,
- onToggle: animatedParam.toggleKeyframe,
- }}
- />
- );
-}
+"use client"; + +import { useRef } from "react"; +import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; +import { + useKeyframedParamProperty, + type KeyframedParamPropertyResult, +} from "@/components/editor/panels/properties/hooks/use-keyframed-param-property"; +import type { ParamDefinition, ParamValues } from "@/params"; +import type { GraphicElement } from "@/timeline"; +import { graphicsRegistry, registerDefaultGraphics, resolveGraphicElementParamsAtTime } from "@/graphics"; +import { useElementPreview } from "@/timeline/hooks/use-element-preview"; +import { useEditor } from "@/editor/use-editor"; +import { + Section, + SectionContent, + SectionFields, + SectionHeader, + SectionTitle, +} from "@/components/section"; +import { PropertyParamField } from "@/components/editor/panels/properties/components/property-param-field"; +import { Button } from "@/components/ui/button"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { MinusSignIcon, PlusSignIcon } from "@hugeicons/core-free-icons"; +import { cn } from "@/utils/ui"; +import type { MediaTime } from "@/wasm"; + +registerDefaultGraphics(); + +const DEFAULT_STROKE_WIDTH = 2; + +export function GraphicTab({ + element, + trackId, +}: { + element: GraphicElement; + trackId: string; +}) { + const definition = graphicsRegistry.get(element.definitionId); + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const { renderElement } = useElementPreview({ + trackId, + elementId: element.id, + fallback: element, + }); + + const liveElement = renderElement as GraphicElement; + const resolvedParams = resolveGraphicElementParamsAtTime({ + element: liveElement, + localTime, + }); + + const shapeParams = definition.params.filter((p) => p.group !== "stroke"); + const hasStrokeParams = definition.params.some((p) => p.group === "stroke"); + + return ( + <div className="flex flex-col"> + <Section collapsible sectionKey={`${element.id}:graphic`}> + <SectionHeader> + <SectionTitle>{definition.name}</SectionTitle> + </SectionHeader> + <SectionContent> + <SectionFields> + {shapeParams.map((param) => ( + <AnimatedGraphicParamField + key={param.key} + param={param} + trackId={trackId} + element={liveElement} + localTime={localTime} + isPlayheadWithinElementRange={isPlayheadWithinElementRange} + resolvedParams={resolvedParams} + /> + ))} + </SectionFields> + </SectionContent> + </Section> + {hasStrokeParams && <StrokeSection element={element} trackId={trackId} />} + </div> + ); +} + +function StrokeSection({ + element, + trackId, +}: { + element: GraphicElement; + trackId: string; +}) { + const editor = useEditor(); + const definition = graphicsRegistry.get(element.definitionId); + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const { renderElement } = useElementPreview({ + trackId, + elementId: element.id, + fallback: element, + }); + + const liveElement = renderElement as GraphicElement; + const resolvedParams = resolveGraphicElementParamsAtTime({ + element: liveElement, + localTime, + }); + const strokeParams = definition.params.filter((p) => p.group === "stroke"); + const lastStrokeWidth = useRef(DEFAULT_STROKE_WIDTH); + const isStrokeEnabled = Number(element.params.strokeWidth ?? 0) > 0; + + const toggleStroke = () => { + if (isStrokeEnabled) { + lastStrokeWidth.current = Number( + element.params.strokeWidth ?? DEFAULT_STROKE_WIDTH, + ); + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + patch: { params: { ...element.params, strokeWidth: 0 } }, + }, + ], + }); + } else { + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + patch: { + params: { + ...element.params, + strokeWidth: lastStrokeWidth.current, + }, + }, + }, + ], + }); + } + }; + + return ( + <Section + collapsible + defaultOpen={isStrokeEnabled} + sectionKey={`${element.id}:stroke`} + > + <SectionHeader + trailing={ + <Button + variant="ghost" + size="icon" + onClick={(event) => { + event.stopPropagation(); + toggleStroke(); + }} + > + <HugeiconsIcon + icon={isStrokeEnabled ? MinusSignIcon : PlusSignIcon} + strokeWidth={1} + /> + </Button> + } + > + <SectionTitle>Stroke</SectionTitle> + </SectionHeader> + <SectionContent + className={cn(!isStrokeEnabled && "pointer-events-none opacity-50")} + > + <SectionFields> + {strokeParams.map((param) => ( + <AnimatedGraphicParamField + key={param.key} + param={param} + trackId={trackId} + element={liveElement} + localTime={localTime} + isPlayheadWithinElementRange={isPlayheadWithinElementRange} + resolvedParams={resolvedParams} + /> + ))} + </SectionFields> + </SectionContent> + </Section> + ); +} + +function AnimatedGraphicParamField({ + param, + trackId, + element, + localTime, + isPlayheadWithinElementRange, + resolvedParams, +}: { + key?: string; + param: ParamDefinition; + trackId: string; + element: GraphicElement; + localTime: MediaTime; + isPlayheadWithinElementRange: boolean; + resolvedParams: ParamValues; +}) { + const animatedParam: KeyframedParamPropertyResult = useKeyframedParamProperty( + { + param, + trackId, + elementId: element.id, + animations: element.animations, + localTime, + isPlayheadWithinElementRange, + resolvedValue: resolvedParams[param.key] ?? param.default, + buildBaseUpdates: ({ value }) => ({ + params: { + ...element.params, + [param.key]: value, + }, + }), + }, + ); + + return ( + <PropertyParamField + param={param} + value={resolvedParams[param.key] ?? param.default} + onPreview={animatedParam.onPreview} + onCommit={animatedParam.onCommit} + keyframe={{ + isActive: animatedParam.isKeyframedAtTime, + isDisabled: !isPlayheadWithinElementRange, + onToggle: animatedParam.toggleKeyframe, + }} + /> + ); +} diff --git a/apps/web/src/graphics/definitions/index.ts b/apps/web/src/graphics/definitions/index.ts index 6fd9e446..846a9983 100644 --- a/apps/web/src/graphics/definitions/index.ts +++ b/apps/web/src/graphics/definitions/index.ts @@ -16,7 +16,10 @@ export function registerDefaultGraphics(): void { if (graphicsRegistry.has(definition.id)) {
continue;
}
- graphicsRegistry.register(definition.id, definition);
+ graphicsRegistry.register({
+ key: definition.id,
+ definition,
+ });
}
}
diff --git a/apps/web/src/graphics/index.ts b/apps/web/src/graphics/index.ts index 3f277d40..7b8d905e 100644 --- a/apps/web/src/graphics/index.ts +++ b/apps/web/src/graphics/index.ts @@ -1,149 +1,155 @@ -import { resolveGraphicParamsAtTime } from "@/animation";
-import type { ElementAnimations } from "@/animation/types";
-import { buildDefaultParamValues } from "@/params/registry";
-import type { ParamValues } from "@/params";
-import { graphicsRegistry } from "./registry";
-import {
- registerDefaultGraphics,
- ellipseGraphicDefinition,
- polygonGraphicDefinition,
- rectangleGraphicDefinition,
- starGraphicDefinition,
-} from "./definitions";
-import {
- DEFAULT_GRAPHIC_SOURCE_SIZE,
- type GraphicInstance,
- type GraphicDefinition,
-} from "./types";
-
-const graphicPreviewUrlCache = new Map<string, string>();
-
-const FALLBACK_CORNER_RADIUS_RATIO = 0.2;
-const FALLBACK_FILL_OPACITY = 0.08;
-const FALLBACK_MIN_FONT_SIZE = 12;
-const FALLBACK_FONT_SIZE_RATIO = 0.15;
-
-function buildFallbackPreviewUrl({
- name,
- size,
-}: {
- name: string;
- size: number;
-}): string {
- const svg = `
- <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
- <rect width="${size}" height="${size}" rx="${size * FALLBACK_CORNER_RADIUS_RATIO}" fill="white" fill-opacity="${FALLBACK_FILL_OPACITY}" />
- <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="white" font-size="${Math.max(FALLBACK_MIN_FONT_SIZE, size * FALLBACK_FONT_SIZE_RATIO)}" font-family="sans-serif">${name}</text>
- </svg>
- `;
- return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
-}
-
-export function getGraphicDefinition({
- definitionId,
-}: {
- definitionId: string;
-}): GraphicDefinition {
- registerDefaultGraphics();
- return graphicsRegistry.get(definitionId);
-}
-
-export function buildDefaultGraphicInstance({
- definitionId,
-}: {
- definitionId: string;
-}): GraphicInstance {
- const definition = getGraphicDefinition({ definitionId });
- return {
- definitionId,
- params: buildDefaultParamValues(definition.params),
- };
-}
-
-export function resolveGraphicParams(
- definition: GraphicDefinition,
- params?: ParamValues,
-): ParamValues {
- return {
- ...buildDefaultParamValues(definition.params),
- ...(params ?? {}),
- };
-}
-
-export function resolveGraphicElementParamsAtTime({
- element,
- localTime,
-}: {
- element: {
- definitionId: string;
- params: ParamValues;
- animations?: ElementAnimations;
- };
- localTime: number;
-}): ParamValues {
- const definition = getGraphicDefinition({
- definitionId: element.definitionId,
- });
- return resolveGraphicParamsAtTime({
- params: resolveGraphicParams(definition, element.params),
- definitions: definition.params,
- animations: element.animations,
- localTime,
- });
-}
-
-export function buildGraphicPreviewUrl({
- definitionId,
- params,
- size = DEFAULT_GRAPHIC_SOURCE_SIZE,
-}: {
- definitionId: string;
- params?: ParamValues;
- size?: number;
-}): string {
- const definition = getGraphicDefinition({ definitionId });
- const resolvedParams = resolveGraphicParams(definition, params);
- const cacheKey = JSON.stringify({ definitionId, resolvedParams, size });
- const cachedUrl = graphicPreviewUrlCache.get(cacheKey);
- if (cachedUrl) {
- return cachedUrl;
- }
-
- if (typeof document === "undefined") {
- return buildFallbackPreviewUrl({ name: definition.name, size });
- }
-
- const canvas = document.createElement("canvas");
- canvas.width = size;
- canvas.height = size;
- const ctx = canvas.getContext("2d");
- if (!ctx) {
- return buildFallbackPreviewUrl({ name: definition.name, size });
- }
-
- definition.render({
- ctx,
- params: resolvedParams,
- width: size,
- height: size,
- });
-
- const previewUrl = canvas.toDataURL("image/png");
- graphicPreviewUrlCache.set(cacheKey, previewUrl);
- return previewUrl;
-}
-
-export {
- DEFAULT_GRAPHIC_SOURCE_SIZE,
- ellipseGraphicDefinition,
- graphicsRegistry,
- polygonGraphicDefinition,
- rectangleGraphicDefinition,
- registerDefaultGraphics,
- starGraphicDefinition,
-};
-export type {
- GraphicDefinition,
- GraphicInstance,
- GraphicRenderContext,
-} from "./types";
+import { resolveGraphicParamsAtTime } from "@/animation"; +import type { ElementAnimations } from "@/animation/types"; +import { buildDefaultParamValues } from "@/params/registry"; +import type { ParamValues } from "@/params"; +import { graphicsRegistry } from "./registry"; +import { + registerDefaultGraphics, + ellipseGraphicDefinition, + polygonGraphicDefinition, + rectangleGraphicDefinition, + starGraphicDefinition, +} from "./definitions"; +import { + DEFAULT_GRAPHIC_SOURCE_SIZE, + type GraphicInstance, + type GraphicDefinition, +} from "./types"; + +const graphicPreviewUrlCache = new Map<string, string>(); + +const FALLBACK_CORNER_RADIUS_RATIO = 0.2; +const FALLBACK_FILL_OPACITY = 0.08; +const FALLBACK_MIN_FONT_SIZE = 12; +const FALLBACK_FONT_SIZE_RATIO = 0.15; + +function buildFallbackPreviewUrl({ + name, + size, +}: { + name: string; + size: number; +}): string { + const svg = ` + <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}"> + <rect width="${size}" height="${size}" rx="${size * FALLBACK_CORNER_RADIUS_RATIO}" fill="white" fill-opacity="${FALLBACK_FILL_OPACITY}" /> + <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="white" font-size="${Math.max(FALLBACK_MIN_FONT_SIZE, size * FALLBACK_FONT_SIZE_RATIO)}" font-family="sans-serif">${name}</text> + </svg> + `; + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; +} + +export function getGraphicDefinition({ + definitionId, +}: { + definitionId: string; +}): GraphicDefinition { + registerDefaultGraphics(); + return graphicsRegistry.get(definitionId); +} + +export function buildDefaultGraphicInstance({ + definitionId, +}: { + definitionId: string; +}): GraphicInstance { + const definition = getGraphicDefinition({ definitionId }); + return { + definitionId, + params: buildDefaultParamValues(definition.params), + }; +} + +export function resolveGraphicParams({ + definition, + params, +}: { + definition: GraphicDefinition; + params?: ParamValues; +}): ParamValues { + return { + ...buildDefaultParamValues(definition.params), + ...(params ?? {}), + }; +} + +export function resolveGraphicElementParamsAtTime({ + element, + localTime, +}: { + element: { + definitionId: string; + params: ParamValues; + animations?: ElementAnimations; + }; + localTime: number; +}): ParamValues { + const definition = getGraphicDefinition({ + definitionId: element.definitionId, + }); + return resolveGraphicParamsAtTime({ + params: resolveGraphicParams({ + definition, + params: element.params, + }), + definitions: definition.params, + animations: element.animations, + localTime, + }); +} + +export function buildGraphicPreviewUrl({ + definitionId, + params, + size = DEFAULT_GRAPHIC_SOURCE_SIZE, +}: { + definitionId: string; + params?: ParamValues; + size?: number; +}): string { + const definition = getGraphicDefinition({ definitionId }); + const resolvedParams = resolveGraphicParams({ definition, params }); + const cacheKey = JSON.stringify({ definitionId, resolvedParams, size }); + const cachedUrl = graphicPreviewUrlCache.get(cacheKey); + if (cachedUrl) { + return cachedUrl; + } + + if (typeof document === "undefined") { + return buildFallbackPreviewUrl({ name: definition.name, size }); + } + + const canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext("2d"); + if (!ctx) { + return buildFallbackPreviewUrl({ name: definition.name, size }); + } + + definition.render({ + ctx, + params: resolvedParams, + width: size, + height: size, + }); + + const previewUrl = canvas.toDataURL("image/png"); + graphicPreviewUrlCache.set(cacheKey, previewUrl); + return previewUrl; +} + +export { + DEFAULT_GRAPHIC_SOURCE_SIZE, + ellipseGraphicDefinition, + graphicsRegistry, + polygonGraphicDefinition, + rectangleGraphicDefinition, + registerDefaultGraphics, + starGraphicDefinition, +}; +export type { + GraphicDefinition, + GraphicInstance, + GraphicRenderContext, +} from "./types"; diff --git a/apps/web/src/hooks/use-committed-ref.ts b/apps/web/src/hooks/use-committed-ref.ts new file mode 100644 index 00000000..ff064985 --- /dev/null +++ b/apps/web/src/hooks/use-committed-ref.ts @@ -0,0 +1,11 @@ +import { useLayoutEffect, useRef } from "react"; + +export function useCommittedRef<T>(value: T) { + const ref = useRef(value); + + useLayoutEffect(() => { + ref.current = value; + }, [value]); + + return ref; +} diff --git a/apps/web/src/hooks/use-focus-lock.ts b/apps/web/src/hooks/use-focus-lock.ts index 236d2623..86092aec 100644 --- a/apps/web/src/hooks/use-focus-lock.ts +++ b/apps/web/src/hooks/use-focus-lock.ts @@ -1,4 +1,5 @@ import { useEffect, useRef } from "react";
+import { useCommittedRef } from "@/hooks/use-committed-ref";
type FocusLockCursor = "text" | "default" | "pointer" | "crosshair";
@@ -37,8 +38,7 @@ export function useFocusLock<T extends HTMLElement = HTMLElement>({ allowSelector?: string;
}) {
const containerRef = useRef<T>(null);
- const onDismissRef = useRef(onDismiss);
- onDismissRef.current = onDismiss;
+ const onDismissRef = useCommittedRef(onDismiss);
useEffect(() => {
if (!isActive) return;
@@ -53,10 +53,13 @@ export function useFocusLock<T extends HTMLElement = HTMLElement>({ const handleOutsidePointerDown = (event: PointerEvent) => {
if (event.button !== 0) return;
- if (container.contains(event.target as Node)) return;
+ const target = event.target;
+ if (target instanceof Node && container.contains(target)) return;
- const target = event.target as Element | null;
- const isAllowedTarget = allowSelector && target?.closest(allowSelector);
+ const isAllowedTarget =
+ allowSelector &&
+ target instanceof Element &&
+ target.closest(allowSelector);
if (isAllowedTarget) return;
onDismissRef.current();
@@ -73,7 +76,7 @@ export function useFocusLock<T extends HTMLElement = HTMLElement>({ container.removeAttribute(DATA_ATTR);
focusLockStyle.remove();
};
- }, [isActive, cursor, allowSelector]);
+ }, [isActive, cursor, allowSelector, onDismissRef]);
return { containerRef };
}
diff --git a/apps/web/src/masks/custom-path.ts b/apps/web/src/masks/custom-path.ts index 0349df63..b35702f6 100644 --- a/apps/web/src/masks/custom-path.ts +++ b/apps/web/src/masks/custom-path.ts @@ -330,13 +330,27 @@ function clampUnit(value: number): number { return Math.min(1, Math.max(0, value)); } -function getDistanceSquared(a: CanvasPoint, b: CanvasPoint): number { +function getDistanceSquared({ + a, + b, +}: { + a: CanvasPoint; + b: CanvasPoint; +}): number { const dx = a.x - b.x; const dy = a.y - b.y; return dx * dx + dy * dy; } -function lerpPoint(a: CanvasPoint, b: CanvasPoint, t: number): CanvasPoint { +function lerpPoint({ + a, + b, + t, +}: { + a: CanvasPoint; + b: CanvasPoint; + t: number; +}): CanvasPoint { return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t, @@ -483,7 +497,10 @@ export function findClosestPointOnCustomMaskSegment({ const sampleCount = 24; let bestT = 0; - let bestDistanceSquared = getDistanceSquared(canvasPoint, segment.start); + let bestDistanceSquared = getDistanceSquared({ + a: canvasPoint, + b: segment.start, + }); for (let step = 0; step <= sampleCount; step++) { const t = step / sampleCount; @@ -494,7 +511,7 @@ export function findClosestPointOnCustomMaskSegment({ p3: segment.end, t, }); - const distanceSquared = getDistanceSquared(canvasPoint, point); + const distanceSquared = getDistanceSquared({ a: canvasPoint, b: point }); if (distanceSquared < bestDistanceSquared) { bestDistanceSquared = distanceSquared; bestT = t; @@ -516,7 +533,10 @@ export function findClosestPointOnCustomMaskSegment({ }), })); for (const candidate of candidates) { - const distanceSquared = getDistanceSquared(canvasPoint, candidate.point); + const distanceSquared = getDistanceSquared({ + a: canvasPoint, + b: candidate.point, + }); if (distanceSquared < bestDistanceSquared) { bestDistanceSquared = distanceSquared; bestT = candidate.t; @@ -573,12 +593,12 @@ export function insertPointIntoCustomMaskSegment({ y: endPoint.y + endPoint.inY, }; const p3 = { x: endPoint.x, y: endPoint.y }; - const p01 = lerpPoint(p0, p1, clampedT); - const p12 = lerpPoint(p1, p2, clampedT); - const p23 = lerpPoint(p2, p3, clampedT); - const p012 = lerpPoint(p01, p12, clampedT); - const p123 = lerpPoint(p12, p23, clampedT); - const splitPoint = lerpPoint(p012, p123, clampedT); + const p01 = lerpPoint({ a: p0, b: p1, t: clampedT }); + const p12 = lerpPoint({ a: p1, b: p2, t: clampedT }); + const p23 = lerpPoint({ a: p2, b: p3, t: clampedT }); + const p012 = lerpPoint({ a: p01, b: p12, t: clampedT }); + const p123 = lerpPoint({ a: p12, b: p23, t: clampedT }); + const splitPoint = lerpPoint({ a: p012, b: p123, t: clampedT }); const nextPoints = [...points]; nextPoints[indices.startIndex] = { diff --git a/apps/web/src/masks/definitions/split.ts b/apps/web/src/masks/definitions/split.ts index d50a2571..d8fdc789 100644 --- a/apps/web/src/masks/definitions/split.ts +++ b/apps/web/src/masks/definitions/split.ts @@ -53,10 +53,13 @@ function splitLineGeometry({ return { normalX, normalY, lineX, lineY }; } -function pointsEqual( - a: { x: number; y: number }, - b: { x: number; y: number }, -): boolean { +function pointsEqual({ + a, + b, +}: { + a: { x: number; y: number }; + b: { x: number; y: number }; +}): boolean { return ( Math.abs(a.x - b.x) <= INTERSECTION_EPSILON && Math.abs(a.y - b.y) <= INTERSECTION_EPSILON @@ -101,7 +104,7 @@ export function getSplitMaskStrokeSegment({ y2, }); - if (!hit || intersections.some((point) => pointsEqual(point, hit))) { + if (!hit || intersections.some((point) => pointsEqual({ a: point, b: hit }))) { continue; } @@ -307,13 +310,18 @@ export const splitMaskDefinition: MaskDefinition<SplitMaskParams> = { [0, height, 0, 0], ]; - const isInsideHalfPlane = (x: number, y: number) => - halfPlaneSign({ lineX, lineY, normalX, normalY, x, y }) >= 0; + const isInsideHalfPlane = ({ + x, + y, + }: { + x: number; + y: number; + }) => halfPlaneSign({ lineX, lineY, normalX, normalY, x, y }) >= 0; const vertices: [number, number][] = []; for (const [x1, y1, x2, y2] of edges) { - const isVertex1Inside = isInsideHalfPlane(x1, y1); - const isVertex2Inside = isInsideHalfPlane(x2, y2); + const isVertex1Inside = isInsideHalfPlane({ x: x1, y: y1 }); + const isVertex2Inside = isInsideHalfPlane({ x: x2, y: y2 }); if (isVertex1Inside && isVertex2Inside) { vertices.push([x2, y2]); diff --git a/apps/web/src/masks/registry.ts b/apps/web/src/masks/registry.ts index 00b276c8..0bcf1cf0 100644 --- a/apps/web/src/masks/registry.ts +++ b/apps/web/src/masks/registry.ts @@ -115,7 +115,10 @@ export class MasksRegistry extends DefinitionRegistry< }, icon, }; - this.register(definition.type, withBaseParams); + this.register({ + key: definition.type, + definition: withBaseParams, + }); } } diff --git a/apps/web/src/media/use-paste-media.ts b/apps/web/src/media/use-paste-media.ts index 387b2b9a..7dbd4078 100644 --- a/apps/web/src/media/use-paste-media.ts +++ b/apps/web/src/media/use-paste-media.ts @@ -68,10 +68,10 @@ export function usePasteMedia() { const startTime = editor.playback.getCurrentTime(); for (const asset of processedAssets) { - const addMediaCmd = new AddMediaAssetCommand( - activeProject.metadata.id, + const addMediaCmd = new AddMediaAssetCommand({ + projectId: activeProject.metadata.id, asset, - ); + }); const assetId = addMediaCmd.getAssetId(); const duration = asset.duration != null diff --git a/apps/web/src/params/index.ts b/apps/web/src/params/index.ts index 633bfbd0..23085407 100644 --- a/apps/web/src/params/index.ts +++ b/apps/web/src/params/index.ts @@ -1,50 +1,50 @@ -export type ParamValues = Record<string, number | string | boolean>;
-
-export type ParamGroup = "stroke";
-
-interface BaseParamDefinition<TKey extends string = string> {
- key: TKey;
- label: string;
- group?: ParamGroup;
-}
-
-export interface NumberParamDefinition<TKey extends string = string>
- extends BaseParamDefinition<TKey> {
- type: "number";
- default: number;
- min: number;
- max?: number;
- step: number;
- /** When set, min/max/step are in display space. display = stored * displayMultiplier. */
- displayMultiplier?: number;
- /** Show as percentage of max. min/max/step/default stay in stored space. */
- unit?: "percent";
- /** Short label shown as the scrub handle icon in the number field (e.g. "W", "R"). */
- shortLabel?: string;
-}
-
-export interface BooleanParamDefinition<TKey extends string = string>
- extends BaseParamDefinition<TKey> {
- type: "boolean";
- default: boolean;
-}
-
-export interface ColorParamDefinition<TKey extends string = string>
- extends BaseParamDefinition<TKey> {
- type: "color";
- default: string;
-}
-
-export interface SelectParamDefinition<TKey extends string = string>
- extends BaseParamDefinition<TKey> {
- type: "select";
- default: string;
- options: Array<{ value: string; label: string }>;
-}
-
-export type ParamDefinition<TKey extends string = string> =
- | NumberParamDefinition<TKey>
- | BooleanParamDefinition<TKey>
- | ColorParamDefinition<TKey>
- | SelectParamDefinition<TKey>;
-
+export type ParamValues = Record<string, number | string | boolean>; + +export type ParamGroup = "stroke"; + +interface BaseParamDefinition<TKey extends string = string> { + key: TKey; + label: string; + group?: ParamGroup; +} + +export interface NumberParamDefinition<TKey extends string = string> + extends BaseParamDefinition<TKey> { + type: "number"; + default: number; + min: number; + max?: number; + step: number; + /** When set, min/max/step are in display space. display = stored * displayMultiplier. */ + displayMultiplier?: number; + /** Show as percentage of max. min/max/step/default stay in stored space. */ + unit?: "percent"; + /** Short label shown as the scrub handle icon in the number field (e.g. "W", "R"). */ + shortLabel?: string; +} + +export interface BooleanParamDefinition<TKey extends string = string> + extends BaseParamDefinition<TKey> { + type: "boolean"; + default: boolean; +} + +export interface ColorParamDefinition<TKey extends string = string> + extends BaseParamDefinition<TKey> { + type: "color"; + default: string; +} + +export interface SelectParamDefinition<TKey extends string = string> + extends BaseParamDefinition<TKey> { + type: "select"; + default: string; + options: Array<{ value: string; label: string }>; +} + +export type ParamDefinition<TKey extends string = string> = + | NumberParamDefinition<TKey> + | BooleanParamDefinition<TKey> + | ColorParamDefinition<TKey> + | SelectParamDefinition<TKey>; + diff --git a/apps/web/src/params/registry.ts b/apps/web/src/params/registry.ts index d9fcc266..3ce483e4 100644 --- a/apps/web/src/params/registry.ts +++ b/apps/web/src/params/registry.ts @@ -1,40 +1,46 @@ -import type { ParamDefinition, ParamValues } from "@/params";
-
-export function buildDefaultParamValues(
- params: ParamDefinition[],
-): ParamValues {
- const values: ParamValues = {};
- for (const param of params) {
- values[param.key] = param.default;
- }
- return values;
-}
-
-export class DefinitionRegistry<TKey extends string, TDefinition> {
- private definitions = new Map<TKey, TDefinition>();
- private entityName: string;
-
- constructor(entityName: string) {
- this.entityName = entityName;
- }
-
- register(key: TKey, definition: TDefinition): void {
- this.definitions.set(key, definition);
- }
-
- has(key: TKey): boolean {
- return this.definitions.has(key);
- }
-
- get(key: TKey): TDefinition {
- const def = this.definitions.get(key);
- if (!def) {
- throw new Error(`Unknown ${this.entityName}: ${key}`);
- }
- return def;
- }
-
- getAll(): TDefinition[] {
- return Array.from(this.definitions.values());
- }
-}
+import type { ParamDefinition, ParamValues } from "@/params"; + +export function buildDefaultParamValues( + params: ParamDefinition[], +): ParamValues { + const values: ParamValues = {}; + for (const param of params) { + values[param.key] = param.default; + } + return values; +} + +export class DefinitionRegistry<TKey extends string, TDefinition> { + private definitions = new Map<TKey, TDefinition>(); + private entityName: string; + + constructor(entityName: string) { + this.entityName = entityName; + } + + register({ + key, + definition, + }: { + key: TKey; + definition: TDefinition; + }): void { + this.definitions.set(key, definition); + } + + has(key: TKey): boolean { + return this.definitions.has(key); + } + + get(key: TKey): TDefinition { + const def = this.definitions.get(key); + if (!def) { + throw new Error(`Unknown ${this.entityName}: ${key}`); + } + return def; + } + + getAll(): TDefinition[] { + return Array.from(this.definitions.values()); + } +} diff --git a/apps/web/src/preview/components/context-menu.tsx b/apps/web/src/preview/components/context-menu.tsx index fca55d71..a0f1bc5c 100644 --- a/apps/web/src/preview/components/context-menu.tsx +++ b/apps/web/src/preview/components/context-menu.tsx @@ -13,12 +13,12 @@ import { toast } from "sonner"; export function PreviewContextMenu({ onToggleFullscreen, - containerRef, + container, overlayControls, onOverlayVisibilityChange, }: { onToggleFullscreen: () => void; - containerRef: React.RefObject<HTMLElement | null>; + container: HTMLElement | null; overlayControls: PreviewOverlayControl[]; onOverlayVisibilityChange: (params: { overlayId: string; @@ -51,7 +51,7 @@ export function PreviewContextMenu({ }; return ( - <ContextMenuContent className="w-56" container={containerRef.current}> + <ContextMenuContent className="w-56" container={container}> <ContextMenuItem onClick={viewport.fitToScreen} inset> Fit to screen </ContextMenuItem> diff --git a/apps/web/src/preview/components/index.tsx b/apps/web/src/preview/components/index.tsx index e8f5c572..894eec78 100644 --- a/apps/web/src/preview/components/index.tsx +++ b/apps/web/src/preview/components/index.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo, useRef } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import useDeepCompareEffect from "use-deep-compare-effect"; import { useEditor } from "@/editor/use-editor"; import { useRafLoop } from "@/hooks/use-raf-loop"; @@ -68,15 +68,20 @@ export function PreviewPanel({ }) => void; }) { const containerRef = useRef<HTMLDivElement>(null); + const [container, setContainer] = useState<HTMLDivElement | null>(null); const { toggleFullscreen } = useFullscreen({ containerRef }); + const handleContainerRef = useCallback((node: HTMLDivElement | null) => { + containerRef.current = node; + setContainer(node); + }, []); return ( <div - ref={containerRef} + ref={handleContainerRef} className="panel bg-background relative flex size-full min-h-0 min-w-0 flex-col rounded-sm border" > <PreviewCanvas - containerRef={containerRef} + container={container} onToggleFullscreen={toggleFullscreen} overlayControls={overlayControls} overlayInstances={overlayInstances} @@ -117,13 +122,13 @@ function RenderTreeController() { } function PreviewCanvas({ - containerRef, + container, onToggleFullscreen, overlayControls, overlayInstances, onOverlayVisibilityChange, }: { - containerRef: React.RefObject<HTMLElement | null>; + container: HTMLElement | null; onToggleFullscreen: () => void; overlayControls: PreviewOverlayControl[]; overlayInstances: PreviewOverlayInstance[]; @@ -149,6 +154,7 @@ function PreviewCanvas({ viewportRef, viewportWidth: viewportSize.width, }); + const { canPan, panByScreenDelta, scaleZoom } = viewport; const renderer = useMemo(() => { return new CanvasRenderer({ @@ -240,7 +246,7 @@ function PreviewCanvas({ Math.min(Math.abs(pendingZoomDelta), 30); const zoomFactor = Math.exp(-cappedDelta / 300); - viewport.scaleZoom({ factor: zoomFactor }); + scaleZoom({ factor: zoomFactor }); pendingZoomDelta = 0; zoomRafId = null; }); @@ -249,7 +255,7 @@ function PreviewCanvas({ return; } - if (!viewport.canPan) { + if (!canPan) { return; } @@ -263,7 +269,7 @@ function PreviewCanvas({ if (panRafId === null) { panRafId = requestAnimationFrame(() => { - viewport.panByScreenDelta({ + panByScreenDelta({ deltaX: pendingPanDeltaX, deltaY: pendingPanDeltaY, }); @@ -290,7 +296,7 @@ function PreviewCanvas({ cancelAnimationFrame(panRafId); } }; - }, [viewport.canPan, viewport.panByScreenDelta, viewport.scaleZoom]); + }, [canPan, panByScreenDelta, scaleZoom]); return ( <PreviewViewportProvider value={viewport}> @@ -329,7 +335,7 @@ function PreviewCanvas({ </ContextMenuTrigger> <PreviewContextMenu onToggleFullscreen={onToggleFullscreen} - containerRef={containerRef} + container={container} overlayControls={overlayControls} onOverlayVisibilityChange={onOverlayVisibilityChange} /> diff --git a/apps/web/src/preview/components/preview-viewport.tsx b/apps/web/src/preview/components/preview-viewport.tsx index 6dc9f084..be261adc 100644 --- a/apps/web/src/preview/components/preview-viewport.tsx +++ b/apps/web/src/preview/components/preview-viewport.tsx @@ -9,6 +9,7 @@ import { useRef,
useState,
} from "react";
+import { useCommittedRef } from "@/hooks/use-committed-ref";
import {
canvasToOverlay,
getDisplayScale,
@@ -219,8 +220,7 @@ export function usePreviewViewportState({ }));
const [isPanning, setIsPanning] = useState(false);
const panSessionRef = useRef<PanSession | null>(null);
- const centerRef = useRef(center);
- centerRef.current = center;
+ const centerRef = useCommittedRef(center);
const fitScale = useMemo(
() =>
@@ -409,10 +409,10 @@ export function usePreviewViewportState({ pointerId: event.pointerId,
};
setIsPanning(true);
- (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
+ event.currentTarget.setPointerCapture(event.pointerId);
return true;
},
- [zoom],
+ [centerRef, zoom],
);
const handlePanPointerMove = useCallback(
@@ -451,13 +451,9 @@ export function usePreviewViewportState({ }
if (
- (event.currentTarget as HTMLElement).hasPointerCapture(
- panSession.pointerId,
- )
+ event.currentTarget.hasPointerCapture(panSession.pointerId)
) {
- (event.currentTarget as HTMLElement).releasePointerCapture(
- panSession.pointerId,
- );
+ event.currentTarget.releasePointerCapture(panSession.pointerId);
}
panSessionRef.current = null;
diff --git a/apps/web/src/preview/components/text-edit-overlay.tsx b/apps/web/src/preview/components/text-edit-overlay.tsx index 1d599b3b..dd6364d6 100644 --- a/apps/web/src/preview/components/text-edit-overlay.tsx +++ b/apps/web/src/preview/components/text-edit-overlay.tsx @@ -120,7 +120,6 @@ export function TextEditOverlay({ transformOrigin: "center center", }} > - {/* biome-ignore lint/a11y/useSemanticElements: contenteditable required for multiline, IME, paste */} <div ref={divRef} contentEditable diff --git a/apps/web/src/preview/hooks/use-preview-interaction.ts b/apps/web/src/preview/hooks/use-preview-interaction.ts index d093dcf2..b707f1e6 100644 --- a/apps/web/src/preview/hooks/use-preview-interaction.ts +++ b/apps/web/src/preview/hooks/use-preview-interaction.ts @@ -1,5 +1,6 @@ -import { useEffect, useReducer, useRef } from "react"; +import { useEffect, useReducer, useState } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { useShiftKey } from "@/hooks/use-shift-key"; import { usePreviewViewport } from "@/preview/components/preview-viewport"; import type { SnapLine } from "@/preview/preview-snap"; @@ -59,17 +60,10 @@ export function usePreviewInteraction({ onSnapLinesChange, }, }; - - const depsRef = useRef<PreviewInteractionDeps>(deps); - depsRef.current = deps; - - const controllerRef = useRef<PreviewInteractionController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new PreviewInteractionController({ - depsRef: depsRef as PreviewInteractionDepsRef, - }); - } - const controller = controllerRef.current; + const depsRef = useCommittedRef(deps) as PreviewInteractionDepsRef; + const [controller] = useState( + () => new PreviewInteractionController({ depsRef }), + ); const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect( diff --git a/apps/web/src/preview/hooks/use-transform-handles.ts b/apps/web/src/preview/hooks/use-transform-handles.ts index f4b7e306..55d8d8f7 100644 --- a/apps/web/src/preview/hooks/use-transform-handles.ts +++ b/apps/web/src/preview/hooks/use-transform-handles.ts @@ -1,7 +1,8 @@ -import { useEffect, useReducer, useRef } from "react"; +import { useEffect, useReducer, useState } from "react"; import { usePreviewViewport } from "@/preview/components/preview-viewport"; import type { OnSnapLinesChange } from "@/preview/hooks/use-preview-interaction"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { useShiftKey } from "@/hooks/use-shift-key"; import { registerCanceller } from "@/editor/cancel-interaction"; import { @@ -48,15 +49,10 @@ export function useTransformHandles({ onSnapLinesChange, }, }; - - const depsRef = useRef<TransformHandleDeps>(deps); - depsRef.current = deps; - - const controllerRef = useRef<TransformHandleController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new TransformHandleController({ depsRef }); - } - const controller = controllerRef.current; + const depsRef = useCommittedRef(deps); + const [controller] = useState( + () => new TransformHandleController({ depsRef }), + ); const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect(() => controller.subscribe(rerender), [controller]); diff --git a/apps/web/src/rendering/components/blending-tab.tsx b/apps/web/src/rendering/components/blending-tab.tsx index 52d2f051..e69de29b 100644 --- a/apps/web/src/rendering/components/blending-tab.tsx +++ b/apps/web/src/rendering/components/blending-tab.tsx @@ -1,231 +0,0 @@ -import { useEditor } from "@/editor/use-editor"; -import { clamp } from "@/utils/math"; -import { NumberField } from "@/components/ui/number-field"; -import { OcCheckerboardIcon } from "@/components/icons"; -import { Fragment, useRef } from "react"; -import { useMenuPreview } from "@/editor/use-menu-preview"; -import { - Section, - SectionContent, - SectionField, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { - Select, - SelectContent, - SelectItem, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import type { BlendMode } from "@/rendering"; -import type { ElementType } from "@/timeline"; -import type { ElementAnimations } from "@/animation/types"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { RainDropIcon } from "@hugeicons/core-free-icons"; -import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; -import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; -import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; -import { resolveOpacityAtTime } from "@/animation/values"; -import { DEFAULTS } from "@/timeline/defaults"; -import { isPropertyAtDefault } from "./transform-tab"; -import type { MediaTime } from "@/wasm"; - -type BlendingElement = { - id: string; - opacity: number; - type: ElementType; - blendMode?: BlendMode; - startTime: MediaTime; - duration: MediaTime; - animations?: ElementAnimations; -}; - -const BLEND_MODE_GROUPS: { value: BlendMode; label: string }[][] = [ - [{ value: "normal", label: "Normal" }], - [ - { value: "darken", label: "Darken" }, - { value: "multiply", label: "Multiply" }, - { value: "color-burn", label: "Color Burn" }, - ], - [ - { value: "lighten", label: "Lighten" }, - { value: "screen", label: "Screen" }, - { value: "plus-lighter", label: "Plus Lighter" }, - { value: "color-dodge", label: "Color Dodge" }, - ], - [ - { value: "overlay", label: "Overlay" }, - { value: "soft-light", label: "Soft Light" }, - { value: "hard-light", label: "Hard Light" }, - ], - [ - { value: "difference", label: "Difference" }, - { value: "exclusion", label: "Exclusion" }, - ], - [ - { value: "hue", label: "Hue" }, - { value: "saturation", label: "Saturation" }, - { value: "color", label: "Color" }, - { value: "luminosity", label: "Luminosity" }, - ], -]; - -export function BlendingTab({ - element, - trackId, -}: { - element: BlendingElement; - trackId: string; -}) { - const editor = useEditor(); - const isPreviewActive = useEditor((e) => e.timeline.isPreviewActive()); - const blendMode = element.blendMode ?? DEFAULTS.element.blendMode; - const committedBlendModeRef = useRef(blendMode); - if (!isPreviewActive) { - committedBlendModeRef.current = blendMode; - } - - const { - onPointerLeave, - onOpenChange: handleBlendModeOpenChange, - markCommitted, - } = useMenuPreview(); - - const previewBlendMode = ({ value }: { value: BlendMode }) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { blendMode: value } }, - ], - }); - - const commitBlendMode = (value: BlendMode) => { - if (editor.timeline.isPreviewActive()) { - editor.timeline.commitPreview(); - } else { - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - patch: { blendMode: value }, - }, - ], - }); - } - markCommitted(); - }; - - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const resolvedOpacity = resolveOpacityAtTime({ - baseOpacity: element.opacity, - animations: element.animations, - localTime, - }); - - const opacity = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "opacity", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedOpacity * 100).toString(), - parse: (input) => { - const parsed = parseFloat(input); - if (Number.isNaN(parsed)) return null; - return clamp({ value: parsed, min: 0, max: 100 }) / 100; - }, - valueAtPlayhead: resolvedOpacity, - step: 0.01, - buildBaseUpdates: ({ value }) => ({ opacity: value }), - }); - - return ( - <Section collapsible sectionKey={`${element.id}:blending`}> - <SectionHeader> - <SectionTitle>Blending</SectionTitle> - </SectionHeader> - <SectionContent> - <div className="flex items-start gap-2"> - <SectionField - label="Opacity" - className="w-1/2" - beforeLabel={ - <KeyframeToggle - isActive={opacity.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle opacity keyframe" - onToggle={opacity.toggleKeyframe} - /> - } - > - <NumberField - className="w-full" - icon={ - <OcCheckerboardIcon className="size-3.5 text-muted-foreground" /> - } - value={opacity.displayValue} - min={0} - max={100} - onFocus={opacity.onFocus} - onChange={opacity.onChange} - onBlur={opacity.onBlur} - onScrub={opacity.scrubTo} - onScrubEnd={opacity.commitScrub} - onReset={() => - opacity.commitValue({ value: DEFAULTS.element.opacity }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: opacity.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedOpacity, - staticValue: element.opacity, - defaultValue: DEFAULTS.element.opacity, - })} - dragSensitivity="slow" - /> - </SectionField> - <SectionField label="Blend mode" className="w-1/2"> - <Select - value={committedBlendModeRef.current} - onOpenChange={handleBlendModeOpenChange} - onValueChange={commitBlendMode} - > - <SelectTrigger - icon={<HugeiconsIcon icon={RainDropIcon} />} - className="w-full" - > - <SelectValue placeholder="Select blend mode" /> - </SelectTrigger> - <SelectContent className="w-36" onPointerLeave={onPointerLeave}> - {BLEND_MODE_GROUPS.map((group, groupIndex) => ( - <Fragment key={group[0]?.value ?? `group-${groupIndex}`}> - {group.map((option) => ( - <SelectItem - key={option.value} - value={option.value} - onPointerEnter={() => - previewBlendMode({ value: option.value }) - } - > - {option.label} - </SelectItem> - ))} - {groupIndex < BLEND_MODE_GROUPS.length - 1 ? ( - <SelectSeparator /> - ) : null} - </Fragment> - ))} - </SelectContent> - </Select> - </SectionField> - </div> - </SectionContent> - </Section> - ); -} diff --git a/apps/web/src/rendering/components/transform-tab.tsx b/apps/web/src/rendering/components/transform-tab.tsx index f4d5a183..e69de29b 100644 --- a/apps/web/src/rendering/components/transform-tab.tsx +++ b/apps/web/src/rendering/components/transform-tab.tsx @@ -1,462 +0,0 @@ -import { NumberField } from "@/components/ui/number-field"; -import { useEditor } from "@/editor/use-editor"; -import { clamp, isNearlyEqual } from "@/utils/math"; -import type { VisualElement } from "@/timeline"; -import { - Section, - SectionContent, - SectionField, - SectionFields, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { Button } from "@/components/ui/button"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { - ArrowExpandIcon, - Link05Icon, - RotateClockwiseIcon, -} from "@hugeicons/core-free-icons"; -import { - getGroupKeyframesAtTime, - hasGroupKeyframeAtTime, -} from "@/animation"; -import { resolveTransformAtTime } from "@/rendering/animation-values"; -import { DEFAULTS } from "@/timeline/defaults"; -import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; -import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; -import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; -import { usePropertiesStore } from "@/components/editor/panels/properties/stores/properties-store"; - -export function parseNumericInput({ input }: { input: string }): number | null { - const parsed = parseFloat(input); - return Number.isNaN(parsed) ? null : parsed; -} - -export function isPropertyAtDefault({ - hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue, - staticValue, - defaultValue, -}: { - hasAnimatedKeyframes: boolean; - isPlayheadWithinElementRange: boolean; - resolvedValue: number; - staticValue: number; - defaultValue: number; -}): boolean { - if (hasAnimatedKeyframes && isPlayheadWithinElementRange) { - return isNearlyEqual({ - leftValue: resolvedValue, - rightValue: defaultValue, - }); - } - - return staticValue === defaultValue; -} - -export function TransformTab({ - element, - trackId, -}: { - element: VisualElement; - trackId: string; -}) { - const editor = useEditor(); - const isScaleLocked = usePropertiesStore((s) => s.isTransformScaleLocked); - const setTransformScaleLocked = usePropertiesStore( - (s) => s.setTransformScaleLocked, - ); - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const resolvedTransform = resolveTransformAtTime({ - baseTransform: element.transform, - animations: element.animations, - localTime, - }); - - const positionX = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "transform.positionX", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedTransform.position.x).toString(), - parse: (input) => parseNumericInput({ input }), - valueAtPlayhead: resolvedTransform.position.x, - step: 1, - buildBaseUpdates: ({ value }) => ({ - transform: { - ...element.transform, - position: { ...element.transform.position, x: value }, - }, - }), - }); - - const positionY = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "transform.positionY", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedTransform.position.y).toString(), - parse: (input) => parseNumericInput({ input }), - valueAtPlayhead: resolvedTransform.position.y, - step: 1, - buildBaseUpdates: ({ value }) => ({ - transform: { - ...element.transform, - position: { ...element.transform.position, y: value }, - }, - }), - }); - - const parseScale = (input: string) => { - const parsed = parseNumericInput({ input }); - if (parsed === null) return null; - return parsed / 100; - }; - - const scaleX = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "transform.scaleX", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedTransform.scaleX * 100).toString(), - parse: parseScale, - valueAtPlayhead: resolvedTransform.scaleX, - step: 0.01, - buildBaseUpdates: ({ value }) => ({ - transform: { - ...element.transform, - scaleX: value, - ...(isScaleLocked ? { scaleY: value } : {}), - }, - }), - buildAdditionalKeyframes: isScaleLocked - ? ({ value }) => [{ propertyPath: "transform.scaleY", value }] - : undefined, - }); - - const scaleY = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "transform.scaleY", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedTransform.scaleY * 100).toString(), - parse: parseScale, - valueAtPlayhead: resolvedTransform.scaleY, - step: 0.01, - buildBaseUpdates: ({ value }) => ({ - transform: { - ...element.transform, - scaleY: value, - ...(isScaleLocked ? { scaleX: value } : {}), - }, - }), - buildAdditionalKeyframes: isScaleLocked - ? ({ value }) => [{ propertyPath: "transform.scaleX", value }] - : undefined, - }); - - const scaleFieldPropsX = { - value: scaleX.displayValue, - onFocus: scaleX.onFocus, - onChange: scaleX.onChange, - onBlur: scaleX.onBlur, - dragSensitivity: "slow" as const, - onScrub: scaleX.scrubTo, - onScrubEnd: scaleX.commitScrub, - onReset: () => - scaleX.commitValue({ value: DEFAULTS.element.transform.scaleX }), - isDefault: isPropertyAtDefault({ - hasAnimatedKeyframes: scaleX.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedTransform.scaleX, - staticValue: element.transform.scaleX, - defaultValue: DEFAULTS.element.transform.scaleX, - }), - }; - - const scaleFieldPropsY = { - value: scaleY.displayValue, - onFocus: scaleY.onFocus, - onChange: scaleY.onChange, - onBlur: scaleY.onBlur, - dragSensitivity: "slow" as const, - onScrub: scaleY.scrubTo, - onScrubEnd: scaleY.commitScrub, - onReset: () => - scaleY.commitValue({ value: DEFAULTS.element.transform.scaleY }), - isDefault: isPropertyAtDefault({ - hasAnimatedKeyframes: scaleY.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedTransform.scaleY, - staticValue: element.transform.scaleY, - defaultValue: DEFAULTS.element.transform.scaleY, - }), - }; - - const rotation = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "transform.rotate", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedTransform.rotate).toString(), - parse: (input) => { - const parsed = parseNumericInput({ input }); - if (parsed === null) return null; - return clamp({ value: parsed, min: -360, max: 360 }); - }, - valueAtPlayhead: resolvedTransform.rotate, - step: 1, - buildBaseUpdates: ({ value }) => ({ - transform: { - ...element.transform, - rotate: value, - }, - }), - }); - - const hasScaleKeyframe = hasGroupKeyframeAtTime({ - animations: element.animations, - group: "transform.scale", - time: localTime, - }); - - const toggleScaleKeyframe = () => { - if (!isPlayheadWithinElementRange) return; - const existing = getGroupKeyframesAtTime({ - animations: element.animations, - group: "transform.scale", - time: localTime, - }); - if (existing.length > 0) { - editor.timeline.removeKeyframes({ - keyframes: existing.map((ref) => ({ - trackId, - elementId: element.id, - ...ref, - })), - }); - return; - } - editor.timeline.upsertKeyframes({ - keyframes: [ - { - trackId, - elementId: element.id, - propertyPath: "transform.scaleX", - time: localTime, - value: resolvedTransform.scaleX, - }, - { - trackId, - elementId: element.id, - propertyPath: "transform.scaleY", - time: localTime, - value: resolvedTransform.scaleY, - }, - ], - }); - }; - - const scaleLockButton = ( - <Button - type="button" - variant={isScaleLocked ? "secondary" : "ghost"} - size="icon" - aria-pressed={isScaleLocked} - onClick={() => setTransformScaleLocked(!isScaleLocked)} - > - <HugeiconsIcon icon={Link05Icon} /> - </Button> - ); - - return ( - <Section collapsible sectionKey={`${element.id}:transform`}> - <SectionHeader> - <SectionTitle>Transform</SectionTitle> - </SectionHeader> - <SectionContent> - <SectionFields> - <div className="flex items-end gap-2"> - {isScaleLocked ? ( - <> - <SectionField - label="Scale" - className="min-w-0 flex-1" - beforeLabel={ - <KeyframeToggle - isActive={hasScaleKeyframe} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle scale keyframe" - onToggle={toggleScaleKeyframe} - /> - } - > - <NumberField - icon={<HugeiconsIcon icon={ArrowExpandIcon} />} - {...scaleFieldPropsX} - /> - </SectionField> - {scaleLockButton} - </> - ) : ( - <> - <SectionField - label="Width" - className="min-w-0 flex-1" - beforeLabel={ - <KeyframeToggle - isActive={scaleX.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle width scale keyframe" - onToggle={scaleX.toggleKeyframe} - /> - } - > - <NumberField icon="W" {...scaleFieldPropsX} /> - </SectionField> - {scaleLockButton} - <SectionField - label="Height" - className="min-w-0 flex-1" - beforeLabel={ - <KeyframeToggle - isActive={scaleY.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle height scale keyframe" - onToggle={scaleY.toggleKeyframe} - /> - } - > - <NumberField icon="H" {...scaleFieldPropsY} /> - </SectionField> - </> - )} - </div> - <div className="flex items-end gap-2"> - <SectionField - label="X" - className="min-w-0 flex-1" - beforeLabel={ - <KeyframeToggle - isActive={positionX.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle X position keyframe" - onToggle={positionX.toggleKeyframe} - /> - } - > - <NumberField - icon="X" - value={positionX.displayValue} - onFocus={positionX.onFocus} - onChange={positionX.onChange} - onBlur={positionX.onBlur} - onScrub={positionX.scrubTo} - onScrubEnd={positionX.commitScrub} - onReset={() => - positionX.commitValue({ - value: DEFAULTS.element.transform.position.x, - }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: positionX.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedTransform.position.x, - staticValue: element.transform.position.x, - defaultValue: DEFAULTS.element.transform.position.x, - })} - /> - </SectionField> - <SectionField - label="Y" - className="min-w-0 flex-1" - beforeLabel={ - <KeyframeToggle - isActive={positionY.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle Y position keyframe" - onToggle={positionY.toggleKeyframe} - /> - } - > - <NumberField - icon="Y" - value={positionY.displayValue} - onFocus={positionY.onFocus} - onChange={positionY.onChange} - onBlur={positionY.onBlur} - onScrub={positionY.scrubTo} - onScrubEnd={positionY.commitScrub} - onReset={() => - positionY.commitValue({ - value: DEFAULTS.element.transform.position.y, - }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: positionY.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedTransform.position.y, - staticValue: element.transform.position.y, - defaultValue: DEFAULTS.element.transform.position.y, - })} - /> - </SectionField> - </div> - - <SectionField - label="Rotation" - beforeLabel={ - <KeyframeToggle - isActive={rotation.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle rotation keyframe" - onToggle={rotation.toggleKeyframe} - /> - } - > - <div className="flex items-center gap-2"> - <NumberField - icon={<HugeiconsIcon icon={RotateClockwiseIcon} />} - className="flex-none" - value={rotation.displayValue} - onFocus={rotation.onFocus} - onChange={rotation.onChange} - onBlur={rotation.onBlur} - dragSensitivity="slow" - onScrub={rotation.scrubTo} - onScrubEnd={rotation.commitScrub} - onReset={() => - rotation.commitValue({ - value: DEFAULTS.element.transform.rotate, - }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: rotation.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedTransform.rotate, - staticValue: element.transform.rotate, - defaultValue: DEFAULTS.element.transform.rotate, - })} - /> - </div> - </SectionField> - </SectionFields> - </SectionContent> - </Section> - ); -} diff --git a/apps/web/src/selection/hooks/use-box-select.ts b/apps/web/src/selection/hooks/use-box-select.ts index 90a584b5..3bbff14e 100644 --- a/apps/web/src/selection/hooks/use-box-select.ts +++ b/apps/web/src/selection/hooks/use-box-select.ts @@ -4,15 +4,40 @@ import { useCallback, useEffect, useRef, useState } from "react"; import type { BoxSelectionSnapshot, ResolveIntersections, + SelectionBoxBounds, } from "@/selection/types"; interface SelectionBoxState<TId> extends BoxSelectionSnapshot<TId> { startPos: { x: number; y: number }; currentPos: { x: number; y: number }; + bounds: SelectionBoxBounds | null; isActive: boolean; isAdditive: boolean; } +function getSelectionBoxBounds({ + container, + startPos, + currentPos, +}: { + container: HTMLElement; + startPos: { x: number; y: number }; + currentPos: { x: number; y: number }; +}): SelectionBoxBounds { + const containerRect = container.getBoundingClientRect(); + const startX = startPos.x - containerRect.left; + const startY = startPos.y - containerRect.top; + const currentX = currentPos.x - containerRect.left; + const currentY = currentPos.y - containerRect.top; + + return { + left: Math.min(startX, currentX), + top: Math.min(startY, currentY), + width: Math.abs(currentX - startX), + height: Math.abs(currentY - startY), + }; +} + export function useBoxSelect<TId>({ containerRef, resolveIntersections, @@ -40,33 +65,43 @@ export function useBoxSelect<TId>({ const [selectionBox, setSelectionBox] = useState<SelectionBoxState<TId> | null>(null); const justFinishedSelectingRef = useRef(false); - const shouldStartSelectionCheck = shouldStartSelection ?? (() => true); - const getIsAdditiveSelectionCheck = - getIsAdditiveSelection ?? - ((event: React.MouseEvent<Element>) => event.ctrlKey || event.metaKey); const handleMouseDown = useCallback( (event: React.MouseEvent<Element>) => { - const canStartSelection = shouldStartSelectionCheck(event); + const canStartSelection = shouldStartSelection + ? shouldStartSelection(event) + : true; if (!isEnabled || event.button !== 0 || !canStartSelection) { return; } + const startPos = { x: event.clientX, y: event.clientY }; + const container = containerRef.current; setSelectionBox({ - startPos: { x: event.clientX, y: event.clientY }, - currentPos: { x: event.clientX, y: event.clientY }, + startPos, + currentPos: startPos, + bounds: container + ? getSelectionBoxBounds({ + container, + startPos, + currentPos: startPos, + }) + : null, isActive: false, - isAdditive: getIsAdditiveSelectionCheck(event), + isAdditive: getIsAdditiveSelection + ? getIsAdditiveSelection(event) + : event.ctrlKey || event.metaKey, initialSelectedIds: selectedIds, initialAnchorId: anchorId, }); }, [ anchorId, - getIsAdditiveSelectionCheck, + containerRef, + getIsAdditiveSelection, isEnabled, selectedIds, - shouldStartSelectionCheck, + shouldStartSelection, ], ); @@ -98,11 +133,20 @@ export function useBoxSelect<TId>({ } const handleMouseMove = ({ clientX, clientY }: MouseEvent) => { + const currentPos = { x: clientX, y: clientY }; const deltaX = Math.abs(clientX - selectionBox.startPos.x); const deltaY = Math.abs(clientY - selectionBox.startPos.y); + const container = containerRef.current; const nextSelectionBox = { ...selectionBox, - currentPos: { x: clientX, y: clientY }, + currentPos, + bounds: container + ? getSelectionBoxBounds({ + container, + startPos: selectionBox.startPos, + currentPos, + }) + : null, isActive: deltaX > 5 || deltaY > 5 || selectionBox.isActive, }; @@ -133,26 +177,26 @@ export function useBoxSelect<TId>({ window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; - }, [selectionBox, updateSelection]); + }, [containerRef, selectionBox, updateSelection]); useEffect(() => { if (!selectionBox) { return; } + const container = containerRef.current; const previousBodyUserSelect = document.body.style.userSelect; - const previousContainerUserSelect = - containerRef.current?.style.userSelect ?? ""; + const previousContainerUserSelect = container?.style.userSelect ?? ""; document.body.style.userSelect = "none"; - if (containerRef.current) { - containerRef.current.style.userSelect = "none"; + if (container) { + container.style.userSelect = "none"; } return () => { document.body.style.userSelect = previousBodyUserSelect; - if (containerRef.current) { - containerRef.current.style.userSelect = previousContainerUserSelect; + if (container) { + container.style.userSelect = previousContainerUserSelect; } }; }, [containerRef, selectionBox]); @@ -162,7 +206,10 @@ export function useBoxSelect<TId>({ }, []); return { - selectionBox, + selectionBox: + selectionBox?.isActive && selectionBox.bounds + ? { bounds: selectionBox.bounds } + : null, handleMouseDown, isSelecting: selectionBox?.isActive ?? false, shouldIgnoreClick, diff --git a/apps/web/src/selection/hooks/use-selection-scope.ts b/apps/web/src/selection/hooks/use-selection-scope.ts index ad4d2ce8..7fddab08 100644 --- a/apps/web/src/selection/hooks/use-selection-scope.ts +++ b/apps/web/src/selection/hooks/use-selection-scope.ts @@ -1,28 +1,25 @@ -import { useEffect, useRef } from "react";
+import { useEffect, useState } from "react";
+import { useCommittedRef } from "@/hooks/use-committed-ref";
import { useSelectionContext } from "@/selection/context";
import { activateScope, type ScopeEntry } from "@/selection/scope";
export function useSelectionScope() {
const { selectedIds, clearSelection } = useSelectionContext();
- const hasSelectionRef = useRef(selectedIds.length > 0);
- const clearSelectionRef = useRef(clearSelection);
const hasSelection = selectedIds.length > 0;
-
- hasSelectionRef.current = hasSelection;
- clearSelectionRef.current = clearSelection;
-
- const entryRef = useRef<ScopeEntry>({
+ const hasSelectionRef = useCommittedRef(hasSelection);
+ const clearSelectionRef = useCommittedRef(clearSelection);
+ const [entry] = useState<ScopeEntry>(() => ({
hasSelection: () => hasSelectionRef.current,
clear: () => {
clearSelectionRef.current();
},
- });
+ }));
useEffect(() => {
if (!hasSelection) {
return;
}
- return activateScope({ entry: entryRef.current });
- }, [hasSelection]);
+ return activateScope({ entry });
+ }, [entry, hasSelection]);
}
diff --git a/apps/web/src/selection/selectable-item.tsx b/apps/web/src/selection/selectable-item.tsx index cc9e1dbf..9735c4a8 100644 --- a/apps/web/src/selection/selectable-item.tsx +++ b/apps/web/src/selection/selectable-item.tsx @@ -6,7 +6,13 @@ import { SELECTABLE_ITEM_ATTRIBUTE } from "@/selection/attributes"; import type { SelectableItemProps } from "@/selection/types";
import { cn } from "@/utils/ui";
-function setForwardedRef<T>(ref: React.ForwardedRef<T>, value: T | null) {
+function setForwardedRef<T>({
+ ref,
+ value,
+}: {
+ ref: React.ForwardedRef<T>;
+ value: T | null;
+}) {
if (typeof ref === "function") {
ref(value);
return;
@@ -55,7 +61,7 @@ export const SelectableItem = forwardRef<HTMLDivElement, SelectableItemProps>( const handleRef = useCallback(
(element: HTMLDivElement | null) => {
registerItem(id, element);
- setForwardedRef(forwardedRef, element);
+ setForwardedRef({ ref: forwardedRef, value: element });
},
[forwardedRef, id, registerItem],
);
diff --git a/apps/web/src/selection/selectable-surface.tsx b/apps/web/src/selection/selectable-surface.tsx index 24166daf..12682ba8 100644 --- a/apps/web/src/selection/selectable-surface.tsx +++ b/apps/web/src/selection/selectable-surface.tsx @@ -167,14 +167,15 @@ export function SelectableSurface({ [],
);
- const { selectionBox, handleMouseDown, shouldIgnoreClick } = useBoxSelect({
- containerRef,
- resolveIntersections,
- selectedIds: selectionState.selectedIds,
- anchorId: selectionState.anchorId,
- onSelectionChange: handleBoxSelectionChange,
- shouldStartSelection,
- });
+ const { selectionBox, handleMouseDown, isSelecting, shouldIgnoreClick } =
+ useBoxSelect({
+ containerRef,
+ resolveIntersections,
+ selectedIds: selectionState.selectedIds,
+ anchorId: selectionState.anchorId,
+ onSelectionChange: handleBoxSelectionChange,
+ shouldStartSelection,
+ });
const handleBackgroundClick = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
@@ -236,7 +237,7 @@ export function SelectableSurface({ return () => clearTimeout(timer);
}, [getItemElement, onRevealComplete, revealId]);
- const isBoxSelecting = selectionBox?.isActive ?? false;
+ const isBoxSelecting = isSelecting;
const contextValue = useMemo(() => {
return {
@@ -276,12 +277,7 @@ export function SelectableSurface({ onKeyDown={handleBackgroundKeyDown}
>
{children}
- <SelectionBox
- startPos={selectionBox?.startPos ?? null}
- currentPos={selectionBox?.currentPos ?? null}
- containerRef={containerRef}
- isActive={selectionBox?.isActive ?? false}
- />
+ <SelectionBox bounds={selectionBox?.bounds ?? null} />
</div>
</SelectionContext.Provider>
);
diff --git a/apps/web/src/selection/selection-box.tsx b/apps/web/src/selection/selection-box.tsx index e30f574d..2f2bf933 100644 --- a/apps/web/src/selection/selection-box.tsx +++ b/apps/web/src/selection/selection-box.tsx @@ -1,49 +1,22 @@ "use client";
-import { useMemo } from "react";
+import type { SelectionBoxBounds } from "@/selection/types";
interface SelectionBoxProps {
- startPos: { x: number; y: number } | null;
- currentPos: { x: number; y: number } | null;
- containerRef: React.RefObject<HTMLElement | null>;
- isActive: boolean;
+ bounds: SelectionBoxBounds | null;
}
-export function SelectionBox({
- startPos,
- currentPos,
- containerRef,
- isActive,
-}: SelectionBoxProps) {
- const selectionBoxStyle = useMemo(() => {
- if (!isActive || !startPos || !currentPos || !containerRef.current) {
- return null;
- }
-
- const containerRect = containerRef.current.getBoundingClientRect();
- const startX = startPos.x - containerRect.left;
- const startY = startPos.y - containerRect.top;
- const currentX = currentPos.x - containerRect.left;
- const currentY = currentPos.y - containerRect.top;
-
- const left = Math.min(startX, currentX);
- const top = Math.min(startY, currentY);
- const width = Math.abs(currentX - startX);
- const height = Math.abs(currentY - startY);
-
- return {
- left: `${left}px`,
- top: `${top}px`,
- width: `${width}px`,
- height: `${height}px`,
- };
- }, [containerRef, currentPos, isActive, startPos]);
-
- if (!selectionBoxStyle) return null;
+export function SelectionBox({ bounds }: SelectionBoxProps) {
+ if (!bounds) return null;
return (
<div
- style={selectionBoxStyle}
+ style={{
+ left: `${bounds.left}px`,
+ top: `${bounds.top}px`,
+ width: `${bounds.width}px`,
+ height: `${bounds.height}px`,
+ }}
className="border-foreground/50 bg-foreground/5 pointer-events-none absolute z-50 border"
/>
);
diff --git a/apps/web/src/selection/types.ts b/apps/web/src/selection/types.ts index 31520788..a797d6c0 100644 --- a/apps/web/src/selection/types.ts +++ b/apps/web/src/selection/types.ts @@ -8,6 +8,13 @@ export interface BoxSelectionSnapshot<TId = string> { initialAnchorId: TId | null;
}
+export interface SelectionBoxBounds {
+ left: number;
+ top: number;
+ width: number;
+ height: number;
+}
+
export interface BoxSelectionChange<TId = string>
extends BoxSelectionSnapshot<TId> {
intersectedIds: TId[];
diff --git a/apps/web/src/services/renderer/canvas-renderer.ts b/apps/web/src/services/renderer/canvas-renderer.ts index 4a002675..58294e9c 100644 --- a/apps/web/src/services/renderer/canvas-renderer.ts +++ b/apps/web/src/services/renderer/canvas-renderer.ts @@ -1,5 +1,6 @@ import type { FrameRate } from "opencut-wasm"; import type { AnyBaseNode } from "./nodes/base-node"; +import { createCanvasSurface } from "./canvas-utils"; import { buildFrameDescriptor } from "./compositor/frame-descriptor"; import { wasmCompositor } from "./compositor/wasm-compositor"; import { resolveRenderTree } from "./resolve"; @@ -16,8 +17,8 @@ export type CanvasRendererParams = { }; export class CanvasRenderer { - canvas: OffscreenCanvas | HTMLCanvasElement; - context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D; + canvas: OffscreenCanvas; + context: OffscreenCanvasRenderingContext2D; width: number; height: number; fps: FrameRate; @@ -27,22 +28,9 @@ export class CanvasRenderer { this.height = height; this.fps = fps; - try { - this.canvas = new OffscreenCanvas(width, height); - } catch { - this.canvas = document.createElement("canvas"); - this.canvas.width = width; - this.canvas.height = height; - } - - const context = this.canvas.getContext("2d"); - if (!context) { - throw new Error("Failed to get canvas context"); - } - - this.context = context as - | OffscreenCanvasRenderingContext2D - | CanvasRenderingContext2D; + const surface = createCanvasSurface({ width, height }); + this.canvas = surface.canvas; + this.context = surface.context; } getOutputCanvas(): HTMLCanvasElement { @@ -57,20 +45,9 @@ export class CanvasRenderer { this.width = width; this.height = height; - if (this.canvas instanceof OffscreenCanvas) { - this.canvas = new OffscreenCanvas(width, height); - } else { - this.canvas.width = width; - this.canvas.height = height; - } - - const context = this.canvas.getContext("2d"); - if (!context) { - throw new Error("Failed to get canvas context"); - } - this.context = context as - | OffscreenCanvasRenderingContext2D - | CanvasRenderingContext2D; + const surface = createCanvasSurface({ width, height }); + this.canvas = surface.canvas; + this.context = surface.context; } async render({ node, time }: { node: AnyBaseNode; time: number }) { diff --git a/apps/web/src/services/renderer/canvas-utils.ts b/apps/web/src/services/renderer/canvas-utils.ts index 093168e6..6981e6d5 100644 --- a/apps/web/src/services/renderer/canvas-utils.ts +++ b/apps/web/src/services/renderer/canvas-utils.ts @@ -1,16 +1,17 @@ -export function createOffscreenCanvas({ +export function createCanvasSurface({ width, height, }: { width: number; height: number; -}): OffscreenCanvas | HTMLCanvasElement { - try { - return new OffscreenCanvas(width, height); - } catch { - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - return canvas; +}): { + canvas: OffscreenCanvas; + context: OffscreenCanvasRenderingContext2D; +} { + const canvas = new OffscreenCanvas(width, height); + const context = canvas.getContext("2d"); + if (!context) { + throw new Error("Failed to create 2D rendering context"); } + return { canvas, context }; } diff --git a/apps/web/src/services/renderer/compositor/frame-descriptor.ts b/apps/web/src/services/renderer/compositor/frame-descriptor.ts index 190a0237..1bc316ef 100644 --- a/apps/web/src/services/renderer/compositor/frame-descriptor.ts +++ b/apps/web/src/services/renderer/compositor/frame-descriptor.ts @@ -3,7 +3,7 @@ import { masksRegistry } from "@/masks"; import { incrementCounter } from "@/diagnostics/render-perf"; import type { AnyBaseNode } from "../nodes/base-node"; import type { CanvasRenderer } from "../canvas-renderer"; -import { createOffscreenCanvas } from "../canvas-utils"; +import { createCanvasSurface } from "../canvas-utils"; import { BlurBackgroundNode } from "../nodes/blur-background-node"; import { ColorNode } from "../nodes/color-node"; import { EffectLayerNode } from "../nodes/effect-layer-node"; @@ -414,15 +414,11 @@ function buildMaskArtifacts({ const { width: canvasWidth, height: canvasHeight } = renderer; const maskContentHash = `mask:${mask.type}:${JSON.stringify(mask.params)}:${transformHash(transform)}:${canvasWidth}x${canvasHeight}:direct=${shouldRenderMaskDirectly}`; const drawMask: TextureCanvasDrawFn = (ctx) => { - const elementMaskCanvas = createOffscreenCanvas({ - width: Math.round(transform.width), - height: Math.round(transform.height), - }); - const elementMaskCtx = elementMaskCanvas.getContext("2d") as - | CanvasRenderingContext2D - | OffscreenCanvasRenderingContext2D - | null; - if (!elementMaskCtx) return; + const { canvas: elementMaskCanvas, context: elementMaskCtx } = + createCanvasSurface({ + width: Math.round(transform.width), + height: Math.round(transform.height), + }); if (shouldRenderMaskDirectly && definition.renderer.renderMask) { definition.renderer.renderMask({ @@ -465,15 +461,10 @@ function buildMaskArtifacts({ const strokeTextureId = `${path}:mask-stroke`; const strokeContentHash = `stroke:${mask.type}:${JSON.stringify(mask.params)}:${transformHash(transform)}:${canvasWidth}x${canvasHeight}`; const drawStroke: TextureCanvasDrawFn = (ctx) => { - const strokeCanvas = createOffscreenCanvas({ + const { canvas: strokeCanvas, context: strokeCtx } = createCanvasSurface({ width: Math.round(transform.width), height: Math.round(transform.height), }); - const strokeCtx = strokeCanvas.getContext("2d") as - | CanvasRenderingContext2D - | OffscreenCanvasRenderingContext2D - | null; - if (!strokeCtx) return; if (definition.renderer.renderStroke) { definition.renderer.renderStroke({ diff --git a/apps/web/src/services/renderer/effect-preview.ts b/apps/web/src/services/renderer/effect-preview.ts index c4cacde0..cbfb1a31 100644 --- a/apps/web/src/services/renderer/effect-preview.ts +++ b/apps/web/src/services/renderer/effect-preview.ts @@ -1,4 +1,4 @@ -import { createOffscreenCanvas } from "./canvas-utils"; +import { createCanvasSurface } from "./canvas-utils"; import { effectsRegistry, resolveEffectPasses } from "@/effects"; import { buildDefaultParamValues } from "@/params/registry"; import type { ParamValues } from "@/params"; @@ -8,7 +8,7 @@ const PREVIEW_SIZE = 160; const PREVIEW_IMAGE_PATH = "/effects/preview.jpg"; class EffectPreviewService { - private testSourceCanvas: OffscreenCanvas | HTMLCanvasElement | null = null; + private testSourceCanvas: OffscreenCanvas | null = null; private previewImageElement: HTMLImageElement | null = null; private onReadyCallbacks = new Set<() => void>(); @@ -98,7 +98,7 @@ class EffectPreviewService { }: { width: number; height: number; - }): OffscreenCanvas | HTMLCanvasElement | null { + }): OffscreenCanvas | null { const isImageReady = this.previewImageElement?.complete && (this.previewImageElement.naturalWidth ?? 0) > 0; @@ -106,15 +106,8 @@ class EffectPreviewService { return null; } - const canvas = createOffscreenCanvas({ width, height }); - const ctx = canvas.getContext("2d") as - | CanvasRenderingContext2D - | OffscreenCanvasRenderingContext2D - | null; - if (!ctx) { - throw new Error("failed to get 2d context for test source"); - } - ctx.drawImage(this.previewImageElement, 0, 0, width, height); + const { canvas, context } = createCanvasSurface({ width, height }); + context.drawImage(this.previewImageElement, 0, 0, width, height); return canvas; } @@ -124,7 +117,7 @@ class EffectPreviewService { }: { width: number; height: number; - }): CanvasImageSource | null { + }): OffscreenCanvas | null { if ( !this.testSourceCanvas || this.testSourceCanvas.width !== width || @@ -141,17 +134,17 @@ class EffectPreviewService { height, passes, }: { - source: CanvasImageSource; + source: OffscreenCanvas; width: number; height: number; passes: ReturnType<typeof resolveEffectPasses>; - }): OffscreenCanvas | HTMLCanvasElement { + }): OffscreenCanvas { return gpuRenderer.applyEffect({ source, width, height, passes, - }) as OffscreenCanvas | HTMLCanvasElement; + }); } } diff --git a/apps/web/src/services/renderer/gpu-renderer.ts b/apps/web/src/services/renderer/gpu-renderer.ts index c61b6b98..c1be3f1a 100644 --- a/apps/web/src/services/renderer/gpu-renderer.ts +++ b/apps/web/src/services/renderer/gpu-renderer.ts @@ -34,23 +34,17 @@ export const gpuRenderer = { height, passes, }: { - source: CanvasImageSource; + source: OffscreenCanvas; width: number; height: number; passes: EffectPass[]; - }): CanvasImageSource { + }): OffscreenCanvas { if (passes.length === 0 || !gpuAvailable) { return source; } - const sourceCanvas = ensureOffscreenCanvas({ - source, - width, - height, - label: "effect source", - }); return applyEffectPasses({ - source: sourceCanvas, + source, width, height, passes: serializeEffectPasses(passes), @@ -63,23 +57,17 @@ export const gpuRenderer = { height, feather, }: { - maskCanvas: CanvasImageSource; + maskCanvas: OffscreenCanvas; width: number; height: number; feather: number; - }): CanvasImageSource { + }): OffscreenCanvas { if (!gpuAvailable) { return maskCanvas; } - const sourceCanvas = ensureOffscreenCanvas({ - source: maskCanvas, - width, - height, - label: "mask source", - }); return applyMaskFeatherWasm({ - mask: sourceCanvas, + mask: maskCanvas, width, height, feather, @@ -87,35 +75,6 @@ export const gpuRenderer = { }, }; -function ensureOffscreenCanvas({ - source, - width, - height, - label, -}: { - source: CanvasImageSource; - width: number; - height: number; - label: string; -}): OffscreenCanvas { - if (source instanceof OffscreenCanvas) { - return source; - } - - if (typeof OffscreenCanvas === "undefined") { - throw new Error(`OffscreenCanvas is required for the GPU ${label}`); - } - - const canvas = new OffscreenCanvas(width, height); - const context = canvas.getContext("2d"); - if (!context) { - throw new Error(`Failed to get 2d context for the GPU ${label}`); - } - context.clearRect(0, 0, width, height); - context.drawImage(source, 0, 0, width, height); - return canvas; -} - function serializeEffectPasses(passes: EffectPass[]) { return passes.map((pass) => ({ shader: pass.shader, diff --git a/apps/web/src/services/renderer/mask-feather.ts b/apps/web/src/services/renderer/mask-feather.ts index 4ea7eb2b..dde4decc 100644 --- a/apps/web/src/services/renderer/mask-feather.ts +++ b/apps/web/src/services/renderer/mask-feather.ts @@ -6,15 +6,15 @@ export function applyMaskFeather({ height, feather, }: { - maskCanvas: CanvasImageSource; + maskCanvas: OffscreenCanvas; width: number; height: number; feather: number; -}): OffscreenCanvas | HTMLCanvasElement { +}): OffscreenCanvas { return gpuRenderer.applyMaskFeather({ maskCanvas, width, height, feather, - }) as OffscreenCanvas | HTMLCanvasElement; + }); } diff --git a/apps/web/src/services/renderer/nodes/graphic-node.ts b/apps/web/src/services/renderer/nodes/graphic-node.ts index 2d022510..798b56a0 100644 --- a/apps/web/src/services/renderer/nodes/graphic-node.ts +++ b/apps/web/src/services/renderer/nodes/graphic-node.ts @@ -1,4 +1,4 @@ -import { createOffscreenCanvas } from "../canvas-utils"; +import { createCanvasSurface } from "../canvas-utils"; import { DEFAULT_GRAPHIC_SOURCE_SIZE, getGraphicDefinition, @@ -25,7 +25,7 @@ export class GraphicNode extends VisualNode< ResolvedGraphicNodeState > { private cachedKey: string | null = null; - private cachedSource: OffscreenCanvas | HTMLCanvasElement | null = null; + private cachedSource: OffscreenCanvas | null = null; constructor(params: GraphicNodeParams) { super(params); @@ -36,7 +36,7 @@ export class GraphicNode extends VisualNode< resolvedParams, }: { resolvedParams: ParamValues; - }): OffscreenCanvas | HTMLCanvasElement | null { + }): OffscreenCanvas { const definition = getGraphicDefinition({ definitionId: this.params.definitionId, }); @@ -48,20 +48,13 @@ export class GraphicNode extends VisualNode< return this.cachedSource; } - const canvas = createOffscreenCanvas({ + const { canvas, context } = createCanvasSurface({ width: DEFAULT_GRAPHIC_SOURCE_SIZE, height: DEFAULT_GRAPHIC_SOURCE_SIZE, }); - const ctx = canvas.getContext("2d") as - | CanvasRenderingContext2D - | OffscreenCanvasRenderingContext2D - | null; - if (!ctx) { - return null; - } definition.render({ - ctx, + ctx: context, params: resolvedParams, width: DEFAULT_GRAPHIC_SOURCE_SIZE, height: DEFAULT_GRAPHIC_SOURCE_SIZE, diff --git a/apps/web/src/services/renderer/nodes/image-node.ts b/apps/web/src/services/renderer/nodes/image-node.ts index d8c56803..4f475a2d 100644 --- a/apps/web/src/services/renderer/nodes/image-node.ts +++ b/apps/web/src/services/renderer/nodes/image-node.ts @@ -17,10 +17,13 @@ export interface CachedImageSource { const imageSourceCache = new Map<string, Promise<CachedImageSource>>(); -export function loadImageSource( - url: string, - maxSourceSize?: number, -): Promise<CachedImageSource> { +export function loadImageSource({ + url, + maxSourceSize, +}: { + url: string; + maxSourceSize?: number; +}): Promise<CachedImageSource> { const cacheKey = `${url}::${maxSourceSize ?? "full"}`; const cached = imageSourceCache.get(cacheKey); diff --git a/apps/web/src/services/renderer/resolve.ts b/apps/web/src/services/renderer/resolve.ts index 36a6ecb5..4637df8d 100644 --- a/apps/web/src/services/renderer/resolve.ts +++ b/apps/web/src/services/renderer/resolve.ts @@ -235,10 +235,10 @@ async function resolveImageNode({ node: ImageNode; context: ResolveContext; }): Promise<ResolvedVisualSourceNodeState | null> { - const source = await loadImageSource( - node.params.url, - node.params.maxSourceSize, - ); + const source = await loadImageSource({ + url: node.params.url, + maxSourceSize: node.params.maxSourceSize, + }); const visualState = resolveVisualState({ params: node.params, context, @@ -434,7 +434,7 @@ async function resolveBackdropSource({ }; } - const source = await loadImageSource(node.params.url); + const source = await loadImageSource({ url: node.params.url }); return { source: source.source, width: source.width, diff --git a/apps/web/src/services/storage/indexeddb-adapter.ts b/apps/web/src/services/storage/indexeddb-adapter.ts index 2df4feaa..4a7498b6 100644 --- a/apps/web/src/services/storage/indexeddb-adapter.ts +++ b/apps/web/src/services/storage/indexeddb-adapter.ts @@ -5,7 +5,15 @@ export class IndexedDBAdapter<T> implements StorageAdapter<T> { private storeName: string; private version: number; - constructor(dbName: string, storeName: string, version = 1) { + constructor({ + dbName, + storeName, + version = 1, + }: { + dbName: string; + storeName: string; + version?: number; + }) { this.dbName = dbName; this.storeName = storeName; this.version = version; @@ -39,7 +47,13 @@ export class IndexedDBAdapter<T> implements StorageAdapter<T> { }); } - async set(key: string, value: T): Promise<void> { + async set({ + key, + value, + }: { + key: string; + value: T; + }): Promise<void> { const db = await this.getDB(); const transaction = db.transaction([this.storeName], "readwrite"); const store = transaction.objectStore(this.storeName); diff --git a/apps/web/src/services/storage/migrations/__tests__/helpers.ts b/apps/web/src/services/storage/migrations/__tests__/helpers.ts new file mode 100644 index 00000000..381cb935 --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/helpers.ts @@ -0,0 +1,39 @@ +/** + * Type-safe accessors for navigating migration output. + * + * Migrations input/output `Record<string, unknown>` because they handle + * potentially malformed data from older versions. Tests need to inspect + * specific properties of the migrated result without using type assertions. + * + * These helpers narrow through type guards (Array.isArray + a record + * predicate), so the file contains no unsafe assertions despite turning + * unknown into concrete shapes. + */ + +function describe(value: unknown): string { + if (value === null) return "null"; + if (Array.isArray(value)) return "array"; + return typeof value; +} + +function isRecord(value: unknown): value is Record<string, unknown> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export function asRecord(value: unknown): Record<string, unknown> { + if (!isRecord(value)) { + throw new Error(`Expected record, got ${describe(value)}`); + } + return value; +} + +export function asArray(value: unknown): unknown[] { + if (!Array.isArray(value)) { + throw new Error(`Expected array, got ${describe(value)}`); + } + return value; +} + +export function asRecordArray(value: unknown): Record<string, unknown>[] { + return asArray(value).map(asRecord); +} diff --git a/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts b/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts index 2cd67da8..73b64fed 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts @@ -8,6 +8,7 @@ import { v0ProjectWithMetadata, v1Project, } from "./fixtures"; +import { asArray, asRecord, asRecordArray } from "./helpers"; describe("V0 to V1 Migration", () => { const fixedDate = new Date("2024-06-01T12:00:00.000Z"); @@ -22,7 +23,7 @@ describe("V0 to V1 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(1); expect(Array.isArray(result.project.scenes)).toBe(true); - expect((result.project.scenes as unknown[]).length).toBe(1); + expect(asArray(result.project.scenes).length).toBe(1); expect(result.project.currentSceneId).toBeDefined(); }); @@ -32,7 +33,7 @@ describe("V0 to V1 Migration", () => { options: { now: fixedDate }, }); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const mainScene = scenes[0]; expect(mainScene.isMain).toBe(true); @@ -48,7 +49,7 @@ describe("V0 to V1 Migration", () => { options: { now: fixedDate }, }); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.updatedAt).toBe(fixedDate.toISOString()); }); diff --git a/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts index 0840ac76..6670c479 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts @@ -11,6 +11,7 @@ import { v1ProjectWithMultipleScenes, v2Project, } from "./fixtures"; +import { asRecord, asRecordArray } from "./helpers"; const DEFAULT_FPS = 30; const DEFAULT_BACKGROUND_BLUR_INTENSITY = 10; @@ -25,7 +26,7 @@ describe("V1 to V2 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(2); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.id).toBe(v1Project.id); expect(metadata.name).toBe(v1Project.name); expect(typeof metadata.createdAt).toBe("string"); @@ -35,7 +36,7 @@ describe("V1 to V2 Migration", () => { test("creates settings object from flat properties", () => { const result = transformProjectV1ToV2({ project: v1Project }); - const settings = result.project.settings as Record<string, unknown>; + const settings = asRecord(result.project.settings); expect(settings.fps).toBe(v1Project.fps); expect(settings.canvasSize).toEqual(v1Project.canvasSize); expect(settings.originalCanvasSize).toBe(null); @@ -44,8 +45,8 @@ describe("V1 to V2 Migration", () => { test("converts color background correctly", () => { const result = transformProjectV1ToV2({ project: v1Project }); - const settings = result.project.settings as Record<string, unknown>; - const background = settings.background as Record<string, unknown>; + const settings = asRecord(result.project.settings); + const background = asRecord(settings.background); expect(background.type).toBe("color"); expect(background.color).toBe(v1Project.backgroundColor); }); @@ -58,8 +59,8 @@ describe("V1 to V2 Migration", () => { }; const result = transformProjectV1ToV2({ project: projectWithBlur }); - const settings = result.project.settings as Record<string, unknown>; - const background = settings.background as Record<string, unknown>; + const settings = asRecord(result.project.settings); + const background = asRecord(settings.background); expect(background.type).toBe("blur"); expect(background.blurIntensity).toBe(30); }); @@ -67,7 +68,7 @@ describe("V1 to V2 Migration", () => { test("applies legacy bookmarks to main scene", () => { const result = transformProjectV1ToV2({ project: v1Project }); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const mainScene = scenes.find((s) => s.isMain === true); expect(mainScene?.bookmarks).toEqual(v1Project.bookmarks); }); @@ -77,7 +78,7 @@ describe("V1 to V2 Migration", () => { project: v1ProjectWithMultipleScenes, }); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const introScene = scenes.find((s) => s.name === "Intro"); expect(introScene?.bookmarks).toEqual([1.0]); }); @@ -102,7 +103,7 @@ describe("V1 to V2 Migration", () => { }); expect(result.skipped).toBe(false); - const settings = result.project.settings as Record<string, unknown>; + const settings = asRecord(result.project.settings); expect(settings.fps).toBe(DEFAULT_FPS); expect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE); }); @@ -115,11 +116,11 @@ describe("V1 to V2 Migration", () => { }; const result = transformProjectV1ToV2({ project: minimalProject }); - const settings = result.project.settings as Record<string, unknown>; + const settings = asRecord(result.project.settings); expect(settings.fps).toBe(DEFAULT_FPS); expect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE); - const background = settings.background as Record<string, unknown>; + const background = asRecord(settings.background); expect(background.type).toBe("color"); expect(background.color).toBe(DEFAULT_BACKGROUND_COLOR); }); @@ -135,8 +136,8 @@ describe("V1 to V2 Migration", () => { project: projectWithBlurNoIntensity, }); - const settings = result.project.settings as Record<string, unknown>; - const background = settings.background as Record<string, unknown>; + const settings = asRecord(result.project.settings); + const background = asRecord(settings.background); expect(background.blurIntensity).toBe(DEFAULT_BACKGROUND_BLUR_INTENSITY); }); @@ -183,9 +184,9 @@ describe("V1 to V2 Migration", () => { project: projectWithTracks, }); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const mainScene = scenes[0]; - const tracks = mainScene.tracks as Array<Record<string, unknown>>; + const tracks = asRecordArray(mainScene.tracks); expect(tracks.length).toBe(1); expect(tracks[0].name).toBe("Existing Track"); }); @@ -240,9 +241,9 @@ describe("V1 to V2 Migration", () => { context, }); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const mainScene = scenes[0]; - const tracks = mainScene.tracks as Array<Record<string, unknown>>; + const tracks = asRecordArray(mainScene.tracks); expect(Array.isArray(tracks)).toBe(true); expect(tracks).toHaveLength(1); expect(tracks[0].type).toBe("video"); @@ -302,9 +303,9 @@ describe("V1 to V2 Migration", () => { }); expect(result.skipped).toBe(false); - const scenes = result.project.scenes as Array<Record<string, unknown>>; - const tracks = scenes[0].tracks as Array<Record<string, unknown>>; - const elements = tracks[0].elements as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); + const tracks = asRecordArray(scenes[0].tracks); + const elements = asRecordArray(tracks[0].elements); const textElement = elements[0]; expect(textElement.opacity).toBe(0.5); expect(textElement.transform).toEqual({ diff --git a/apps/web/src/services/storage/migrations/__tests__/v15-to-v16.test.ts b/apps/web/src/services/storage/migrations/__tests__/v15-to-v16.test.ts index 122af2cb..1af445ac 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v15-to-v16.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v15-to-v16.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test";
import { transformProjectV15ToV16 } from "../transformers/v15-to-v16";
+import { asRecordArray } from "./helpers";
describe("V15 to V16 Migration", () => {
test("renames sticker tracks to graphic tracks", () => {
@@ -61,10 +62,10 @@ describe("V15 to V16 Migration", () => { expect(result.skipped).toBe(false);
expect(result.project.version).toBe(16);
- expect(
- (result.project.scenes as Array<{ tracks: Array<{ type: string }> }>)[0]
- .tracks[0].type,
- ).toBe("graphic");
+ const firstTrack = asRecordArray(
+ asRecordArray(result.project.scenes)[0].tracks,
+ )[0];
+ expect(firstTrack.type).toBe("graphic");
});
test("skips projects already on v16", () => {
diff --git a/apps/web/src/services/storage/migrations/__tests__/v16-to-v17.test.ts b/apps/web/src/services/storage/migrations/__tests__/v16-to-v17.test.ts index 352ca762..10a792ca 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v16-to-v17.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v16-to-v17.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test";
import { transformProjectV16ToV17 } from "../transformers/v16-to-v17";
+import { asRecord, asRecordArray } from "./helpers";
describe("V16 to V17 Migration", () => {
test("adds center stroke alignment to masks that do not have it", () => {
@@ -97,13 +98,13 @@ describe("V16 to V17 Migration", () => { expect(result.skipped).toBe(false);
expect(result.project.version).toBe(17);
- const migratedMasks = (
- ((result.project.scenes as Array<{ tracks: Array<{ elements: Array<{ masks: Array<{ params: Record<string, unknown> }> }> }> }>)[0]
- .tracks[0].elements[0].masks)
- );
+ const firstScene = asRecordArray(result.project.scenes)[0];
+ const firstTrack = asRecordArray(firstScene.tracks)[0];
+ const firstElement = asRecordArray(firstTrack.elements)[0];
+ const migratedMasks = asRecordArray(firstElement.masks);
- expect(migratedMasks[0].params.strokeAlign).toBe("center");
- expect(migratedMasks[1].params.strokeAlign).toBe("outside");
+ expect(asRecord(migratedMasks[0].params).strokeAlign).toBe("center");
+ expect(asRecord(migratedMasks[1].params).strokeAlign).toBe("outside");
});
test("skips projects already on v17", () => {
diff --git a/apps/web/src/services/storage/migrations/__tests__/v18-to-v19.test.ts b/apps/web/src/services/storage/migrations/__tests__/v18-to-v19.test.ts index 0f679b6d..0cb30f2b 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v18-to-v19.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v18-to-v19.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test";
import { transformProjectV18ToV19 } from "../transformers/v18-to-v19";
+import { asRecord } from "./helpers";
describe("V18 to V19 Migration", () => {
test("adds canvas size mode and empty remembered custom size defaults", () => {
@@ -26,15 +27,13 @@ describe("V18 to V19 Migration", () => { expect(result.skipped).toBe(false);
expect(result.project.version).toBe(19);
- expect(
- (result.project.settings as Record<string, unknown>).canvasSizeMode,
- ).toBe("preset");
- expect(
- (result.project.settings as Record<string, unknown>).lastCustomCanvasSize,
- ).toBeNull();
- expect(
- (result.project.settings as Record<string, unknown>).originalCanvasSize,
- ).toEqual({ width: 1920, height: 1080 });
+ const settings = asRecord(result.project.settings);
+ expect(settings.canvasSizeMode).toBe("preset");
+ expect(settings.lastCustomCanvasSize).toBeNull();
+ expect(settings.originalCanvasSize).toEqual({
+ width: 1920,
+ height: 1080,
+ });
});
test("skips projects already on v19", () => {
diff --git a/apps/web/src/services/storage/migrations/__tests__/v19-to-v20.test.ts b/apps/web/src/services/storage/migrations/__tests__/v19-to-v20.test.ts index 0e0cfbbe..b1bfe86f 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v19-to-v20.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v19-to-v20.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { transformProjectV19ToV20 } from "../transformers/v19-to-v20"; +import { asRecordArray } from "./helpers"; describe("V19 to V20 Migration", () => { test("backfills source audio enabled state on video elements", () => { @@ -82,19 +83,16 @@ describe("V19 to V20 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(20); - expect( - ((result.project.scenes as Array<Record<string, unknown>>)[0] - .tracks as Array<Record<string, unknown>>)[0].elements, - ).toEqual([ + const scene = asRecordArray(result.project.scenes)[0]; + const tracks = asRecordArray(scene.tracks); + expect(tracks[0].elements).toEqual([ expect.objectContaining({ id: "video-1", isSourceAudioEnabled: true, }), ]); expect( - (((result.project.scenes as Array<Record<string, unknown>>)[0] - .tracks as Array<Record<string, unknown>>)[1] - .elements as Array<Record<string, unknown>>)[0].isSourceAudioEnabled, + asRecordArray(tracks[1].elements)[0].isSourceAudioEnabled, ).toBeUndefined(); }); @@ -120,11 +118,10 @@ describe("V19 to V20 Migration", () => { }, }); - expect( - (((result.project.scenes as Array<Record<string, unknown>>)[0] - .tracks as Array<Record<string, unknown>>)[0] - .elements as Array<Record<string, unknown>>)[0].isSourceAudioEnabled, - ).toBe(false); + const scene = asRecordArray(result.project.scenes)[0]; + const track = asRecordArray(scene.tracks)[0]; + const element = asRecordArray(track.elements)[0]; + expect(element.isSourceAudioEnabled).toBe(false); }); test("skips projects already on v20", () => { diff --git a/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts b/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts index ba8b3bfb..d18b4dd4 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts @@ -8,6 +8,7 @@ import { v2ProjectWithBlurBackground, v3Project, } from "./fixtures"; +import { asRecord } from "./helpers"; describe("V2 to V3 Migration", () => { describe("transformProjectV2ToV3", () => { @@ -17,14 +18,14 @@ describe("V2 to V3 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(3); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(typeof metadata.duration).toBe("number"); }); test("calculates duration from scene tracks", () => { const result = transformProjectV2ToV3({ project: v2Project }); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); // v2Project has a video element with duration 15.5 and a text element at startTime 2 with duration 5 // Total duration should be max(15.5, 2+5) = 15.5 expect(metadata.duration).toBe(15.5); @@ -36,7 +37,7 @@ describe("V2 to V3 Migration", () => { }); expect(result.skipped).toBe(false); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); // v2ProjectWithBlurBackground has a video with duration 30 expect(metadata.duration).toBe(30); }); @@ -45,7 +46,7 @@ describe("V2 to V3 Migration", () => { const result = transformProjectV2ToV3({ project: v2ProjectEmptyScenes }); expect(result.skipped).toBe(false); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.duration).toBe(0); }); @@ -55,7 +56,7 @@ describe("V2 to V3 Migration", () => { }); expect(result.skipped).toBe(false); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.duration).toBe(0); }); @@ -91,7 +92,7 @@ describe("V2 to V3 Migration", () => { test("preserves existing metadata fields", () => { const result = transformProjectV2ToV3({ project: v2Project }); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.id).toBe(v2Project.metadata.id); expect(metadata.name).toBe(v2Project.metadata.name); expect(metadata.thumbnail).toBe(v2Project.metadata.thumbnail); @@ -122,7 +123,7 @@ describe("V2 to V3 Migration", () => { }); expect(result.skipped).toBe(false); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.duration).toBe(0); }); @@ -156,7 +157,7 @@ describe("V2 to V3 Migration", () => { }; const result = transformProjectV2ToV3({ project: multiSceneProject }); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); // Duration is from main scene only, not sum of all scenes expect(metadata.duration).toBe(10); }); diff --git a/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts b/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts index 3d77a775..2737bfc3 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { transformProjectV20ToV21 } from "../transformers/v20-to-v21"; +import { asRecord } from "./helpers"; const LEGACY_DEFAULT_BACKGROUND_BLUR_INTENSITY = 50; @@ -27,8 +28,8 @@ describe("V20 to V21 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(21); - const settings = result.project.settings as Record<string, unknown>; - const background = settings.background as Record<string, unknown>; + const settings = asRecord(result.project.settings); + const background = asRecord(settings.background); expect(background.blurIntensity).toBe(500); }); @@ -45,8 +46,8 @@ describe("V20 to V21 Migration", () => { }); expect(result.skipped).toBe(false); - const settings = result.project.settings as Record<string, unknown>; - const background = settings.background as Record<string, unknown>; + const settings = asRecord(result.project.settings); + const background = asRecord(settings.background); expect(background.blurIntensity).toBe( LEGACY_DEFAULT_BACKGROUND_BLUR_INTENSITY, ); @@ -65,7 +66,7 @@ describe("V20 to V21 Migration", () => { }); expect(result.skipped).toBe(false); - const settings = result.project.settings as Record<string, unknown>; + const settings = asRecord(result.project.settings); expect(settings.background).toEqual({ type: "color", color: "#000000" }); }); diff --git a/apps/web/src/services/storage/migrations/__tests__/v21-to-v22.test.ts b/apps/web/src/services/storage/migrations/__tests__/v21-to-v22.test.ts index 18b815b0..76d9c0e1 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v21-to-v22.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v21-to-v22.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { transformProjectV21ToV22 } from "../transformers/v21-to-v22"; +import { asRecord, asRecordArray } from "./helpers"; describe("V21 to V22 Migration", () => { test("migrates legacy animation channels to bindings and component channels", () => { @@ -77,12 +78,12 @@ describe("V21 to V22 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(22); - const scenes = result.project.scenes as Array<Record<string, unknown>>; - const tracks = scenes[0].tracks as Array<Record<string, unknown>>; - const elements = tracks[0].elements as Array<Record<string, unknown>>; - const animations = elements[0].animations as Record<string, unknown>; - const bindings = animations.bindings as Record<string, Record<string, unknown>>; - const channels = animations.channels as Record<string, Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); + const tracks = asRecordArray(scenes[0].tracks); + const elements = asRecordArray(tracks[0].elements); + const animations = asRecord(elements[0].animations); + const bindings = asRecord(animations.bindings); + const channels = asRecord(animations.channels); expect(bindings.opacity).toEqual({ path: "opacity", diff --git a/apps/web/src/services/storage/migrations/__tests__/v22-to-v23.test.ts b/apps/web/src/services/storage/migrations/__tests__/v22-to-v23.test.ts index 5b764b48..e229ecbc 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v22-to-v23.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v22-to-v23.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { transformProjectV22ToV23 } from "../transformers/v22-to-v23"; +import { asRecord, asRecordArray } from "./helpers"; describe("V22 to V23 Migration", () => { test("converts project time values from seconds to ticks and fps to a frame-rate object", () => { @@ -101,20 +102,17 @@ describe("V22 to V23 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(23); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.duration).toBe(1_860_000); - const settings = result.project.settings as Record<string, unknown>; + const settings = asRecord(result.project.settings); expect(settings.fps).toEqual({ numerator: 30_000, denominator: 1_001 }); - const timelineViewState = result.project.timelineViewState as Record< - string, - unknown - >; + const timelineViewState = asRecord(result.project.timelineViewState); expect(timelineViewState.playheadTime).toBe(150_000); expect(timelineViewState.scrollLeft).toBe(120); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const scene = scenes[0]; expect(scene.bookmarks).toEqual([ { @@ -126,8 +124,8 @@ describe("V22 to V23 Migration", () => { { time: 540_000 }, ]); - const tracks = scene.tracks as Array<Record<string, unknown>>; - const elements = tracks[0].elements as Array<Record<string, unknown>>; + const tracks = asRecordArray(scene.tracks); + const elements = asRecordArray(tracks[0].elements); const element = elements[0]; expect(element.startTime).toBe(150_000); expect(element.duration).toBe(660_000); @@ -135,8 +133,8 @@ describe("V22 to V23 Migration", () => { expect(element.trimEnd).toBe(60_000); expect(element.sourceDuration).toBe(750_000); - const animations = element.animations as Record<string, unknown>; - const channels = animations.channels as Record<string, Record<string, unknown>>; + const animations = asRecord(element.animations); + const channels = asRecord(animations.channels); expect(channels["opacity:value"]).toEqual({ kind: "scalar", keys: [ diff --git a/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts b/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts index 730d958d..5e16e886 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { transformProjectV26ToV27 } from "../transformers/v26-to-v27"; +import { asRecord, asRecordArray } from "./helpers"; describe("V26 to V27 Migration", () => { test("converts custom mask paths from JSON strings to typed point arrays", () => { @@ -59,13 +60,13 @@ describe("V26 to V27 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(27); - const scenes = result.project.scenes as Array<Record<string, unknown>>; - const tracks = scenes[0].tracks as Record<string, unknown>; - const mainTrack = tracks.main as Record<string, unknown>; - const elements = mainTrack.elements as Array<Record<string, unknown>>; - const masks = elements[0].masks as Array<Record<string, unknown>>; - const customParams = masks[0].params as Record<string, unknown>; - const rectangleParams = masks[1].params as Record<string, unknown>; + const scenes = asRecordArray(result.project.scenes); + const tracks = asRecord(scenes[0].tracks); + const mainTrack = asRecord(tracks.main); + const elements = asRecordArray(mainTrack.elements); + const masks = asRecordArray(elements[0].masks); + const customParams = asRecord(masks[0].params); + const rectangleParams = asRecord(masks[1].params); expect(customParams.path).toEqual([ { diff --git a/apps/web/src/services/storage/migrations/__tests__/v27-to-v28.test.ts b/apps/web/src/services/storage/migrations/__tests__/v27-to-v28.test.ts index d467c8a1..cb09e07d 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v27-to-v28.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v27-to-v28.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { transformProjectV27ToV28 } from "../transformers/v27-to-v28"; +import { asRecord, asRecordArray } from "./helpers"; describe("V27 to V28 Migration", () => { test("rounds persisted media-time floats back to integer ticks", () => { @@ -96,18 +97,15 @@ describe("V27 to V28 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(28); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.duration).toBe(2_152_466); - const timelineViewState = result.project.timelineViewState as Record< - string, - unknown - >; + const timelineViewState = asRecord(result.project.timelineViewState); expect(timelineViewState.playheadTime).toBe(301_235); expect(timelineViewState.zoomLevel).toBe(1.25); expect(timelineViewState.scrollLeft).toBe(120); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const scene = scenes[0]; expect(scene.bookmarks).toEqual([ { @@ -118,9 +116,9 @@ describe("V27 to V28 Migration", () => { }, ]); - const tracks = scene.tracks as Record<string, unknown>; - const mainTrack = tracks.main as Record<string, unknown>; - const elements = mainTrack.elements as Array<Record<string, unknown>>; + const tracks = asRecord(scene.tracks); + const mainTrack = asRecord(tracks.main); + const elements = asRecordArray(mainTrack.elements); const element = elements[0]; expect(element.startTime).toBe(300_000); expect(element.duration).toBe(2_152_466); @@ -128,9 +126,9 @@ describe("V27 to V28 Migration", () => { expect(element.trimEnd).toBe(15_001); expect(element.sourceDuration).toBe(2_197_468); - const animations = element.animations as Record<string, unknown>; - const channels = animations.channels as Record<string, Record<string, unknown>>; - const opacityChannel = channels.opacity; + const animations = asRecord(element.animations); + const channels = asRecord(animations.channels); + const opacityChannel = asRecord(channels.opacity); expect(opacityChannel.keys).toEqual([ { id: "key-1", @@ -204,26 +202,23 @@ describe("V27 to V28 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(28); - const metadata = result.project.metadata as Record<string, unknown>; + const metadata = asRecord(result.project.metadata); expect(metadata.duration).toBe(120_000); - const timelineViewState = result.project.timelineViewState as Record< - string, - unknown - >; + const timelineViewState = asRecord(result.project.timelineViewState); expect(timelineViewState).toEqual({ zoomLevel: 2, scrollLeft: 300, playheadTime: 30_000, }); - const scenes = result.project.scenes as Array<Record<string, unknown>>; + const scenes = asRecordArray(result.project.scenes); const scene = scenes[0]; expect(scene.bookmarks).toEqual([{ time: 60_000, duration: 15_000 }]); - const tracks = scene.tracks as Record<string, unknown>; - const mainTrack = tracks.main as Record<string, unknown>; - const element = (mainTrack.elements as Array<Record<string, unknown>>)[0]; + const tracks = asRecord(scene.tracks); + const mainTrack = asRecord(tracks.main); + const element = asRecordArray(mainTrack.elements)[0]; expect(element.startTime).toBe(10_000); expect(element.duration).toBe(20_000); expect(element.trimStart).toBe(1_000); diff --git a/apps/web/src/services/storage/migrations/__tests__/v3-to-v4.test.ts b/apps/web/src/services/storage/migrations/__tests__/v3-to-v4.test.ts index 6c2d6597..e5ad7e34 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v3-to-v4.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v3-to-v4.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test";
import { transformProjectV3ToV4 } from "../transformers/v3-to-v4";
import { v3Project } from "./fixtures";
+import { asRecordArray } from "./helpers";
describe("V3 to V4 Migration", () => {
test("normalizes legacy text fontWeight values", () => {
@@ -54,15 +55,9 @@ describe("V3 to V4 Migration", () => { expect(result.skipped).toBe(false);
expect(result.project.version).toBe(4);
- const migratedScene = (
- result.project.scenes as Array<Record<string, unknown>>
- )[0];
- const migratedTrack = (
- migratedScene.tracks as Array<Record<string, unknown>>
- )[0];
- const migratedElement = (
- migratedTrack.elements as Array<Record<string, unknown>>
- )[0];
+ const migratedScene = asRecordArray(result.project.scenes)[0];
+ const migratedTrack = asRecordArray(migratedScene.tracks)[0];
+ const migratedElement = asRecordArray(migratedTrack.elements)[0];
expect(migratedElement.fontWeight).toBe("700");
});
@@ -104,15 +99,9 @@ describe("V3 to V4 Migration", () => { };
const result = transformProjectV3ToV4({ project: projectWithoutTextTrack });
- const migratedScene = (
- result.project.scenes as Array<Record<string, unknown>>
- )[0];
- const migratedTrack = (
- migratedScene.tracks as Array<Record<string, unknown>>
- )[0];
- const migratedElement = (
- migratedTrack.elements as Array<Record<string, unknown>>
- )[0];
+ const migratedScene = asRecordArray(result.project.scenes)[0];
+ const migratedTrack = asRecordArray(migratedScene.tracks)[0];
+ const migratedElement = asRecordArray(migratedTrack.elements)[0];
expect(migratedElement.iconName).toBe("mdi:home");
});
diff --git a/apps/web/src/services/storage/migrations/__tests__/v4-to-v5.test.ts b/apps/web/src/services/storage/migrations/__tests__/v4-to-v5.test.ts index 95386137..16e9e7ca 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v4-to-v5.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v4-to-v5.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test";
import { transformProjectV4ToV5 } from "../transformers/v4-to-v5";
import { v3Project } from "./fixtures";
+import { asRecordArray } from "./helpers";
describe("V4 to V5 Migration", () => {
test("migrates sticker iconName to stickerId and removes legacy color", () => {
@@ -48,15 +49,9 @@ describe("V4 to V5 Migration", () => { expect(result.skipped).toBe(false);
expect(result.project.version).toBe(5);
- const migratedScene = (
- result.project.scenes as Array<Record<string, unknown>>
- )[0];
- const migratedTrack = (
- migratedScene.tracks as Array<Record<string, unknown>>
- )[0];
- const migratedElement = (
- migratedTrack.elements as Array<Record<string, unknown>>
- )[0];
+ const migratedScene = asRecordArray(result.project.scenes)[0];
+ const migratedTrack = asRecordArray(migratedScene.tracks)[0];
+ const migratedElement = asRecordArray(migratedTrack.elements)[0];
expect(migratedElement.stickerId).toBe("icons:mdi:home");
expect("iconName" in migratedElement).toBe(false);
@@ -101,15 +96,9 @@ describe("V4 to V5 Migration", () => { };
const result = transformProjectV4ToV5({ project: projectWithStickerId });
- const migratedScene = (
- result.project.scenes as Array<Record<string, unknown>>
- )[0];
- const migratedTrack = (
- migratedScene.tracks as Array<Record<string, unknown>>
- )[0];
- const migratedElement = (
- migratedTrack.elements as Array<Record<string, unknown>>
- )[0];
+ const migratedScene = asRecordArray(result.project.scenes)[0];
+ const migratedTrack = asRecordArray(migratedScene.tracks)[0];
+ const migratedElement = asRecordArray(migratedTrack.elements)[0];
expect(migratedElement.stickerId).toBe("flags:AD");
});
diff --git a/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts b/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts index b05d1837..8fd267fd 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; import { transformProjectV5ToV6 } from "../transformers/v5-to-v6"; import { v5Project } from "./fixtures"; +import { asRecordArray } from "./helpers"; describe("V5 to V6 Migration", () => { test("converts number bookmarks to Bookmark objects", async () => { @@ -13,19 +14,13 @@ describe("V5 to V6 Migration", () => { expect(result.skipped).toBe(false); expect(result.project.version).toBe(6); - const mainScene = ( - result.project.scenes as Array<{ bookmarks: unknown[] }> - )[0]; - expect(mainScene.bookmarks).toEqual([ + const scenes = asRecordArray(result.project.scenes); + expect(scenes[0].bookmarks).toEqual([ { time: 2.0 }, { time: 5.5 }, { time: 12.0 }, ]); - - const introScene = ( - result.project.scenes as Array<{ bookmarks: unknown[] }> - )[1]; - expect(introScene.bookmarks).toEqual([]); + expect(scenes[1].bookmarks).toEqual([]); }); test("skips projects that are already v6", () => { @@ -83,9 +78,7 @@ describe("V5 to V6 Migration", () => { }); expect(result.skipped).toBe(false); - const mainScene = ( - result.project.scenes as Array<{ bookmarks: unknown[] }> - )[0]; + const mainScene = asRecordArray(result.project.scenes)[0]; expect(mainScene.bookmarks).toEqual([ { time: 1, note: "Intro", color: "#ef4444" }, { time: 5.5, duration: 2 }, diff --git a/apps/web/src/services/storage/migrations/__tests__/v8-to-v9.test.ts b/apps/web/src/services/storage/migrations/__tests__/v8-to-v9.test.ts index aa79a6ea..ce8e9bd3 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v8-to-v9.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v8-to-v9.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test";
import { transformProjectV8ToV9 } from "../transformers/v8-to-v9";
+import { asRecord, asRecordArray } from "./helpers";
const v8ProjectWithText = {
id: "project-v8-text",
@@ -70,16 +71,16 @@ describe("V8 to V9 Migration", () => { expect(result.skipped).toBe(false);
expect(result.project.version).toBe(9);
- const track = (
- result.project.scenes as Array<{ tracks: Array<{ elements: unknown[] }> }>
- )[0].tracks[0];
- const elements = track.elements as Array<{ background: { enabled: boolean; color: string } }>;
+ const track = asRecordArray(asRecordArray(result.project.scenes)[0].tracks)[0];
+ const elements = asRecordArray(track.elements);
+ const background0 = asRecord(elements[0].background);
+ const background1 = asRecord(elements[1].background);
- expect(elements[0].background.enabled).toBe(true);
- expect(elements[0].background.color).toBe("#ff0000");
+ expect(background0.enabled).toBe(true);
+ expect(background0.color).toBe("#ff0000");
- expect(elements[1].background.enabled).toBe(false);
- expect(elements[1].background.color).toBe("transparent");
+ expect(background1.enabled).toBe(false);
+ expect(background1.color).toBe("transparent");
});
test("preserves existing background.enabled if already present", () => {
@@ -87,7 +88,7 @@ describe("V8 to V9 Migration", () => { ...v8ProjectWithText,
scenes: [
{
- ...(v8ProjectWithText.scenes as Record<string, unknown>[])[0],
+ ...asRecordArray(v8ProjectWithText.scenes)[0],
tracks: [
{
id: "track-text",
@@ -116,10 +117,9 @@ describe("V8 to V9 Migration", () => { const result = transformProjectV8ToV9({ project: projectWithEnabled });
expect(result.skipped).toBe(false);
- const elements = (
- result.project.scenes as Array<{ tracks: Array<{ elements: unknown[] }> }>
- )[0].tracks[0].elements as Array<{ background: { enabled: boolean } }>;
- expect(elements[0].background.enabled).toBe(false);
+ const track = asRecordArray(asRecordArray(result.project.scenes)[0].tracks)[0];
+ const elements = asRecordArray(track.elements);
+ expect(asRecord(elements[0].background).enabled).toBe(false);
});
test("skips non-text elements and tracks", () => {
diff --git a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts index 065a6b34..e9e23c4e 100644 --- a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts +++ b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts @@ -19,34 +19,6 @@ const EMPTY_V1_TO_V2_CONTEXT: V1ToV2Context = { mediaTypesById: {}, }; -interface LegacyMediaElement { - type: "media"; - mediaId: string; - muted?: boolean; - [key: string]: unknown; -} - -interface LegacyTextElement { - type: "text"; - x: number; - y: number; - rotation: number; - opacity: number; - [key: string]: unknown; -} - -interface LegacyAudioElement { - type: "audio"; - mediaId: string; - [key: string]: unknown; -} - -interface LegacyMediaTrack { - type: "media"; - elements: unknown[]; - [key: string]: unknown; -} - interface V2Transform { scale: number; position: { x: number; y: number }; @@ -315,7 +287,7 @@ function transformTracks({ const isMain = !isFirstVideoTrackFound; isFirstVideoTrackFound = true; const videoTrack = transformMediaTrack({ - track: track as LegacyMediaTrack, + track, context, isMain, }); @@ -343,7 +315,7 @@ function transformMediaTrack({ context, isMain, }: { - track: LegacyMediaTrack; + track: Record<string, unknown>; context: V1ToV2Context; isMain: boolean; }): V2VideoTrack { @@ -354,8 +326,7 @@ function transformMediaTrack({ return null; } - const mediaElement = element as LegacyMediaElement; - const mediaId = getStringValue({ value: mediaElement.mediaId }); + const mediaId = getStringValue({ value: element.mediaId }); if (!mediaId) { return null; } @@ -372,7 +343,7 @@ function transformMediaTrack({ rotate: 0, }; - const muted = mediaElement.muted === true; + const muted = element.muted === true; if (mediaType === "image") { const imageElement: V2ImageElement = { @@ -442,15 +413,14 @@ function transformTextTrack({ return null; } - const textElement = element as LegacyTextElement; - const x = getNumberValue({ value: textElement.x, fallback: 0 }); - const y = getNumberValue({ value: textElement.y, fallback: 0 }); + const x = getNumberValue({ value: element.x, fallback: 0 }); + const y = getNumberValue({ value: element.y, fallback: 0 }); const rotation = getNumberValue({ - value: textElement.rotation, + value: element.rotation, fallback: 0, }); const opacity = getNumberValue({ - value: textElement.opacity, + value: element.opacity, fallback: 1, }); @@ -464,23 +434,23 @@ function transformTextTrack({ id: getStringValue({ value: element.id, fallback: "" }), name: getStringValue({ value: element.name, fallback: "" }), type: "text", - content: getStringValue({ value: textElement.content, fallback: "" }), + content: getStringValue({ value: element.content, fallback: "" }), fontSize: getNumberValue({ - value: textElement.fontSize, + value: element.fontSize, fallback: 16, }), fontFamily: getStringValue({ - value: textElement.fontFamily, + value: element.fontFamily, fallback: "Arial", }), color: getStringValue({ - value: textElement.color, + value: element.color, fallback: "#000000", }), background: { enabled: false, color: getStringValue({ - value: textElement.backgroundColor, + value: element.backgroundColor, fallback: "transparent", }), cornerRadius: 0, @@ -489,22 +459,26 @@ function transformTextTrack({ offsetX: 0, offsetY: 0, }, - textAlign: (getStringValue({ - value: textElement.textAlign, + textAlign: parseEnum({ + value: element.textAlign, + allowed: ["left", "center", "right"] as const, fallback: "left", - }) || "left") as "left" | "center" | "right", - fontWeight: (getStringValue({ - value: textElement.fontWeight, + }), + fontWeight: parseEnum({ + value: element.fontWeight, + allowed: ["normal", "bold"] as const, fallback: "normal", - }) || "normal") as "normal" | "bold", - fontStyle: (getStringValue({ - value: textElement.fontStyle, + }), + fontStyle: parseEnum({ + value: element.fontStyle, + allowed: ["normal", "italic"] as const, fallback: "normal", - }) || "normal") as "normal" | "italic", - textDecoration: (getStringValue({ - value: textElement.textDecoration, + }), + textDecoration: parseEnum({ + value: element.textDecoration, + allowed: ["none", "underline", "line-through"] as const, fallback: "none", - }) || "none") as "none" | "underline" | "line-through", + }), hidden: false, transform, opacity, @@ -538,8 +512,7 @@ function transformAudioTrack({ return null; } - const audioElement = element as LegacyAudioElement; - const mediaId = getStringValue({ value: audioElement.mediaId }); + const mediaId = getStringValue({ value: element.mediaId }); if (!mediaId) { return null; } @@ -762,6 +735,23 @@ function getStringValue({ return fallback; } +function parseEnum<T extends string>({ + value, + allowed, + fallback, +}: { + value: unknown; + allowed: readonly T[]; + fallback: T; +}): T { + for (const candidate of allowed) { + if (value === candidate) { + return candidate; + } + } + return fallback; +} + function normalizeDateString({ value }: { value: unknown }): string { if (value instanceof Date) { return value.toISOString(); diff --git a/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts b/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts index 89dde38e..6e152adb 100644 --- a/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts +++ b/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts @@ -94,10 +94,7 @@ function migrateSplitMask({ mask }: { mask: unknown }): unknown { const x = (position - 0.5) * Math.cos(angleRad);
const y = (position - 0.5) * Math.sin(angleRad);
- const { position: _removed, ...restParams } = params as Record<
- string,
- unknown
- > & { position: unknown };
+ const { position: _removed, ...restParams } = params;
return {
...mask,
diff --git a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts index 3b118ee7..4477d363 100644 --- a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts +++ b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts @@ -12,19 +12,18 @@ interface CustomMaskPathPoint { } function isCustomMaskPathPoint(value: unknown): value is CustomMaskPathPoint { - if (!value || typeof value !== "object") { + if (!isRecord(value)) { return false; } - const candidate = value as Record<string, unknown>; return ( - typeof candidate.id === "string" && - typeof candidate.x === "number" && - typeof candidate.y === "number" && - typeof candidate.inX === "number" && - typeof candidate.inY === "number" && - typeof candidate.outX === "number" && - typeof candidate.outY === "number" + typeof value.id === "string" && + typeof value.x === "number" && + typeof value.y === "number" && + typeof value.inX === "number" && + typeof value.inY === "number" && + typeof value.outX === "number" && + typeof value.outY === "number" ); } diff --git a/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts b/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts index 84d97fd8..7a28134b 100644 --- a/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts +++ b/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts @@ -1,10 +1,10 @@ import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
-const LEGACY_FONT_WEIGHT_MAP = {
- normal: "400",
- bold: "700",
-} as const;
+const LEGACY_FONT_WEIGHT_MAP = new Map<string, string>([
+ ["normal", "400"],
+ ["bold", "700"],
+]);
const VALID_NUMERIC_FONT_WEIGHTS = new Set([
"100",
@@ -168,10 +168,9 @@ function normalizeFontWeight({ value }: { value: unknown }): unknown { }
const normalized = value.trim().toLowerCase();
- if (normalized in LEGACY_FONT_WEIGHT_MAP) {
- return LEGACY_FONT_WEIGHT_MAP[
- normalized as keyof typeof LEGACY_FONT_WEIGHT_MAP
- ];
+ const mapped = LEGACY_FONT_WEIGHT_MAP.get(normalized);
+ if (mapped !== undefined) {
+ return mapped;
}
if (VALID_NUMERIC_FONT_WEIGHTS.has(normalized)) {
diff --git a/apps/web/src/services/storage/opfs-adapter.ts b/apps/web/src/services/storage/opfs-adapter.ts index 879223b5..220ee452 100644 --- a/apps/web/src/services/storage/opfs-adapter.ts +++ b/apps/web/src/services/storage/opfs-adapter.ts @@ -27,7 +27,13 @@ export class OPFSAdapter implements StorageAdapter<File> { } } - async set(key: string, file: File): Promise<void> { + async set({ + key, + value: file, + }: { + key: string; + value: File; + }): Promise<void> { const directory = await this.getDirectory(); const fileHandle = await directory.getFileHandle(key, { create: true }); const writable = await fileHandle.createWritable(); diff --git a/apps/web/src/services/storage/service.ts b/apps/web/src/services/storage/service.ts index a388e807..e8fc26c0 100644 --- a/apps/web/src/services/storage/service.ts +++ b/apps/web/src/services/storage/service.ts @@ -65,17 +65,17 @@ class StorageService { version: 1, }; - this.projectsAdapter = new IndexedDBAdapter<SerializedProject>( - this.config.projectsDb, - "projects", - this.config.version, - ); + this.projectsAdapter = new IndexedDBAdapter<SerializedProject>({ + dbName: this.config.projectsDb, + storeName: "projects", + version: this.config.version, + }); - this.savedSoundsAdapter = new IndexedDBAdapter<SavedSoundsData>( - this.config.savedSoundsDb, - "saved-sounds", - this.config.version, - ); + this.savedSoundsAdapter = new IndexedDBAdapter<SavedSoundsData>({ + dbName: this.config.savedSoundsDb, + storeName: "saved-sounds", + version: this.config.version, + }); } private async ensureMigrations(): Promise<void> { @@ -91,11 +91,11 @@ class StorageService { } private getProjectMediaAdapters({ projectId }: { projectId: string }) { - const mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>( - `${this.config.mediaDb}-${projectId}`, - "media-metadata", - this.config.version, - ); + const mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>({ + dbName: `${this.config.mediaDb}-${projectId}`, + storeName: "media-metadata", + version: this.config.version, + }); const mediaAssetsAdapter = new OPFSAdapter(`media-files-${projectId}`); @@ -161,7 +161,10 @@ class StorageService { timelineViewState: project.timelineViewState, }; - await this.projectsAdapter.set(project.metadata.id, serializedProject); + await this.projectsAdapter.set({ + key: project.metadata.id, + value: serializedProject, + }); } async loadProject({ @@ -305,8 +308,14 @@ class StorageService { }; try { - await mediaAssetsAdapter.set(mediaAsset.id, mediaAsset.file); - await mediaMetadataAdapter.set(mediaAsset.id, metadata); + await mediaAssetsAdapter.set({ + key: mediaAsset.id, + value: mediaAsset.file, + }); + await mediaMetadataAdapter.set({ + key: mediaAsset.id, + value: metadata, + }); } catch (error) { try { await mediaAssetsAdapter.remove(mediaAsset.id); @@ -501,7 +510,10 @@ class StorageService { lastModified: new Date().toISOString(), }; - await this.savedSoundsAdapter.set("user-sounds", updatedData); + await this.savedSoundsAdapter.set({ + key: "user-sounds", + value: updatedData, + }); } catch (error) { console.error("Failed to save sound effect:", error); throw error; @@ -517,7 +529,10 @@ class StorageService { lastModified: new Date().toISOString(), }; - await this.savedSoundsAdapter.set("user-sounds", updatedData); + await this.savedSoundsAdapter.set({ + key: "user-sounds", + value: updatedData, + }); } catch (error) { console.error("Failed to remove saved sound:", error); throw error; diff --git a/apps/web/src/services/storage/types.ts b/apps/web/src/services/storage/types.ts index 58c14b49..f20b311a 100644 --- a/apps/web/src/services/storage/types.ts +++ b/apps/web/src/services/storage/types.ts @@ -8,7 +8,7 @@ import type { TScene } from "@/timeline"; export interface StorageAdapter<T> { get(key: string): Promise<T | null>; - set(key: string, value: T): Promise<void>; + set(args: { key: string; value: T }): Promise<void>; remove(key: string): Promise<void>; list(): Promise<string[]>; clear(): Promise<void>; diff --git a/apps/web/src/stickers/providers/index.ts b/apps/web/src/stickers/providers/index.ts index ca23963c..56b3f6cb 100644 --- a/apps/web/src/stickers/providers/index.ts +++ b/apps/web/src/stickers/providers/index.ts @@ -1,24 +1,24 @@ -import { stickersRegistry } from "../registry";
-import type { StickerProvider } from "@/stickers/types";
-import { flagsProvider } from "./flags";
-import { logosProvider } from "./logos";
-import { shapesProvider } from "./shapes";
-
-const defaultProviders: StickerProvider[] = [
- logosProvider,
- flagsProvider,
- shapesProvider,
-];
-
-export function registerDefaultStickerProviders({
- providersToRegister = defaultProviders,
-}: {
- providersToRegister?: StickerProvider[];
-} = {}): void {
- for (const provider of providersToRegister) {
- if (stickersRegistry.has(provider.id)) {
- continue;
- }
- stickersRegistry.register(provider.id, provider);
- }
-}
+import { stickersRegistry } from "../registry"; +import type { StickerProvider } from "@/stickers/types"; +import { flagsProvider } from "./flags"; +import { logosProvider } from "./logos"; +import { shapesProvider } from "./shapes"; + +const defaultProviders: StickerProvider[] = [ + logosProvider, + flagsProvider, + shapesProvider, +]; + +export function registerDefaultStickerProviders({ + providersToRegister = defaultProviders, +}: { + providersToRegister?: StickerProvider[]; +} = {}): void { + for (const provider of providersToRegister) { + if (stickersRegistry.has(provider.id)) { + continue; + } + stickersRegistry.register(provider.id, provider); + } +} diff --git a/apps/web/src/subtitles/components/assets-view.tsx b/apps/web/src/subtitles/components/assets-view.tsx index 7e2ddad9..84480fa4 100644 --- a/apps/web/src/subtitles/components/assets-view.tsx +++ b/apps/web/src/subtitles/components/assets-view.tsx @@ -64,6 +64,7 @@ const IDLE_STATE: ProcessingState = { warnings: [], }; +/* eslint-disable opencut/prefer-object-params -- React reducers must accept (state, action). */ function processingReducer( state: ProcessingState, action: ProcessingAction, @@ -80,6 +81,7 @@ function processingReducer( return { status: "idle", error: action.error, warnings: [] }; } } +/* eslint-enable opencut/prefer-object-params */ export function Captions() { const [selectedLanguage, setSelectedLanguage] = diff --git a/apps/web/src/subtitles/insert.ts b/apps/web/src/subtitles/insert.ts index 50d6c40b..2a71e181 100644 --- a/apps/web/src/subtitles/insert.ts +++ b/apps/web/src/subtitles/insert.ts @@ -18,7 +18,7 @@ export function insertCaptionChunksAsTextTrack({ return null; } - const addTrackCommand = new AddTrackCommand("text", 0); + const addTrackCommand = new AddTrackCommand({ type: "text", index: 0 }); const trackId = addTrackCommand.getTrackId(); const canvasSize = editor.project.getActive().settings.canvasSize; const insertCommands = captions.map( diff --git a/apps/web/src/text/components/assets-view.tsx b/apps/web/src/text/components/assets-view.tsx index ffabe3b8..82a8e46c 100644 --- a/apps/web/src/text/components/assets-view.tsx +++ b/apps/web/src/text/components/assets-view.tsx @@ -1,47 +1,47 @@ -import { DraggableItem } from "@/components/editor/panels/assets/draggable-item";
-import { PanelView } from "@/components/editor/panels/assets/views/base-panel";
-import { useEditor } from "@/editor/use-editor";
-import { DEFAULTS } from "@/timeline/defaults";
-import { buildTextElement } from "@/timeline/element-utils";
-import type { MediaTime } from "@/wasm";
-
-export function TextView() {
- const editor = useEditor();
-
- const handleAddToTimeline = ({ currentTime }: { currentTime: MediaTime }) => {
- const activeScene = editor.scenes.getActiveScene();
- if (!activeScene) return;
-
- const element = buildTextElement({
- raw: DEFAULTS.text.element,
- startTime: currentTime,
- });
-
- editor.timeline.insertElement({
- element,
- placement: { mode: "auto" },
- });
- };
-
- return (
- <PanelView title="Text">
- <DraggableItem
- name="Default text"
- preview={
- <div className="bg-accent flex size-full items-center justify-center rounded">
- <span className="text-xs select-none">Default text</span>
- </div>
- }
- dragData={{
- id: "temp-text-id",
- type: DEFAULTS.text.element.type,
- name: DEFAULTS.text.element.name,
- content: DEFAULTS.text.element.content,
- }}
- aspectRatio={1}
- onAddToTimeline={handleAddToTimeline}
- shouldShowLabel={false}
- />
- </PanelView>
- );
-}
+import { DraggableItem } from "@/components/editor/panels/assets/draggable-item"; +import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; +import { useEditor } from "@/editor/use-editor"; +import { DEFAULTS } from "@/timeline/defaults"; +import { buildTextElement } from "@/timeline/element-utils"; +import type { MediaTime } from "@/wasm"; + +export function TextView() { + const editor = useEditor(); + + const handleAddToTimeline = ({ currentTime }: { currentTime: MediaTime }) => { + const activeScene = editor.scenes.getActiveScene(); + if (!activeScene) return; + + const element = buildTextElement({ + raw: DEFAULTS.text.element, + startTime: currentTime, + }); + + editor.timeline.insertElement({ + element, + placement: { mode: "auto" }, + }); + }; + + return ( + <PanelView title="Text"> + <DraggableItem + name="Default text" + preview={ + <div className="bg-accent flex size-full items-center justify-center rounded"> + <span className="text-xs select-none">Default text</span> + </div> + } + dragData={{ + id: "temp-text-id", + type: DEFAULTS.text.element.type, + name: DEFAULTS.text.element.name, + content: DEFAULTS.text.element.content, + }} + aspectRatio={1} + onAddToTimeline={handleAddToTimeline} + shouldShowLabel={false} + /> + </PanelView> + ); +} diff --git a/apps/web/src/text/components/text-tab.tsx b/apps/web/src/text/components/text-tab.tsx index 0f0b274a..e69de29b 100644 --- a/apps/web/src/text/components/text-tab.tsx +++ b/apps/web/src/text/components/text-tab.tsx @@ -1,767 +0,0 @@ -"use client"; - -import { Textarea } from "@/components/ui/textarea"; -import { FontPicker } from "@/components/ui/font-picker"; -import type { TextElement } from "@/timeline"; -import { NumberField } from "@/components/ui/number-field"; -import { useRef } from "react"; -import { - Section, - SectionContent, - SectionField, - SectionFields, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { ColorPicker } from "@/components/ui/color-picker"; -import { Button } from "@/components/ui/button"; -import { uppercase } from "@/utils/string"; -import { clamp, formatNumberForDisplay } from "@/utils/math"; -import { useEditor } from "@/editor/use-editor"; -import { CORNER_RADIUS_MAX, CORNER_RADIUS_MIN } from "@/text/background"; -import { - DEFAULT_TEXT_COLOR, - MAX_FONT_SIZE, - MIN_FONT_SIZE, -} from "@/text/typography"; -import { usePropertyDraft } from "@/components/editor/panels/properties/hooks/use-property-draft"; -import { useKeyframedColorProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-color-property"; -import { useKeyframedNumberProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-number-property"; -import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; -import { KeyframeToggle } from "@/components/editor/panels/properties/components/keyframe-toggle"; -import { isPropertyAtDefault } from "@/rendering/components/transform-tab"; -import { - resolveColorAtTime, - resolveNumberAtTime, -} from "@/animation/values"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { - MinusSignIcon, - PlusSignIcon, - TextFontIcon, -} from "@hugeicons/core-free-icons"; -import { OcTextHeightIcon, OcTextWidthIcon } from "@/components/icons"; -import { DEFAULTS } from "@/timeline/defaults"; -import { cn } from "@/utils/ui"; - -export function TextTab({ - element, - trackId, -}: { - element: TextElement; - trackId: string; -}) { - return ( - <div className="flex flex-col"> - <ContentSection element={element} trackId={trackId} /> - <TypographySection element={element} trackId={trackId} /> - <SpacingSection element={element} trackId={trackId} /> - <BackgroundSection element={element} trackId={trackId} /> - </div> - ); -} - -function ContentSection({ - element, - trackId, -}: { - element: TextElement; - trackId: string; -}) { - const editor = useEditor(); - - const content = usePropertyDraft({ - displayValue: element.content, - parse: (input) => input, - onPreview: (value) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { content: value } }, - ], - }), - onCommit: () => editor.timeline.commitPreview(), - }); - - return ( - <Section collapsible sectionKey={`${element.id}:content`}> - <SectionHeader> - <SectionTitle>Content</SectionTitle> - </SectionHeader> - <SectionContent> - <Textarea - placeholder="Name" - value={content.displayValue} - className="min-h-20" - onFocus={content.onFocus} - onChange={content.onChange} - onBlur={content.onBlur} - /> - </SectionContent> - </Section> - ); -} - -function TypographySection({ - element, - trackId, -}: { - element: TextElement; - trackId: string; -}) { - const editor = useEditor(); - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const resolvedTextColor = resolveColorAtTime({ - baseColor: element.color, - animations: element.animations, - propertyPath: "color", - localTime, - }); - - const textColor = useKeyframedColorProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "color", - localTime, - isPlayheadWithinElementRange, - resolvedColor: resolvedTextColor, - buildBaseUpdates: ({ value }) => ({ color: value }), - }); - - const fontSize = usePropertyDraft({ - displayValue: element.fontSize.toString(), - parse: (input) => { - const parsed = parseFloat(input); - if (Number.isNaN(parsed)) return null; - return clamp({ - value: Math.round(parsed), - min: MIN_FONT_SIZE, - max: MAX_FONT_SIZE, - }); - }, - onPreview: (value) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { fontSize: value } }, - ], - }), - onCommit: () => editor.timeline.commitPreview(), - }); - - return ( - <Section collapsible sectionKey={`${element.id}:typography`}> - <SectionHeader> - <SectionTitle>Typography</SectionTitle> - </SectionHeader> - <SectionContent> - <SectionFields> - <SectionField label="Font"> - <FontPicker - defaultValue={element.fontFamily} - onValueChange={(value) => - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - patch: { fontFamily: value }, - }, - ], - }) - } - /> - </SectionField> - <SectionField label="Size"> - <NumberField - value={fontSize.displayValue} - min={MIN_FONT_SIZE} - max={MAX_FONT_SIZE} - onFocus={fontSize.onFocus} - onChange={fontSize.onChange} - onBlur={fontSize.onBlur} - onScrub={fontSize.scrubTo} - onScrubEnd={fontSize.commitScrub} - onReset={() => - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - patch: { - fontSize: DEFAULTS.text.element.fontSize, - }, - }, - ], - }) - } - isDefault={element.fontSize === DEFAULTS.text.element.fontSize} - icon={<HugeiconsIcon icon={TextFontIcon} />} - /> - </SectionField> - <SectionField - label="Color" - beforeLabel={ - <KeyframeToggle - isActive={textColor.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle text color keyframe" - onToggle={textColor.toggleKeyframe} - /> - } - > - <ColorPicker - value={uppercase({ - string: resolvedTextColor.replace("#", ""), - })} - onChange={(color) => textColor.onChange({ color: `#${color}` })} - onChangeEnd={textColor.onChangeEnd} - /> - </SectionField> - </SectionFields> - </SectionContent> - </Section> - ); -} - -function SpacingSection({ - element, - trackId, -}: { - element: TextElement; - trackId: string; -}) { - const editor = useEditor(); - - const letterSpacing = usePropertyDraft({ - displayValue: Math.round( - element.letterSpacing ?? DEFAULTS.text.letterSpacing, - ).toString(), - parse: (input) => { - const parsed = parseFloat(input); - return Number.isNaN(parsed) ? null : Math.round(parsed); - }, - onPreview: (value) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { letterSpacing: value } }, - ], - }), - onCommit: () => editor.timeline.commitPreview(), - }); - - const lineHeight = usePropertyDraft({ - displayValue: formatNumberForDisplay({ - value: element.lineHeight ?? DEFAULTS.text.lineHeight, - fractionDigits: 1, - }), - parse: (input) => { - const parsed = parseFloat(input); - return Number.isNaN(parsed) - ? null - : Math.max(0.1, Math.round(parsed * 10) / 10); - }, - onPreview: (value) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { lineHeight: value } }, - ], - }), - onCommit: () => editor.timeline.commitPreview(), - }); - - return ( - <Section collapsible sectionKey={`${element.id}:spacing`}> - <SectionHeader> - <SectionTitle>Spacing</SectionTitle> - </SectionHeader> - <SectionContent> - <div className="flex items-start gap-2"> - <SectionField label="Letter spacing" className="w-1/2"> - <NumberField - value={letterSpacing.displayValue} - onFocus={letterSpacing.onFocus} - onChange={letterSpacing.onChange} - onBlur={letterSpacing.onBlur} - onScrub={letterSpacing.scrubTo} - onScrubEnd={letterSpacing.commitScrub} - onReset={() => - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - patch: { letterSpacing: DEFAULTS.text.letterSpacing }, - }, - ], - }) - } - isDefault={ - (element.letterSpacing ?? DEFAULTS.text.letterSpacing) === - DEFAULTS.text.letterSpacing - } - icon={<OcTextWidthIcon size={14} />} - /> - </SectionField> - <SectionField label="Line height" className="w-1/2"> - <NumberField - value={lineHeight.displayValue} - onFocus={lineHeight.onFocus} - onChange={lineHeight.onChange} - onBlur={lineHeight.onBlur} - onScrub={lineHeight.scrubTo} - onScrubEnd={lineHeight.commitScrub} - onReset={() => - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - patch: { lineHeight: DEFAULTS.text.lineHeight }, - }, - ], - }) - } - isDefault={ - (element.lineHeight ?? DEFAULTS.text.lineHeight) === - DEFAULTS.text.lineHeight - } - icon={<OcTextHeightIcon size={14} />} - /> - </SectionField> - </div> - </SectionContent> - </Section> - ); -} - -function BackgroundSection({ - element, - trackId, -}: { - element: TextElement; - trackId: string; -}) { - const editor = useEditor(); - const lastSelectedColor = useRef(DEFAULT_TEXT_COLOR); - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const resolvedBgColor = resolveColorAtTime({ - baseColor: element.background.color, - animations: element.animations, - propertyPath: "background.color", - localTime, - }); - - const bgColor = useKeyframedColorProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "background.color", - localTime, - isPlayheadWithinElementRange, - resolvedColor: resolvedBgColor, - buildBaseUpdates: ({ value }) => ({ - background: { ...element.background, color: value }, - }), - }); - - const bg = element.background; - - const resolvedPaddingX = resolveNumberAtTime({ - baseValue: bg.paddingX ?? DEFAULTS.text.background.paddingX, - animations: element.animations, - propertyPath: "background.paddingX", - localTime, - }); - const resolvedPaddingY = resolveNumberAtTime({ - baseValue: bg.paddingY ?? DEFAULTS.text.background.paddingY, - animations: element.animations, - propertyPath: "background.paddingY", - localTime, - }); - const resolvedOffsetX = resolveNumberAtTime({ - baseValue: bg.offsetX ?? DEFAULTS.text.background.offsetX, - animations: element.animations, - propertyPath: "background.offsetX", - localTime, - }); - const resolvedOffsetY = resolveNumberAtTime({ - baseValue: bg.offsetY ?? DEFAULTS.text.background.offsetY, - animations: element.animations, - propertyPath: "background.offsetY", - localTime, - }); - const resolvedCornerRadius = resolveNumberAtTime({ - baseValue: bg.cornerRadius ?? CORNER_RADIUS_MIN, - animations: element.animations, - propertyPath: "background.cornerRadius", - localTime, - }); - - const paddingX = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "background.paddingX", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedPaddingX).toString(), - parse: (input) => { - const parsed = parseFloat(input); - return Number.isNaN(parsed) ? null : Math.max(0, Math.round(parsed)); - }, - valueAtPlayhead: resolvedPaddingX, - step: 1, - buildBaseUpdates: ({ value }) => ({ - background: { ...bg, paddingX: value }, - }), - }); - - const paddingY = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "background.paddingY", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedPaddingY).toString(), - parse: (input) => { - const parsed = parseFloat(input); - return Number.isNaN(parsed) ? null : Math.max(0, Math.round(parsed)); - }, - valueAtPlayhead: resolvedPaddingY, - step: 1, - buildBaseUpdates: ({ value }) => ({ - background: { ...bg, paddingY: value }, - }), - }); - - const offsetX = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "background.offsetX", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedOffsetX).toString(), - parse: (input) => { - const parsed = parseFloat(input); - return Number.isNaN(parsed) ? null : Math.round(parsed); - }, - valueAtPlayhead: resolvedOffsetX, - step: 1, - buildBaseUpdates: ({ value }) => ({ - background: { ...bg, offsetX: value }, - }), - }); - - const offsetY = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "background.offsetY", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedOffsetY).toString(), - parse: (input) => { - const parsed = parseFloat(input); - return Number.isNaN(parsed) ? null : Math.round(parsed); - }, - valueAtPlayhead: resolvedOffsetY, - step: 1, - buildBaseUpdates: ({ value }) => ({ - background: { ...bg, offsetY: value }, - }), - }); - - const cornerRadius = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "background.cornerRadius", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedCornerRadius).toString(), - parse: (input) => { - const parsed = parseFloat(input); - if (Number.isNaN(parsed)) return null; - return clamp({ - value: Math.round(parsed), - min: CORNER_RADIUS_MIN, - max: CORNER_RADIUS_MAX, - }); - }, - valueAtPlayhead: resolvedCornerRadius, - step: 1, - buildBaseUpdates: ({ value }) => ({ - background: { ...bg, cornerRadius: value }, - }), - }); - - const toggleBackgroundEnabled = () => { - const enabled = !element.background.enabled; - const color = - enabled && element.background.color === "transparent" - ? lastSelectedColor.current - : element.background.color; - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - patch: { - background: { - ...element.background, - enabled, - color, - }, - }, - }, - ], - }); - }; - - return ( - <Section - collapsible - defaultOpen={element.background.enabled} - sectionKey={`${element.id}:background`} - > - <SectionHeader - trailing={ - <Button - variant="ghost" - size="icon" - onClick={(event) => { - event.stopPropagation(); - toggleBackgroundEnabled(); - }} - > - <HugeiconsIcon - icon={element.background.enabled ? MinusSignIcon : PlusSignIcon} - strokeWidth={1} - /> - </Button> - } - > - <SectionTitle>Background</SectionTitle> - </SectionHeader> - <SectionContent - className={cn( - !element.background.enabled && "pointer-events-none opacity-50", - )} - > - <SectionFields> - <SectionField - label="Color" - beforeLabel={ - <KeyframeToggle - isActive={bgColor.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle background color keyframe" - onToggle={bgColor.toggleKeyframe} - /> - } - > - <ColorPicker - value={ - !element.background.enabled || - element.background.color === "transparent" - ? lastSelectedColor.current.replace("#", "") - : resolvedBgColor.replace("#", "") - } - onChange={(color) => { - const hexColor = `#${color}`; - if (color !== "transparent") { - lastSelectedColor.current = hexColor; - } - bgColor.onChange({ color: hexColor }); - }} - onChangeEnd={bgColor.onChangeEnd} - /> - </SectionField> - <div className="flex items-start gap-2"> - <SectionField - label="Width" - className="w-1/2" - beforeLabel={ - <KeyframeToggle - isActive={paddingX.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle background width keyframe" - onToggle={paddingX.toggleKeyframe} - /> - } - > - <NumberField - icon="W" - value={paddingX.displayValue} - min={0} - onFocus={paddingX.onFocus} - onChange={paddingX.onChange} - onBlur={paddingX.onBlur} - onScrub={paddingX.scrubTo} - onScrubEnd={paddingX.commitScrub} - onReset={() => - paddingX.commitValue({ - value: DEFAULTS.text.background.paddingX, - }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: paddingX.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedPaddingX, - staticValue: bg.paddingX ?? DEFAULTS.text.background.paddingX, - defaultValue: DEFAULTS.text.background.paddingX, - })} - /> - </SectionField> - <SectionField - label="Height" - className="w-1/2" - beforeLabel={ - <KeyframeToggle - isActive={paddingY.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle background height keyframe" - onToggle={paddingY.toggleKeyframe} - /> - } - > - <NumberField - icon="H" - value={paddingY.displayValue} - min={0} - onFocus={paddingY.onFocus} - onChange={paddingY.onChange} - onBlur={paddingY.onBlur} - onScrub={paddingY.scrubTo} - onScrubEnd={paddingY.commitScrub} - onReset={() => - paddingY.commitValue({ - value: DEFAULTS.text.background.paddingY, - }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: paddingY.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedPaddingY, - staticValue: bg.paddingY ?? DEFAULTS.text.background.paddingY, - defaultValue: DEFAULTS.text.background.paddingY, - })} - /> - </SectionField> - </div> - <div className="flex items-start gap-2"> - <SectionField - label="X-offset" - className="w-1/2" - beforeLabel={ - <KeyframeToggle - isActive={offsetX.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle x-offset keyframe" - onToggle={offsetX.toggleKeyframe} - /> - } - > - <NumberField - icon="X" - value={offsetX.displayValue} - onFocus={offsetX.onFocus} - onChange={offsetX.onChange} - onBlur={offsetX.onBlur} - onScrub={offsetX.scrubTo} - onScrubEnd={offsetX.commitScrub} - onReset={() => - offsetX.commitValue({ - value: DEFAULTS.text.background.offsetX, - }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: offsetX.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedOffsetX, - staticValue: bg.offsetX ?? DEFAULTS.text.background.offsetX, - defaultValue: DEFAULTS.text.background.offsetX, - })} - /> - </SectionField> - <SectionField - label="Y-offset" - className="w-1/2" - beforeLabel={ - <KeyframeToggle - isActive={offsetY.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle y-offset keyframe" - onToggle={offsetY.toggleKeyframe} - /> - } - > - <NumberField - icon="Y" - value={offsetY.displayValue} - onFocus={offsetY.onFocus} - onChange={offsetY.onChange} - onBlur={offsetY.onBlur} - onScrub={offsetY.scrubTo} - onScrubEnd={offsetY.commitScrub} - onReset={() => - offsetY.commitValue({ - value: DEFAULTS.text.background.offsetY, - }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: offsetY.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedOffsetY, - staticValue: bg.offsetY ?? DEFAULTS.text.background.offsetY, - defaultValue: DEFAULTS.text.background.offsetY, - })} - /> - </SectionField> - </div> - <SectionField - label="Corner radius" - beforeLabel={ - <KeyframeToggle - isActive={cornerRadius.isKeyframedAtTime} - isDisabled={!isPlayheadWithinElementRange} - title="Toggle corner radius keyframe" - onToggle={cornerRadius.toggleKeyframe} - /> - } - > - <NumberField - icon="R" - value={cornerRadius.displayValue} - min={CORNER_RADIUS_MIN} - max={CORNER_RADIUS_MAX} - onFocus={cornerRadius.onFocus} - onChange={cornerRadius.onChange} - onBlur={cornerRadius.onBlur} - onScrub={cornerRadius.scrubTo} - onScrubEnd={cornerRadius.commitScrub} - onReset={() => - cornerRadius.commitValue({ value: CORNER_RADIUS_MIN }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: cornerRadius.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedCornerRadius, - staticValue: bg.cornerRadius ?? CORNER_RADIUS_MIN, - defaultValue: CORNER_RADIUS_MIN, - })} - /> - </SectionField> - </SectionFields> - </SectionContent> - </Section> - ); -} diff --git a/apps/web/src/timeline/__tests__/update-pipeline.test.ts b/apps/web/src/timeline/__tests__/update-pipeline.test.ts index 94b32ced..e6d63bd6 100644 --- a/apps/web/src/timeline/__tests__/update-pipeline.test.ts +++ b/apps/web/src/timeline/__tests__/update-pipeline.test.ts @@ -1,66 +1,66 @@ -import { describe, expect, test } from "bun:test";
-import type { Transform } from "@/rendering";
-import type { SceneTracks, VideoElement } from "@/timeline";
-import { applyElementUpdate } from "@/timeline/update-pipeline";
-import { mediaTime, ZERO_MEDIA_TIME } from "@/wasm";
-
-function buildTransform(): Transform {
- return {
- scaleX: 1,
- scaleY: 1,
- position: { x: 0, y: 0 },
- rotate: 0,
- };
-}
-
-function buildVideoElement(overrides: Partial<VideoElement> = {}): VideoElement {
- return {
- id: "video-1",
- type: "video",
- name: "Video 1",
- startTime: ZERO_MEDIA_TIME,
- duration: mediaTime({ ticks: 10 }),
- trimStart: ZERO_MEDIA_TIME,
- trimEnd: ZERO_MEDIA_TIME,
- mediaId: "media-1",
- transform: buildTransform(),
- opacity: 1,
- ...overrides,
- };
-}
-
-function buildTracks(element: VideoElement): SceneTracks {
- return {
- overlay: [],
- main: {
- id: "main-track",
- type: "video",
- name: "Main",
- muted: false,
- hidden: false,
- elements: [element],
- },
- audio: [],
- };
-}
-
-describe("applyElementUpdate", () => {
- test("rounds retimed durations back to integer media time", () => {
- const element = buildVideoElement();
- const tracks = buildTracks(element);
-
- const updatedElement = applyElementUpdate({
- element,
- patch: {
- retime: { rate: 1.5 },
- },
- context: {
- tracks,
- trackId: tracks.main.id,
- },
- });
-
- expect(updatedElement.duration).toBe(7);
- expect(Number.isInteger(updatedElement.duration)).toBe(true);
- });
-});
+import { describe, expect, test } from "bun:test"; +import type { Transform } from "@/rendering"; +import type { SceneTracks, VideoElement } from "@/timeline"; +import { applyElementUpdate } from "@/timeline/update-pipeline"; +import { mediaTime, ZERO_MEDIA_TIME } from "@/wasm"; + +function buildTransform(): Transform { + return { + scaleX: 1, + scaleY: 1, + position: { x: 0, y: 0 }, + rotate: 0, + }; +} + +function buildVideoElement(overrides: Partial<VideoElement> = {}): VideoElement { + return { + id: "video-1", + type: "video", + name: "Video 1", + startTime: ZERO_MEDIA_TIME, + duration: mediaTime({ ticks: 10 }), + trimStart: ZERO_MEDIA_TIME, + trimEnd: ZERO_MEDIA_TIME, + mediaId: "media-1", + transform: buildTransform(), + opacity: 1, + ...overrides, + }; +} + +function buildTracks(element: VideoElement): SceneTracks { + return { + overlay: [], + main: { + id: "main-track", + type: "video", + name: "Main", + muted: false, + hidden: false, + elements: [element], + }, + audio: [], + }; +} + +describe("applyElementUpdate", () => { + test("rounds retimed durations back to integer media time", () => { + const element = buildVideoElement(); + const tracks = buildTracks(element); + + const updatedElement = applyElementUpdate({ + element, + patch: { + retime: { rate: 1.5 }, + }, + context: { + tracks, + trackId: tracks.main.id, + }, + }); + + expect(updatedElement.duration).toBe(7); + expect(Number.isInteger(updatedElement.duration)).toBe(true); + }); +}); diff --git a/apps/web/src/timeline/animation-properties.ts b/apps/web/src/timeline/animation-properties.ts deleted file mode 100644 index 308f6cf0..00000000 --- a/apps/web/src/timeline/animation-properties.ts +++ /dev/null @@ -1,426 +0,0 @@ -import type { - AnimationBindingKind, - AnimationInterpolation, - AnimationPropertyPath, - AnimationValue, - ElementAnimations, - NumericSpec, -} from "@/animation/types"; -import { upsertPathKeyframe } from "@/animation"; -import { parseColorToLinearRgba } from "@/animation/binding-values"; -import type { MediaTime } from "@/wasm"; -import { isAnimationPropertyPath } from "@/animation/path"; -import { MIN_TRANSFORM_SCALE } from "@/animation/transform"; -import { snapToStep } from "@/utils/math"; -import type { TimelineElement } from "@/timeline"; -import { - CORNER_RADIUS_MAX, - CORNER_RADIUS_MIN, -} from "@/text/background"; -import { - canElementHaveAudio, - isVisualElement, -} from "@/timeline/element-utils"; -import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/timeline/audio-constants"; -import { DEFAULTS } from "@/timeline/defaults"; - -export interface AnimationPropertyDefinition { - kind: AnimationBindingKind; - defaultInterpolation: AnimationInterpolation; - numericRanges?: Partial<Record<string, NumericSpec>>; - supportsElement: ({ element }: { element: TimelineElement }) => boolean; - getValue: ({ element }: { element: TimelineElement }) => AnimationValue | null; - coerceValue: ({ value }: { value: AnimationValue }) => AnimationValue | null; - setValue: ({ - element, - value, - }: { - element: TimelineElement; - value: AnimationValue; - }) => TimelineElement; -} - -function applyNumericSpec({ - value, - numericRange, -}: { - value: number; - numericRange: NumericSpec | undefined; -}): number { - if (!numericRange) { - return value; - } - - const steppedValue = - numericRange.step != null - ? snapToStep({ value, step: numericRange.step }) - : value; - const minValue = numericRange.min ?? Number.NEGATIVE_INFINITY; - const maxValue = numericRange.max ?? Number.POSITIVE_INFINITY; - return Math.min(maxValue, Math.max(minValue, steppedValue)); -} - -function coerceNumberValue({ - value, - numericRange, -}: { - value: AnimationValue; - numericRange?: NumericSpec; -}): number | null { - if (typeof value !== "number" || Number.isNaN(value)) { - return null; - } - - return applyNumericSpec({ value, numericRange }); -} - -function coerceColorValue({ - value, -}: { - value: AnimationValue; -}): string | null { - return typeof value === "string" && parseColorToLinearRgba({ color: value }) - ? value - : null; -} - -function createNumberPropertyDefinition({ - numericRange, - supportsElement, - getValue, - setValue, -}: { - numericRange?: NumericSpec; - supportsElement: AnimationPropertyDefinition["supportsElement"]; - getValue: AnimationPropertyDefinition["getValue"]; - setValue: AnimationPropertyDefinition["setValue"]; -}): AnimationPropertyDefinition { - return { - kind: "number", - defaultInterpolation: "linear", - numericRanges: numericRange ? { value: numericRange } : undefined, - supportsElement, - getValue, - coerceValue: ({ value }) => - coerceNumberValue({ - value, - numericRange, - }), - setValue, - }; -} - -const ANIMATION_PROPERTY_REGISTRY: Record< - AnimationPropertyPath, - AnimationPropertyDefinition -> = { - "transform.positionX": createNumberPropertyDefinition({ - numericRange: { step: 1 }, - supportsElement: ({ element }) => isVisualElement(element), - getValue: ({ element }) => - isVisualElement(element) ? element.transform.position.x : null, - setValue: ({ element, value }) => - isVisualElement(element) - ? { - ...element, - transform: { - ...element.transform, - position: { ...element.transform.position, x: value as number }, - }, - } - : element, - }), - "transform.positionY": createNumberPropertyDefinition({ - numericRange: { step: 1 }, - supportsElement: ({ element }) => isVisualElement(element), - getValue: ({ element }) => - isVisualElement(element) ? element.transform.position.y : null, - setValue: ({ element, value }) => - isVisualElement(element) - ? { - ...element, - transform: { - ...element.transform, - position: { ...element.transform.position, y: value as number }, - }, - } - : element, - }), - "transform.scaleX": createNumberPropertyDefinition({ - numericRange: { min: MIN_TRANSFORM_SCALE, step: 0.01 }, - supportsElement: ({ element }) => isVisualElement(element), - getValue: ({ element }) => - isVisualElement(element) ? element.transform.scaleX : null, - setValue: ({ element, value }) => - isVisualElement(element) - ? { - ...element, - transform: { ...element.transform, scaleX: value as number }, - } - : element, - }), - "transform.scaleY": createNumberPropertyDefinition({ - numericRange: { min: MIN_TRANSFORM_SCALE, step: 0.01 }, - supportsElement: ({ element }) => isVisualElement(element), - getValue: ({ element }) => - isVisualElement(element) ? element.transform.scaleY : null, - setValue: ({ element, value }) => - isVisualElement(element) - ? { - ...element, - transform: { ...element.transform, scaleY: value as number }, - } - : element, - }), - "transform.rotate": createNumberPropertyDefinition({ - numericRange: { min: -360, max: 360, step: 1 }, - supportsElement: ({ element }) => isVisualElement(element), - getValue: ({ element }) => - isVisualElement(element) ? element.transform.rotate : null, - setValue: ({ element, value }) => - isVisualElement(element) - ? { - ...element, - transform: { ...element.transform, rotate: value as number }, - } - : element, - }), - opacity: createNumberPropertyDefinition({ - numericRange: { min: 0, max: 1, step: 0.01 }, - supportsElement: ({ element }) => isVisualElement(element), - getValue: ({ element }) => - isVisualElement(element) ? element.opacity : null, - setValue: ({ element, value }) => - isVisualElement(element) - ? { ...element, opacity: value as number } - : element, - }), - volume: createNumberPropertyDefinition({ - numericRange: { min: VOLUME_DB_MIN, max: VOLUME_DB_MAX, step: 0.01 }, - supportsElement: ({ element }) => canElementHaveAudio(element), - getValue: ({ element }) => - canElementHaveAudio(element) ? element.volume ?? 0 : null, - setValue: ({ element, value }) => - canElementHaveAudio(element) - ? { ...element, volume: value as number } - : element, - }), - color: { - kind: "color", - defaultInterpolation: "linear", - supportsElement: ({ element }) => element.type === "text", - getValue: ({ element }) => (element.type === "text" ? element.color : null), - coerceValue: ({ value }) => coerceColorValue({ value }), - setValue: ({ element, value }) => - element.type === "text" - ? { ...element, color: value as string } - : element, - }, - "background.color": { - kind: "color", - defaultInterpolation: "linear", - supportsElement: ({ element }) => element.type === "text", - getValue: ({ element }) => - element.type === "text" ? element.background.color : null, - coerceValue: ({ value }) => coerceColorValue({ value }), - setValue: ({ element, value }) => - element.type === "text" - ? { - ...element, - background: { ...element.background, color: value as string }, - } - : element, - }, - "background.paddingX": createNumberPropertyDefinition({ - numericRange: { min: 0, step: 1 }, - supportsElement: ({ element }) => element.type === "text", - getValue: ({ element }) => - element.type === "text" - ? (element.background.paddingX ?? DEFAULTS.text.background.paddingX) - : null, - setValue: ({ element, value }) => - element.type === "text" - ? { - ...element, - background: { ...element.background, paddingX: value as number }, - } - : element, - }), - "background.paddingY": createNumberPropertyDefinition({ - numericRange: { min: 0, step: 1 }, - supportsElement: ({ element }) => element.type === "text", - getValue: ({ element }) => - element.type === "text" - ? (element.background.paddingY ?? DEFAULTS.text.background.paddingY) - : null, - setValue: ({ element, value }) => - element.type === "text" - ? { - ...element, - background: { ...element.background, paddingY: value as number }, - } - : element, - }), - "background.offsetX": createNumberPropertyDefinition({ - numericRange: { step: 1 }, - supportsElement: ({ element }) => element.type === "text", - getValue: ({ element }) => - element.type === "text" - ? (element.background.offsetX ?? DEFAULTS.text.background.offsetX) - : null, - setValue: ({ element, value }) => - element.type === "text" - ? { - ...element, - background: { ...element.background, offsetX: value as number }, - } - : element, - }), - "background.offsetY": createNumberPropertyDefinition({ - numericRange: { step: 1 }, - supportsElement: ({ element }) => element.type === "text", - getValue: ({ element }) => - element.type === "text" - ? (element.background.offsetY ?? DEFAULTS.text.background.offsetY) - : null, - setValue: ({ element, value }) => - element.type === "text" - ? { - ...element, - background: { ...element.background, offsetY: value as number }, - } - : element, - }), - "background.cornerRadius": createNumberPropertyDefinition({ - numericRange: { - min: CORNER_RADIUS_MIN, - max: CORNER_RADIUS_MAX, - step: 1, - }, - supportsElement: ({ element }) => element.type === "text", - getValue: ({ element }) => - element.type === "text" - ? (element.background.cornerRadius ?? CORNER_RADIUS_MIN) - : null, - setValue: ({ element, value }) => - element.type === "text" - ? { - ...element, - background: { ...element.background, cornerRadius: value as number }, - } - : element, - }), -}; - -export function getAnimationPropertyDefinition({ - propertyPath, -}: { - propertyPath: AnimationPropertyPath; -}): AnimationPropertyDefinition { - return ANIMATION_PROPERTY_REGISTRY[propertyPath]; -} - -export function supportsAnimationProperty({ - element, - propertyPath, -}: { - element: TimelineElement; - propertyPath: AnimationPropertyPath; -}): boolean { - const propertyDefinition = getAnimationPropertyDefinition({ propertyPath }); - return propertyDefinition.supportsElement({ element }); -} - -export function getElementBaseValueForProperty({ - element, - propertyPath, -}: { - element: TimelineElement; - propertyPath: AnimationPropertyPath; -}): AnimationValue | null { - const definition = getAnimationPropertyDefinition({ propertyPath }); - if (!definition.supportsElement({ element })) { - return null; - } - - return definition.getValue({ element }); -} - -export function withElementBaseValueForProperty({ - element, - propertyPath, - value, -}: { - element: TimelineElement; - propertyPath: AnimationPropertyPath; - value: AnimationValue; -}): TimelineElement { - const definition = getAnimationPropertyDefinition({ propertyPath }); - const coercedValue = definition.coerceValue({ value }); - if (coercedValue === null || !definition.supportsElement({ element })) { - return element; - } - - return definition.setValue({ element, value: coercedValue }); -} - -export function getDefaultInterpolationForProperty({ - propertyPath, -}: { - propertyPath: AnimationPropertyPath; -}): AnimationInterpolation { - const propertyDefinition = getAnimationPropertyDefinition({ propertyPath }); - return propertyDefinition.defaultInterpolation; -} - -export function coerceAnimationValueForProperty({ - propertyPath, - value, -}: { - propertyPath: AnimationPropertyPath; - value: AnimationValue; -}): AnimationValue | null { - const propertyDefinition = getAnimationPropertyDefinition({ propertyPath }); - return propertyDefinition.coerceValue({ value }); -} - -export function upsertElementKeyframe({ - animations, - propertyPath, - time, - value, - interpolation, - keyframeId, -}: { - animations: ElementAnimations | undefined; - propertyPath: AnimationPropertyPath; - time: MediaTime; - value: AnimationValue; - interpolation?: AnimationInterpolation; - keyframeId?: string; -}): ElementAnimations | undefined { - const coercedValue = coerceAnimationValueForProperty({ - propertyPath, - value, - }); - if (coercedValue === null) { - return animations; - } - - const propertyDefinition = getAnimationPropertyDefinition({ propertyPath }); - return upsertPathKeyframe({ - animations, - propertyPath, - time, - value: coercedValue, - interpolation, - keyframeId, - kind: propertyDefinition.kind, - defaultInterpolation: propertyDefinition.defaultInterpolation, - coerceValue: ({ value: nextValue }) => - coerceAnimationValueForProperty({ - propertyPath, - value: nextValue, - }), - }); -} diff --git a/apps/web/src/timeline/animation-targets.ts b/apps/web/src/timeline/animation-targets.ts index f9d53fcc..fe683f92 100644 --- a/apps/web/src/timeline/animation-targets.ts +++ b/apps/web/src/timeline/animation-targets.ts @@ -28,7 +28,7 @@ import { getAnimationPropertyDefinition, getElementBaseValueForProperty, withElementBaseValueForProperty, -} from "./animation-properties"; +} from "@/animation"; export interface AnimationPathDescriptor { kind: AnimationBindingKind; diff --git a/apps/web/src/timeline/components/audio-volume-line.tsx b/apps/web/src/timeline/components/audio-volume-line.tsx index 1baac2bd..ac2fd32a 100644 --- a/apps/web/src/timeline/components/audio-volume-line.tsx +++ b/apps/web/src/timeline/components/audio-volume-line.tsx @@ -242,8 +242,7 @@ export function AudioVolumeLine({ )} style={{ top: lineTop }} /> - {/* biome-ignore lint/a11y/noStaticElementInteractions: timeline volume line is a pointer-only editing surface */} - {/* biome-ignore lint/a11y/useKeyWithClickEvents: timeline volume line is a pointer-only editing surface */} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- custom drag widget for clip volume; pointer events drive the interaction, the click/mousedown handlers are propagation-stoppers. The a11y-correct long-term shape is <input type="range"> with a custom thumb. */} <div className="absolute inset-x-0 -translate-y-1/2 touch-none cursor-ns-resize pointer-events-auto" style={{ top: lineTop, height: `${HIT_AREA_HEIGHT_PX}px` }} diff --git a/apps/web/src/timeline/components/audio-waveform.tsx b/apps/web/src/timeline/components/audio-waveform.tsx index 7777bc09..87178f17 100644 --- a/apps/web/src/timeline/components/audio-waveform.tsx +++ b/apps/web/src/timeline/components/audio-waveform.tsx @@ -1,6 +1,7 @@ "use client"; import { useCallback, useEffect, useLayoutEffect, useRef } from "react"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { useResizeObserver } from "@/hooks/use-resize-observer"; import { TIMELINE_AUDIO_WAVEFORM_COLOR } from "./theme"; import { @@ -72,21 +73,19 @@ export function AudioWaveform({ const canvasRef = useRef<HTMLCanvasElement>(null); const containerRef = useRef<HTMLDivElement>(null); const summaryRef = useRef<SourceWaveformSummary | null>(null); - const gainSamplesRef = useRef<number[] | undefined>(gainSamples); - const pixelsPerSecondRef = useRef<number>(pixelsPerSecond); - const clipDurationSecRef = useRef<number>(clipDurationSec); - const retimeRef = useRef<RetimeConfig | undefined>(retime); - const sourceStartSecRef = useRef<number>(sourceStartSec); + const waveformConfigRef = useCommittedRef({ + gainSamples, + pixelsPerSecond, + clipDurationSec, + retime, + sourceStartSec, + color, + burnColor, + }); const scrollParentRef = useRef<HTMLElement | null>(null); const heightRef = useRef<number>(0); const lastRenderSignatureRef = useRef<string | null>(null); - gainSamplesRef.current = gainSamples; - pixelsPerSecondRef.current = pixelsPerSecond; - clipDurationSecRef.current = clipDurationSec; - retimeRef.current = retime; - sourceStartSecRef.current = sourceStartSec; - const clearCanvas = useCallback(() => { const canvas = canvasRef.current; if (!canvas) { @@ -142,13 +141,19 @@ export function AudioWaveform({ return; } + const { + gainSamples: gainSamplesValue, + pixelsPerSecond: pixelsPerSecondValue, + clipDurationSec: clipDurationSecValue, + retime: retimeValue, + sourceStartSec: sourceStartSecValue, + color: colorValue, + burnColor: burnColorValue, + } = waveformConfigRef.current; const dpr = window.devicePixelRatio || 1; const canvasW = Math.max(1, Math.ceil(visibleWidth * dpr)); const canvasH = Math.max(1, Math.round(height * dpr)); const barCount = Math.max(1, Math.floor(visibleWidth / BAR_STEP)); - const pixelsPerSecondValue = pixelsPerSecondRef.current; - const clipDurationSecValue = clipDurationSecRef.current; - const samples = gainSamplesRef.current; const renderSignature = JSON.stringify({ elementWidth, clipLeft, @@ -159,16 +164,16 @@ export function AudioWaveform({ barCount, dpr, clipDurationSec: clipDurationSecValue, - sourceStartSec: sourceStartSecRef.current, + sourceStartSec: sourceStartSecValue, pixelsPerSecond: pixelsPerSecondValue, - retime: retimeRef.current ?? null, + retime: retimeValue ?? null, summarySourceKey: summary.sourceKey, summarySampleRate: summary.sampleRate, summaryTotalSamples: summary.totalSamples, summaryBucketSize: summary.bucketSize, - gainSamples: samples ?? null, - color, - burnColor, + gainSamples: gainSamplesValue ?? null, + color: colorValue, + burnColor: burnColorValue, }); if (lastRenderSignatureRef.current === renderSignature) { return; @@ -190,8 +195,8 @@ export function AudioWaveform({ barCount, pixelsPerSecond: pixelsPerSecondValue, clipDurationSec: clipDurationSecValue, - sourceStartSec: sourceStartSecRef.current, - retime: retimeRef.current, + sourceStartSec: sourceStartSecValue, + retime: retimeValue, sampleRate: summary.sampleRate, maxSampleExclusive: summary.totalSamples, barStepPx: BAR_STEP, @@ -218,9 +223,9 @@ export function AudioWaveform({ Math.min(clipDurationSecValue, barCenterPx / pixelsPerSecondValue), ); const gain = - samples != null + gainSamplesValue != null ? sampleGainAtClipTime({ - samples, + samples: gainSamplesValue, clipTimeSec: clipCenterSec, clipDurationSec: clipDurationSecValue, }) @@ -243,7 +248,7 @@ export function AudioWaveform({ const deviceTop = Math.round((height - barH) * backingScaleY); const deviceHeight = Math.max(1, clipBottom - deviceTop); - ctx.fillStyle = color; + ctx.fillStyle = colorValue; ctx.fillRect( deviceLeft, deviceTop, @@ -253,7 +258,7 @@ export function AudioWaveform({ if (outputAmplitude > 1) { const burnHeight = Math.max(1, Math.round(BAR_WIDTH * backingScaleY)); - ctx.fillStyle = burnColor; + ctx.fillStyle = burnColorValue; ctx.fillRect( deviceLeft, deviceTop, @@ -262,7 +267,7 @@ export function AudioWaveform({ ); } } - }, [burnColor, clearCanvas, color]); + }, [clearCanvas, waveformConfigRef]); useEffect(() => { let isCancelled = false; @@ -296,7 +301,6 @@ export function AudioWaveform({ }; }, [audioBuffer, audioUrl, clearCanvas, drawVisible, sourceFile, sourceKey]); - // biome-ignore lint/correctness/useExhaustiveDependencies: these props are mirrored into refs during render, but the effect must still re-run to redraw when they change. useLayoutEffect(() => { drawVisible(); }, [ @@ -306,6 +310,8 @@ export function AudioWaveform({ clipDurationSec, retime, sourceStartSec, + color, + burnColor, ]); useEffect(() => { diff --git a/apps/web/src/timeline/components/graph-editor/bezier-graph.tsx b/apps/web/src/timeline/components/graph-editor/bezier-graph.tsx index 7d77b985..ff4219c5 100644 --- a/apps/web/src/timeline/components/graph-editor/bezier-graph.tsx +++ b/apps/web/src/timeline/components/graph-editor/bezier-graph.tsx @@ -2,6 +2,7 @@ import { useRef, useState, type PointerEvent } from "react"; import { useShiftKey } from "@/hooks/use-shift-key"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { getBezierPoint } from "@/animation/bezier"; import type { NormalizedCubicBezier } from "@/animation/types"; import { cn } from "@/utils/ui"; @@ -87,9 +88,7 @@ export function BezierGraph({ const svgRef = useRef<SVGSVGElement>(null); const [activeHandle, setActiveHandle] = useState<BezierHandle | null>(null); const isShiftPressedRef = useShiftKey(); - const latestValueRef = useRef(value); - - latestValueRef.current = value; + const latestValueRef = useCommittedRef(value); function getPointerPosition({ event, diff --git a/apps/web/src/timeline/components/index.tsx b/apps/web/src/timeline/components/index.tsx index 490ad1cc..8edbb6fc 100644 --- a/apps/web/src/timeline/components/index.tsx +++ b/apps/web/src/timeline/components/index.tsx @@ -29,6 +29,7 @@ import { useState, type ReactNode, } from "react"; +import { useContainerSize } from "@/hooks/use-container-size"; import type { MediaTime } from "@/wasm"; import type { ElementDragView, DropTarget } from "@/timeline"; import { TimelineTrackContent } from "./timeline-track"; @@ -80,6 +81,7 @@ import { useInitialScrollBottom } from "@/timeline/hooks/use-initial-scroll-bott import { useTimelineResize } from "@/timeline/hooks/use-timeline-resize"; import { useTimelineStore } from "@/timeline/timeline-store"; import { useEditor } from "@/editor/use-editor"; +import { useScrollPosition } from "@/timeline/hooks/use-scroll-position"; import { useTimelinePlayhead } from "@/timeline/hooks/use-timeline-playhead"; import { DragLine } from "./drag-line"; import { invokeAction } from "@/actions"; @@ -148,15 +150,25 @@ export function Timeline() { const [currentSnapPoint, setCurrentSnapPoint] = useState<SnapPoint | null>( null, ); + const { width: tracksContainerWidth } = useContainerSize({ + containerRef: tracksContainerRef, + }); + const { height: timelineHeaderHeightValue } = useContainerSize({ + containerRef: timelineHeaderRef, + }); + const { viewportWidth: tracksViewportWidth } = useScrollPosition({ + scrollRef: tracksScrollRef, + }); const handleSnapPointChange = useCallback((snapPoint: SnapPoint | null) => { setCurrentSnapPoint(snapPoint); }, []); const timelineDuration = timeline.getTotalDuration() || 0; + const containerWidth = tracksContainerWidth || FALLBACK_CONTAINER_WIDTH; const minZoomLevel = getTimelineZoomMin({ duration: timelineDuration, - containerWidth: tracksContainerRef.current?.clientWidth, + containerWidth, }); const savedViewState = editor.project.getTimelineViewState(); @@ -358,8 +370,6 @@ export function Timeline() { }, }); - const containerWidth = - tracksContainerRef.current?.clientWidth || FALLBACK_CONTAINER_WIDTH; const contentWidth = timelineTimeToPixels({ time: timelineDuration, zoomLevel, @@ -373,11 +383,8 @@ export function Timeline() { contentWidth + paddingPx, containerWidth, ); - const tracksViewportWidth = - tracksScrollRef.current?.clientWidth ?? - tracksContainerRef.current?.clientWidth ?? - containerWidth; - const hasHorizontalScrollbar = dynamicTimelineWidth > tracksViewportWidth; + const hasHorizontalScrollbar = + dynamicTimelineWidth > (tracksViewportWidth || containerWidth); useEdgeAutoScroll({ isActive: bookmarkDragState.isDragging, @@ -419,8 +426,7 @@ export function Timeline() { }); const timelineHeaderHeight = - (timelineHeaderRef.current?.getBoundingClientRect().height ?? 0) + - TIMELINE_CONTENT_TOP_PADDING_PX || 0; + timelineHeaderHeightValue + TIMELINE_CONTENT_TOP_PADDING_PX; return ( <section @@ -450,10 +456,7 @@ export function Timeline() { ref={tracksContainerRef} > <SelectionBox - startPos={selectionBox?.startPos || null} - currentPos={selectionBox?.currentPos || null} - containerRef={tracksContainerRef} - isActive={selectionBox?.isActive || false} + bounds={selectionBox?.bounds ?? null} /> <DragLine dropTarget={dropTarget} @@ -509,8 +512,7 @@ export function Timeline() { className="flex min-h-full flex-col" style={{ width: `${dynamicTimelineWidth}px` }} > - {/* biome-ignore lint/a11y/noStaticElementInteractions: canvas seek surface; keyboard seeking is handled by the global keybindings system */} - {/* biome-ignore lint/a11y/useKeyWithClickEvents: canvas seek surface; keyboard seeking is handled by the global keybindings system */} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- spatial gesture surface (tracks container background); direct-target clicks here originate box-select or clear selection. Keyboard control is global timeline shortcuts. */} <div className="relative shrink-0" style={{ @@ -891,9 +893,10 @@ function TimelineGutter({ onMouseDown: (event: React.MouseEvent) => void; onClick: (event: React.MouseEvent) => void; }) { - // biome-ignore lint/a11y/noStaticElementInteractions: canvas seek surface; keyboard seeking is handled by the global keybindings system - // biome-ignore lint/a11y/useKeyWithClickEvents: canvas seek surface; keyboard seeking is handled by the global keybindings system - return <div className="flex-1" onMouseDown={onMouseDown} onClick={onClick} />; + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- spatial gesture surface (empty space below tracks); clicks here clear selection. Keyboard control is global timeline shortcuts. + <div className="flex-1" onMouseDown={onMouseDown} onClick={onClick} /> + ); } function TrackIcon({ track }: { track: TimelineTrack }) { diff --git a/apps/web/src/timeline/components/timeline-element.tsx b/apps/web/src/timeline/components/timeline-element.tsx index cb102c71..be8409f3 100644 --- a/apps/web/src/timeline/components/timeline-element.tsx +++ b/apps/web/src/timeline/components/timeline-element.tsx @@ -31,6 +31,7 @@ import { ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; +import type { SelectionBoxBounds } from "@/selection/types"; import type { TimelineElement as TimelineElementType, TimelineTrack, @@ -51,7 +52,7 @@ import { getTimelinePixelsPerSecond } from "@/timeline"; import { buildWaveformSourceKey } from "@/media/waveform-summary"; import { addMediaTime, type MediaTime, TICKS_PER_SECOND } from "@/wasm"; import { - getActionDefinition, + getActionDefaultShortcuts, type TAction, type TActionWithOptionalArgs, invokeAction, @@ -185,7 +186,7 @@ export function getKeyframeIndicators({ } export function getDisplayShortcut({ action }: { action: TAction }) { - const { defaultShortcuts } = getActionDefinition({ action }); + const defaultShortcuts = getActionDefaultShortcuts({ action }); if (!defaultShortcuts?.length) { return ""; } @@ -206,14 +207,14 @@ interface TimelineElementProps { track: TimelineTrack; side: "left" | "right"; }) => void; - onElementMouseDown: ( - event: React.MouseEvent, - element: TimelineElementType, - ) => void; - onElementClick: ( - event: React.MouseEvent, - element: TimelineElementType, - ) => void; + onElementMouseDown: (params: { + event: React.MouseEvent; + element: TimelineElementType; + }) => void; + onElementClick: (params: { + event: React.MouseEvent; + element: TimelineElementType; + }) => void; dragView: ElementDragView; isDropTarget?: boolean; } @@ -530,14 +531,14 @@ function ElementInner({ isExpanded: boolean; baseTrackHeight: number; expandedContent: React.ReactNode; - onElementClick: ( - event: React.MouseEvent, - element: TimelineElementType, - ) => void; - onElementMouseDown: ( - event: React.MouseEvent, - element: TimelineElementType, - ) => void; + onElementClick: (params: { + event: React.MouseEvent; + element: TimelineElementType; + }) => void; + onElementMouseDown: (params: { + event: React.MouseEvent; + element: TimelineElementType; + }) => void; onResizeStart: (params: { event: React.MouseEvent; element: TimelineElementType; @@ -578,8 +579,8 @@ function ElementInner({ type="button" tabIndex={-1} className="absolute inset-0 size-full flex flex-col" - onClick={(event) => onElementClick(event, element)} - onMouseDown={(event) => onElementMouseDown(event, element)} + onClick={(event) => onElementClick({ event, element })} + onMouseDown={(event) => onElementMouseDown({ event, element })} > <div className={cn( @@ -770,9 +771,7 @@ function ExpandedKeyframeLanes({ onLaneMouseDown: (event: React.MouseEvent) => void; onLaneClick: (event: React.MouseEvent) => void; selectionBox: { - startPos: { x: number; y: number }; - currentPos: { x: number; y: number }; - isActive: boolean; + bounds: SelectionBoxBounds; } | null; isBoxSelecting: boolean; onKeyframeClick: (params: { @@ -808,8 +807,7 @@ function ExpandedKeyframeLanes({ ); return ( - // biome-ignore lint/a11y/noStaticElementInteractions: expanded keyframe lanes are a pointer-only editing surface - // biome-ignore lint/a11y/useKeyWithClickEvents: expanded keyframe lanes are a pointer-only editing surface + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- spatial gesture surface (keyframe lanes); keyboard control over keyframes is via global timeline shortcuts, not per-element focus. <div ref={containerRef} className="relative flex flex-col" @@ -893,14 +891,7 @@ function ExpandedKeyframeLanes({ </div> ); })} - {selectionBox && ( - <SelectionBox - startPos={selectionBox.startPos} - currentPos={selectionBox.currentPos} - containerRef={containerRef} - isActive={selectionBox.isActive} - /> - )} + {selectionBox && <SelectionBox bounds={selectionBox.bounds} />} </div> ); } @@ -1073,7 +1064,7 @@ function EffectsButton({ editor.selection.setSelectedElements({ elements: [{ trackId: track.id, elementId: element.id }], }); - setActiveTab(element.type, "effects"); + setActiveTab({ elementType: element.type, tabId: "effects" }); }; return ( diff --git a/apps/web/src/timeline/components/timeline-playhead.tsx b/apps/web/src/timeline/components/timeline-playhead.tsx index 5b1e45ab..6d77789e 100644 --- a/apps/web/src/timeline/components/timeline-playhead.tsx +++ b/apps/web/src/timeline/components/timeline-playhead.tsx @@ -1,11 +1,13 @@ "use client"; import { useRef } from "react"; +import { useContainerSize } from "@/hooks/use-container-size"; import { getCenteredLineLeft, TIMELINE_INDICATOR_LINE_WIDTH_PX, timelineTimeToSnappedPixels, } from "@/timeline"; +import { useScrollPosition } from "@/timeline/hooks/use-scroll-position"; import { useTimelinePlayhead } from "@/timeline/hooks/use-timeline-playhead"; import { addMediaTime, @@ -52,11 +54,14 @@ export function TimelinePlayhead({ tracksScrollRef, playheadRef, }); + const { height: timelineHeight } = useContainerSize({ containerRef: timelineRef }); + const { height: tracksHeight } = useContainerSize({ + containerRef: tracksScrollRef, + }); + const { scrollLeft } = useScrollPosition({ scrollRef: tracksScrollRef }); const timelineContainerHeight = - timelineRef.current?.clientHeight ?? - tracksScrollRef.current?.clientHeight ?? - 400; + timelineHeight || tracksHeight || 400; const totalHeight = Math.max( 0, timelineContainerHeight - @@ -68,7 +73,6 @@ export function TimelinePlayhead({ time: currentTime, zoomLevel, }); - const scrollLeft = tracksScrollRef.current?.scrollLeft ?? 0; const leftPosition = getCenteredLineLeft({ centerPixel: centerPosition }) - scrollLeft; diff --git a/apps/web/src/timeline/components/timeline-ruler.tsx b/apps/web/src/timeline/components/timeline-ruler.tsx index e2a379ee..ccacdae3 100644 --- a/apps/web/src/timeline/components/timeline-ruler.tsx +++ b/apps/web/src/timeline/components/timeline-ruler.tsx @@ -1,4 +1,4 @@ -import { type JSX, useLayoutEffect, useRef } from "react"; +import { type JSX } from "react"; import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale"; import { mediaTimeToSeconds } from "opencut-wasm"; import { TICKS_PER_SECOND } from "@/wasm"; @@ -51,20 +51,9 @@ export function TimelineRuler({ scrollRef: tracksScrollRef, }); - /** - * widens the virtualization buffer during zoom transitions. - * useScrollPosition lags one frame behind the scroll adjustment - * that useLayoutEffect applies after a zoom change. - */ - const prevZoomRef = useRef(zoomLevel); - const isZoomTransition = zoomLevel !== prevZoomRef.current; - const bufferPx = isZoomTransition - ? Math.max(200, (scrollLeft + viewportWidth) * 0.15) - : 200; - - useLayoutEffect(() => { - prevZoomRef.current = zoomLevel; - }, [zoomLevel]); + // Keep extra buffer because zoom layout and scroll position can briefly + // settle on different frames. + const bufferPx = Math.max(200, (scrollLeft + viewportWidth) * 0.15); const visibleStartTimeSeconds = Math.max( 0, diff --git a/apps/web/src/timeline/components/timeline-track.tsx b/apps/web/src/timeline/components/timeline-track.tsx index 7608e6df..2603f511 100644 --- a/apps/web/src/timeline/components/timeline-track.tsx +++ b/apps/web/src/timeline/components/timeline-track.tsx @@ -62,7 +62,7 @@ export function TimelineTrackContent({ onTrackMouseDown?.(event); }} /> - {/* biome-ignore lint/a11y/noStaticElementInteractions: empty track area is a pointer-only seek surface */} + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- spatial gesture surface; the wrapping <button> handles keyboard track selection, this <div> only forwards background clicks for box-select / deselect. */} <div className="relative h-full min-w-full" style={{ zIndex: TIMELINE_LAYERS.trackContent }} @@ -96,10 +96,10 @@ export function TimelineTrackContent({ onResizeStart={({ event, element, side }) => onResizeStart({ event, element, track, side }) } - onElementMouseDown={(event, element) => + onElementMouseDown={({ event, element }) => onElementMouseDown({ event, element, track }) } - onElementClick={(event, element) => + onElementClick={({ event, element }) => onElementClick({ event, element, track }) } dragView={dragView} diff --git a/apps/web/src/timeline/controllers/drag-drop-controller.ts b/apps/web/src/timeline/controllers/drag-drop-controller.ts index 7fca2676..e0624a74 100644 --- a/apps/web/src/timeline/controllers/drag-drop-controller.ts +++ b/apps/web/src/timeline/controllers/drag-drop-controller.ts @@ -326,7 +326,10 @@ export class DragDropController { trackType: TrackType; }): void { if (target.isNewTrack) { - const addTrackCmd = new AddTrackCommand(trackType, target.trackIndex); + const addTrackCmd = new AddTrackCommand({ + type: trackType, + index: target.trackIndex, + }); this.config.executeCommand( new BatchCommand([ addTrackCmd, diff --git a/apps/web/src/timeline/controllers/element-interaction-controller.ts b/apps/web/src/timeline/controllers/element-interaction-controller.ts index d0d6426a..5afc3a93 100644 --- a/apps/web/src/timeline/controllers/element-interaction-controller.ts +++ b/apps/web/src/timeline/controllers/element-interaction-controller.ts @@ -124,20 +124,27 @@ const IDLE_VIEW: ElementDragView = { kind: "idle" }; // --- Pure helpers --- -function pixelToClickOffsetTime( - clientX: number, - elementRect: DOMRect, - zoomLevel: number, -): MediaTime { +function pixelToClickOffsetTime({ + clientX, + elementRect, + zoomLevel, +}: { + clientX: number; + elementRect: DOMRect; + zoomLevel: number; +}): MediaTime { const clickOffsetX = clientX - elementRect.left; const seconds = clickOffsetX / (BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel); return mediaTime({ ticks: Math.round(seconds * TICKS_PER_SECOND) }); } -function verticalDirection( - startMouseY: number, - currentMouseY: number, -): "up" | "down" | null { +function verticalDirection({ + startMouseY, + currentMouseY, +}: { + startMouseY: number; + currentMouseY: number; +}): "up" | "down" | null { if (currentMouseY < startMouseY) return "up"; if (currentMouseY > startMouseY) return "down"; return null; @@ -147,7 +154,13 @@ function orderedTracks(sceneTracks: SceneTracks): TimelineTrack[] { return [...sceneTracks.overlay, sceneTracks.main, ...sceneTracks.audio]; } -function movedPastDragThreshold(current: Point, origin: Point): boolean { +function movedPastDragThreshold({ + current, + origin, +}: { + current: Point; + origin: Point; +}): boolean { return ( Math.abs(current.x - origin.x) > TIMELINE_DRAG_THRESHOLD_PX || Math.abs(current.y - origin.y) > TIMELINE_DRAG_THRESHOLD_PX @@ -374,11 +387,11 @@ export class ElementInteractionController { elementId: element.id, trackId: track.id, startElementTime: element.startTime, - clickOffsetTime: pixelToClickOffsetTime( - event.clientX, - event.currentTarget.getBoundingClientRect(), - this.deps.viewport.getZoomLevel(), - ), + clickOffsetTime: pixelToClickOffsetTime({ + clientX: event.clientX, + elementRect: event.currentTarget.getBoundingClientRect(), + zoomLevel: this.deps.viewport.getZoomLevel(), + }), selectedElements, }, }; @@ -437,10 +450,13 @@ export class ElementInteractionController { this.notify(); } - private snapResult( - frameSnappedTime: MediaTime, - group: MoveGroup, - ): { snappedTime: MediaTime; snapPoint: SnapPoint | null } { + private snapResult({ + frameSnappedTime, + group, + }: { + frameSnappedTime: MediaTime; + group: MoveGroup; + }): { snappedTime: MediaTime; snapPoint: SnapPoint | null } { const { snap, input, scene, viewport, playback } = this.deps; if (!snap.isEnabled() || input.isShiftHeld()) { @@ -487,7 +503,10 @@ export class ElementInteractionController { viewport, zoomLevel, snappedTime, - verticalDragDirection: verticalDirection(mousedown.origin.y, clientY), + verticalDragDirection: verticalDirection({ + startMouseY: mousedown.origin.y, + currentMouseY: clientY, + }), }); const nextGroupMoveResult = anchorDropTarget @@ -543,7 +562,12 @@ export class ElementInteractionController { clientY: number; scrollContainer: HTMLDivElement; }): void { - if (!movedPastDragThreshold({ x: clientX, y: clientY }, mousedown.origin)) { + if ( + !movedPastDragThreshold({ + current: { x: clientX, y: clientY }, + origin: mousedown.origin, + }) + ) { return; } @@ -568,10 +592,10 @@ export class ElementInteractionController { clickOffsetTime: mousedown.clickOffsetTime, fps, }); - const { snappedTime, snapPoint } = this.snapResult( + const { snappedTime, snapPoint } = this.snapResult({ frameSnappedTime, - moveGroup, - ); + group: moveGroup, + }); // Ensure the anchor is selected before we render the drag — covers the // case where the selection store hasn't committed the mousedown-time @@ -632,10 +656,10 @@ export class ElementInteractionController { clickOffsetTime: mousedown.clickOffsetTime, fps, }); - const { snappedTime, snapPoint } = this.snapResult( + const { snappedTime, snapPoint } = this.snapResult({ frameSnappedTime, - drag.moveGroup, - ); + group: drag.moveGroup, + }); drag.currentTime = snappedTime; drag.currentMouseX = clientX; @@ -666,7 +690,12 @@ export class ElementInteractionController { // If the drag returned within the click threshold of its origin, treat // this as a cancel rather than a commit — the user dragged then put the // element back. - if (!movedPastDragThreshold({ x: clientX, y: clientY }, mousedown.origin)) { + if ( + !movedPastDragThreshold({ + current: { x: clientX, y: clientY }, + origin: mousedown.origin, + }) + ) { this.lastGestureWasDrag = false; this.finishSession(); return; diff --git a/apps/web/src/timeline/controllers/resize-controller.ts b/apps/web/src/timeline/controllers/resize-controller.ts index e8cbaa80..a1098915 100644 --- a/apps/web/src/timeline/controllers/resize-controller.ts +++ b/apps/web/src/timeline/controllers/resize-controller.ts @@ -263,10 +263,13 @@ export class ResizeController { this.notify(); } - private snappedDelta( - session: ResizeSession, - rawDeltaTime: MediaTime, - ): MediaTime { + private snappedDelta({ + session, + rawDeltaTime, + }: { + session: ResizeSession; + rawDeltaTime: MediaTime; + }): MediaTime { const { snappingEnabled, isShiftHeld, zoomLevel } = this.config; if (!snappingEnabled || isShiftHeld()) { @@ -330,7 +333,7 @@ export class ResizeController { TICKS_PER_SECOND, ), }); - const deltaTime = this.snappedDelta(session, rawDeltaTime); + const deltaTime = this.snappedDelta({ session, rawDeltaTime }); const result = computeGroupResize({ members: session.members, side: session.side, diff --git a/apps/web/src/timeline/controllers/zoom-controller.ts b/apps/web/src/timeline/controllers/zoom-controller.ts index c5090e16..91147354 100644 --- a/apps/web/src/timeline/controllers/zoom-controller.ts +++ b/apps/web/src/timeline/controllers/zoom-controller.ts @@ -25,7 +25,13 @@ export interface ZoomConfigRef { readonly current: ZoomConfig; } -function clampZoom(zoomLevel: number, minZoom: number): number { +function clampZoom({ + zoomLevel, + minZoom, +}: { + zoomLevel: number; + minZoom: number; +}): number { return Math.max(minZoom, Math.min(TIMELINE_ZOOM_MAX, zoomLevel)); } @@ -49,7 +55,7 @@ export class ZoomController { const minZoom = this.config.minZoom; this.zoomLevelValue = deps.initialZoom !== undefined - ? clampZoom(deps.initialZoom, minZoom) + ? clampZoom({ zoomLevel: deps.initialZoom, minZoom }) : minZoom; this.previousZoom = this.zoomLevelValue; this.hasInitialized = deps.initialZoom !== undefined; @@ -89,7 +95,10 @@ export class ZoomController { typeof zoomLevelOrUpdater === "function" ? zoomLevelOrUpdater(this.zoomLevelValue) : zoomLevelOrUpdater; - const nextZoom = clampZoom(nextZoomRaw, this.config.minZoom); + const nextZoom = clampZoom({ + zoomLevel: nextZoomRaw, + minZoom: this.config.minZoom, + }); if (nextZoom === this.zoomLevelValue) return; this.zoomLevelValue = nextZoom; @@ -115,10 +124,16 @@ export class ZoomController { } } - reconcileInitialAndMinZoom(minZoom: number, initialZoom?: number): void { + reconcileInitialAndMinZoom({ + minZoom, + initialZoom, + }: { + minZoom: number; + initialZoom?: number; + }): void { if (initialZoom !== undefined && !this.hasInitialized) { this.hasInitialized = true; - this.setZoomLevel(clampZoom(initialZoom, minZoom)); + this.setZoomLevel(clampZoom({ zoomLevel: initialZoom, minZoom })); return; } diff --git a/apps/web/src/timeline/hooks/element/use-element-interaction.ts b/apps/web/src/timeline/hooks/element/use-element-interaction.ts index 26a21f85..220f3fec 100644 --- a/apps/web/src/timeline/hooks/element/use-element-interaction.ts +++ b/apps/web/src/timeline/hooks/element/use-element-interaction.ts @@ -1,5 +1,6 @@ -import { useEffect, useReducer, useRef, type RefObject } from "react"; +import { useEffect, useReducer, useState, type RefObject } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { useShiftKey } from "@/hooks/use-shift-key"; import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { registerCanceller } from "@/editor/cancel-interaction"; @@ -63,17 +64,10 @@ export function useElementInteraction({ onChange: onSnapPointChange, }, }; - - const depsRef = useRef<ElementInteractionDeps>(deps); - depsRef.current = deps; - - const controllerRef = useRef<ElementInteractionController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new ElementInteractionController({ - depsRef: depsRef as ElementInteractionDepsRef, - }); - } - const controller = controllerRef.current; + const depsRef = useCommittedRef(deps) as ElementInteractionDepsRef; + const [controller] = useState( + () => new ElementInteractionController({ depsRef }), + ); const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect(() => controller.subscribe(rerender), [controller]); diff --git a/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts b/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts index ea03cb5c..141bba8f 100644 --- a/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts +++ b/apps/web/src/timeline/hooks/element/use-keyframe-drag.ts @@ -1,5 +1,6 @@ -import { useEffect, useReducer, useRef } from "react"; +import { useEffect, useReducer, useState } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { useKeyframeSelection } from "./use-keyframe-selection"; import { registerCanceller } from "@/editor/cancel-interaction"; import { @@ -44,15 +45,10 @@ export function useKeyframeDrag({ seek: ({ time }) => editor.playback.seek({ time }), getTotalDuration: () => editor.timeline.getTotalDuration(), }; - - const configRef = useRef<KeyframeDragConfig>(config); - configRef.current = config; - - const controllerRef = useRef<KeyframeDragController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new KeyframeDragController({ configRef }); - } - const controller = controllerRef.current; + const configRef = useCommittedRef(config); + const [controller] = useState( + () => new KeyframeDragController({ configRef }), + ); const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect(() => controller.subscribe(rerender), [controller]); diff --git a/apps/web/src/timeline/hooks/use-snap-indicator-position.ts b/apps/web/src/timeline/hooks/use-snap-indicator-position.ts index 2081b939..ac6d91eb 100644 --- a/apps/web/src/timeline/hooks/use-snap-indicator-position.ts +++ b/apps/web/src/timeline/hooks/use-snap-indicator-position.ts @@ -1,6 +1,7 @@ -import { useEffect, useState } from "react";
+import { useContainerSize } from "@/hooks/use-container-size";
import { timelineTimeToSnappedPixels } from "@/timeline";
import { TIMELINE_TRACK_LABELS_COLUMN_WIDTH_PX } from "@/timeline/components/layout";
+import { useScrollPosition } from "./use-scroll-position";
interface UseSnapIndicatorPositionParams {
snapPoint: { time: number } | null;
zoomLevel: number;
@@ -20,24 +21,9 @@ export function useSnapIndicatorPosition({ timelineRef,
tracksScrollRef,
}: UseSnapIndicatorPositionParams): SnapIndicatorPosition {
- const [scrollLeft, setScrollLeft] = useState(0);
-
- useEffect(() => {
- const tracksViewport = tracksScrollRef.current;
-
- if (!tracksViewport) return;
-
- const handleScroll = () => {
- setScrollLeft(tracksViewport.scrollLeft);
- };
-
- setScrollLeft(tracksViewport.scrollLeft);
-
- tracksViewport.addEventListener("scroll", handleScroll);
- return () => tracksViewport.removeEventListener("scroll", handleScroll);
- }, [tracksScrollRef]);
-
- const timelineContainerHeight = timelineRef.current?.offsetHeight || 400;
+ const { height: timelineHeight } = useContainerSize({ containerRef: timelineRef });
+ const { scrollLeft } = useScrollPosition({ scrollRef: tracksScrollRef });
+ const timelineContainerHeight = timelineHeight || 400;
const totalHeight = timelineContainerHeight - 8; // 8px padding from edges
const timelinePosition = timelineTimeToSnappedPixels({
diff --git a/apps/web/src/timeline/hooks/use-timeline-drag-drop.ts b/apps/web/src/timeline/hooks/use-timeline-drag-drop.ts index 4ec2b80b..c405819e 100644 --- a/apps/web/src/timeline/hooks/use-timeline-drag-drop.ts +++ b/apps/web/src/timeline/hooks/use-timeline-drag-drop.ts @@ -1,5 +1,6 @@ -import { useEffect, useReducer, useRef, type RefObject } from "react"; +import { useEffect, useReducer, useState, type RefObject } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { DragDropController, type DragDropConfig, @@ -37,15 +38,8 @@ export function useTimelineDragDrop({ insertElement: (args) => editor.timeline.insertElement(args), addClipEffect: (args) => editor.timeline.addClipEffect(args), }; - - const configRef = useRef<DragDropConfig>(config); - configRef.current = config; - - const controllerRef = useRef<DragDropController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new DragDropController({ configRef }); - } - const controller = controllerRef.current; + const configRef = useCommittedRef(config); + const [controller] = useState(() => new DragDropController({ configRef })); const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect(() => controller.subscribe(rerender), [controller]); diff --git a/apps/web/src/timeline/hooks/use-timeline-playhead.ts b/apps/web/src/timeline/hooks/use-timeline-playhead.ts index 15ef3575..9f10c4a5 100644 --- a/apps/web/src/timeline/hooks/use-timeline-playhead.ts +++ b/apps/web/src/timeline/hooks/use-timeline-playhead.ts @@ -1,5 +1,6 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useState } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { useShiftKey } from "@/hooks/use-shift-key"; import { useEdgeAutoScroll } from "@/timeline/hooks/use-edge-auto-scroll"; import { timelineTimeToPixels } from "@/timeline"; @@ -54,14 +55,8 @@ export function useTimelinePlayhead({ }, }), }; - const configRef = useRef(config); - configRef.current = config; - - const ctrlRef = useRef<PlayheadController | null>(null); - if (!ctrlRef.current) { - ctrlRef.current = new PlayheadController({ configRef }); - } - const ctrl = ctrlRef.current; + const configRef = useCommittedRef(config); + const [ctrl] = useState(() => new PlayheadController({ configRef })); // Scroll → keep playhead position in sync with scroll offset. useEffect(() => { diff --git a/apps/web/src/timeline/hooks/use-timeline-resize.ts b/apps/web/src/timeline/hooks/use-timeline-resize.ts index 3579bd75..6bd0c606 100644 --- a/apps/web/src/timeline/hooks/use-timeline-resize.ts +++ b/apps/web/src/timeline/hooks/use-timeline-resize.ts @@ -1,5 +1,6 @@ -import { useEffect, useReducer, useRef } from "react"; +import { useEffect, useReducer, useState } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { useShiftKey } from "@/hooks/use-shift-key"; import { useElementSelection } from "@/timeline/hooks/element/use-element-selection"; import { useTimelineStore } from "@/timeline/timeline-store"; @@ -55,15 +56,8 @@ export function useTimelineResize({ }), onSnapPointChange, }; - - const configRef = useRef(config); - configRef.current = config; - - const controllerRef = useRef<ResizeController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new ResizeController({ configRef }); - } - const controller = controllerRef.current; + const configRef = useCommittedRef(config); + const [controller] = useState(() => new ResizeController({ configRef })); const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect(() => controller.subscribe(rerender), [controller]); diff --git a/apps/web/src/timeline/hooks/use-timeline-seek.ts b/apps/web/src/timeline/hooks/use-timeline-seek.ts index bf033dae..ea9b44a7 100644 --- a/apps/web/src/timeline/hooks/use-timeline-seek.ts +++ b/apps/web/src/timeline/hooks/use-timeline-seek.ts @@ -1,5 +1,6 @@ -import { useEffect, useRef, type RefObject } from "react"; +import { useEffect, useState, type RefObject } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { SeekController, type SeekConfig, @@ -50,15 +51,8 @@ export function useTimelineSeek({ }, }), }; - - const configRef = useRef(config); - configRef.current = config; - - const controllerRef = useRef<SeekController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new SeekController({ configRef }); - } - const controller = controllerRef.current; + const configRef = useCommittedRef(config); + const [controller] = useState(() => new SeekController({ configRef })); useEffect(() => () => controller.destroy(), [controller]); diff --git a/apps/web/src/timeline/hooks/use-timeline-zoom.ts b/apps/web/src/timeline/hooks/use-timeline-zoom.ts index 3020504f..60768963 100644 --- a/apps/web/src/timeline/hooks/use-timeline-zoom.ts +++ b/apps/web/src/timeline/hooks/use-timeline-zoom.ts @@ -4,9 +4,10 @@ import { useEffect, useLayoutEffect, useReducer, - useRef, + useState, } from "react"; import { useEditor } from "@/editor/use-editor"; +import { useCommittedRef } from "@/hooks/use-committed-ref"; import { TIMELINE_ZOOM_MIN } from "@/timeline/scale"; import { ZoomController, @@ -57,21 +58,17 @@ export function useTimelineZoom({ }, }), }; - const configRef = useRef(config); - configRef.current = config; - - const controllerRef = useRef<ZoomController | null>(null); - if (!controllerRef.current) { - controllerRef.current = new ZoomController({ configRef, initialZoom }); - } - const controller = controllerRef.current; + const configRef = useCommittedRef(config); + const [controller] = useState( + () => new ZoomController({ configRef, initialZoom }), + ); const zoomLevel = controller.zoomLevel; const [, rerender] = useReducer((n: number) => n + 1, 0); useEffect(() => controller.subscribe(rerender), [controller]); useEffect(() => { - controller.reconcileInitialAndMinZoom(minZoom, initialZoom); + controller.reconcileInitialAndMinZoom({ minZoom, initialZoom }); }, [controller, minZoom, initialZoom]); useLayoutEffect(() => { diff --git a/apps/web/src/timeline/index.ts b/apps/web/src/timeline/index.ts index 3775e006..21944561 100644 --- a/apps/web/src/timeline/index.ts +++ b/apps/web/src/timeline/index.ts @@ -1,4 +1,4 @@ -import { type MediaTime, ZERO_MEDIA_TIME } from "@/wasm"; +import { addMediaTime, type MediaTime, ZERO_MEDIA_TIME } from "@/wasm"; import type { SceneTracks } from "./types"; export * from "./types"; @@ -22,9 +22,10 @@ export function calculateTotalDuration({ let maxEnd: MediaTime = ZERO_MEDIA_TIME; for (const track of orderedTracks) { for (const element of track.elements) { - // `startTime + duration` is integer-by-construction (both `MediaTime`), - // so the cast re-establishes the brand without runtime work. - const elementEnd = (element.startTime + element.duration) as MediaTime; + const elementEnd = addMediaTime({ + a: element.startTime, + b: element.duration, + }); if (elementEnd > maxEnd) maxEnd = elementEnd; } } diff --git a/apps/web/src/timeline/types.ts b/apps/web/src/timeline/types.ts index 99df3128..e84a4f4e 100644 --- a/apps/web/src/timeline/types.ts +++ b/apps/web/src/timeline/types.ts @@ -1,332 +1,332 @@ -import type { ElementAnimations } from "@/animation/types";
-import type { Effect } from "@/effects/types";
-import type { Mask } from "@/masks/types";
-import type { ParamValues } from "@/params";
-import type { BlendMode, Transform } from "@/rendering";
-import type { MediaTime } from "@/wasm";
-
-export type ElementRef = {
- trackId: string;
- elementId: string;
-};
-
-export interface Bookmark {
- time: MediaTime;
- note?: string;
- color?: string;
- duration?: MediaTime;
-}
-
-export interface TScene {
- id: string;
- name: string;
- isMain: boolean;
- tracks: SceneTracks;
- bookmarks: Bookmark[];
- createdAt: Date;
- updatedAt: Date;
-}
-
-export type TrackType = "video" | "text" | "audio" | "graphic" | "effect";
-
-interface BaseTrack {
- id: string;
- name: string;
-}
-
-export interface VideoTrack extends BaseTrack {
- type: "video";
- elements: (VideoElement | ImageElement)[];
- muted: boolean;
- hidden: boolean;
-}
-
-export interface TextTrack extends BaseTrack {
- type: "text";
- elements: TextElement[];
- hidden: boolean;
-}
-
-export interface AudioTrack extends BaseTrack {
- type: "audio";
- elements: AudioElement[];
- muted: boolean;
-}
-
-export interface GraphicTrack extends BaseTrack {
- type: "graphic";
- elements: (StickerElement | GraphicElement)[];
- hidden: boolean;
-}
-
-export interface EffectTrack extends BaseTrack {
- type: "effect";
- elements: EffectElement[];
- hidden: boolean;
-}
-
-export type TimelineTrack =
- | VideoTrack
- | TextTrack
- | AudioTrack
- | GraphicTrack
- | EffectTrack;
-
-export type OverlayTrack = VideoTrack | TextTrack | GraphicTrack | EffectTrack;
-
-export interface SceneTracks {
- overlay: OverlayTrack[];
- main: VideoTrack;
- audio: AudioTrack[];
-}
-
-export interface RetimeConfig {
- rate: number;
- maintainPitch?: boolean;
-}
-
-interface BaseAudioElement extends BaseTimelineElement {
- type: "audio";
- volume: number;
- muted?: boolean;
- buffer?: AudioBuffer;
- retime?: RetimeConfig;
-}
-
-export interface UploadAudioElement extends BaseAudioElement {
- sourceType: "upload";
- mediaId: string;
-}
-
-export interface LibraryAudioElement extends BaseAudioElement {
- sourceType: "library";
- sourceUrl: string;
-}
-
-export type AudioElement = UploadAudioElement | LibraryAudioElement;
-
-interface BaseTimelineElement {
- id: string;
- name: string;
- duration: MediaTime;
- startTime: MediaTime;
- trimStart: MediaTime;
- trimEnd: MediaTime;
- sourceDuration?: MediaTime;
- animations?: ElementAnimations;
-}
-
-export interface VideoElement extends BaseTimelineElement {
- type: "video";
- mediaId: string;
- volume?: number;
- muted?: boolean;
- isSourceAudioEnabled?: boolean;
- hidden?: boolean;
- retime?: RetimeConfig;
- transform: Transform;
- opacity: number;
- blendMode?: BlendMode;
- effects?: Effect[];
- masks?: Mask[];
-}
-
-export interface ImageElement extends BaseTimelineElement {
- type: "image";
- mediaId: string;
- hidden?: boolean;
- transform: Transform;
- opacity: number;
- blendMode?: BlendMode;
- effects?: Effect[];
- masks?: Mask[];
-}
-
-export interface TextBackground {
- enabled: boolean;
- color: string;
- cornerRadius?: number;
- paddingX?: number;
- paddingY?: number;
- offsetX?: number;
- offsetY?: number;
-}
-
-export interface TextElement extends BaseTimelineElement {
- type: "text";
- content: string;
- fontSize: number;
- fontFamily: string;
- color: string;
- background: TextBackground;
- textAlign: "left" | "center" | "right";
- fontWeight: "normal" | "bold";
- fontStyle: "normal" | "italic";
- textDecoration: "none" | "underline" | "line-through";
- letterSpacing?: number;
- lineHeight?: number;
- hidden?: boolean;
- transform: Transform;
- opacity: number;
- blendMode?: BlendMode;
- effects?: Effect[];
-}
-
-export interface StickerElement extends BaseTimelineElement {
- type: "sticker";
- stickerId: string;
- /** Natural dimensions of the sticker asset, stored at insert time. Used by renderer and preview bounds to avoid split-brain geometry. */
- intrinsicWidth?: number;
- intrinsicHeight?: number;
- hidden?: boolean;
- transform: Transform;
- opacity: number;
- blendMode?: BlendMode;
- effects?: Effect[];
-}
-
-export interface GraphicElement extends BaseTimelineElement {
- type: "graphic";
- definitionId: string;
- params: ParamValues;
- hidden?: boolean;
- transform: Transform;
- opacity: number;
- blendMode?: BlendMode;
- effects?: Effect[];
- masks?: Mask[];
-}
-
-export interface EffectElement extends BaseTimelineElement {
- type: "effect";
- effectType: string;
- params: ParamValues;
-}
-
-export type ElementUpdatePatch =
- | { transform: Transform }
- | { opacity: number }
- | { volume: number };
-
-export type TimelineElement =
- | AudioElement
- | VideoElement
- | ImageElement
- | TextElement
- | StickerElement
- | GraphicElement
- | EffectElement;
-
-export type ElementType = TimelineElement["type"];
-
-function elementTypes<T extends ElementType[]>(...types: T): T {
- return types;
-}
-
-export const MASKABLE_ELEMENT_TYPES = elementTypes("video", "image", "graphic");
-
-export type MaskableElement = Extract<
- TimelineElement,
- { type: (typeof MASKABLE_ELEMENT_TYPES)[number] }
->;
-
-export const RETIMABLE_ELEMENT_TYPES = elementTypes("video", "audio");
-
-export type RetimableElement = Extract<
- TimelineElement,
- { type: (typeof RETIMABLE_ELEMENT_TYPES)[number] }
->;
-
-export const VISUAL_ELEMENT_TYPES = elementTypes(
- "video",
- "image",
- "text",
- "sticker",
- "graphic",
-);
-
-export type VisualElement = Extract<
- TimelineElement,
- { type: (typeof VISUAL_ELEMENT_TYPES)[number] }
->;
-
-export type CreateUploadAudioElement = Omit<UploadAudioElement, "id">;
-export type CreateLibraryAudioElement = Omit<LibraryAudioElement, "id">;
-export type CreateAudioElement =
- | CreateUploadAudioElement
- | CreateLibraryAudioElement;
-export type CreateVideoElement = Omit<VideoElement, "id">;
-export type CreateImageElement = Omit<ImageElement, "id">;
-export type CreateTextElement = Omit<TextElement, "id">;
-export type CreateStickerElement = Omit<StickerElement, "id">;
-export type CreateGraphicElement = Omit<GraphicElement, "id">;
-export type CreateEffectElement = Omit<EffectElement, "id">;
-export type CreateTimelineElement =
- | CreateAudioElement
- | CreateVideoElement
- | CreateImageElement
- | CreateTextElement
- | CreateStickerElement
- | CreateGraphicElement
- | CreateEffectElement;
-
-export interface ElementDragState {
- isDragging: boolean;
- elementId: string | null;
- dragElementIds: string[];
- dragTimeOffsets: Record<string, MediaTime>;
- trackId: string | null;
- startMouseX: number;
- startMouseY: number;
- startElementTime: MediaTime;
- clickOffsetTime: MediaTime;
- currentTime: MediaTime;
- currentMouseY: number;
-}
-
-export type ElementDragView =
- | { readonly kind: "idle" }
- | {
- readonly kind: "dragging";
- readonly anchorElementId: string;
- readonly trackId: string;
- readonly memberTimeOffsets: ReadonlyMap<string, MediaTime>;
- readonly startMouseX: number;
- readonly startMouseY: number;
- readonly startElementTime: MediaTime;
- readonly clickOffsetTime: MediaTime;
- readonly currentTime: MediaTime;
- readonly currentMouseX: number;
- readonly currentMouseY: number;
- readonly dropTarget: DropTarget | null;
- };
-
-export interface DropTarget {
- trackIndex: number;
- isNewTrack: boolean;
- insertPosition: "above" | "below" | null;
- xPosition: MediaTime;
- targetElement: { elementId: string; trackId: string } | null;
-}
-
-export interface ComputeDropTargetParams {
- elementType: ElementType;
- mouseX: number;
- mouseY: number;
- tracks: SceneTracks;
- playheadTime: MediaTime;
- isExternalDrop: boolean;
- elementDuration: MediaTime;
- pixelsPerSecond: number;
- zoomLevel: number;
- verticalDragDirection?: "up" | "down" | null;
- startTimeOverride?: MediaTime;
- excludeElementId?: string;
- targetElementTypes?: string[];
-}
-
-export interface ClipboardItem {
- trackId: string;
- trackType: TrackType;
- element: CreateTimelineElement;
-}
+import type { ElementAnimations } from "@/animation/types"; +import type { Effect } from "@/effects/types"; +import type { Mask } from "@/masks/types"; +import type { ParamValues } from "@/params"; +import type { BlendMode, Transform } from "@/rendering"; +import type { MediaTime } from "@/wasm"; + +export type ElementRef = { + trackId: string; + elementId: string; +}; + +export interface Bookmark { + time: MediaTime; + note?: string; + color?: string; + duration?: MediaTime; +} + +export interface TScene { + id: string; + name: string; + isMain: boolean; + tracks: SceneTracks; + bookmarks: Bookmark[]; + createdAt: Date; + updatedAt: Date; +} + +export type TrackType = "video" | "text" | "audio" | "graphic" | "effect"; + +interface BaseTrack { + id: string; + name: string; +} + +export interface VideoTrack extends BaseTrack { + type: "video"; + elements: (VideoElement | ImageElement)[]; + muted: boolean; + hidden: boolean; +} + +export interface TextTrack extends BaseTrack { + type: "text"; + elements: TextElement[]; + hidden: boolean; +} + +export interface AudioTrack extends BaseTrack { + type: "audio"; + elements: AudioElement[]; + muted: boolean; +} + +export interface GraphicTrack extends BaseTrack { + type: "graphic"; + elements: (StickerElement | GraphicElement)[]; + hidden: boolean; +} + +export interface EffectTrack extends BaseTrack { + type: "effect"; + elements: EffectElement[]; + hidden: boolean; +} + +export type TimelineTrack = + | VideoTrack + | TextTrack + | AudioTrack + | GraphicTrack + | EffectTrack; + +export type OverlayTrack = VideoTrack | TextTrack | GraphicTrack | EffectTrack; + +export interface SceneTracks { + overlay: OverlayTrack[]; + main: VideoTrack; + audio: AudioTrack[]; +} + +export interface RetimeConfig { + rate: number; + maintainPitch?: boolean; +} + +interface BaseAudioElement extends BaseTimelineElement { + type: "audio"; + volume: number; + muted?: boolean; + buffer?: AudioBuffer; + retime?: RetimeConfig; +} + +export interface UploadAudioElement extends BaseAudioElement { + sourceType: "upload"; + mediaId: string; +} + +export interface LibraryAudioElement extends BaseAudioElement { + sourceType: "library"; + sourceUrl: string; +} + +export type AudioElement = UploadAudioElement | LibraryAudioElement; + +interface BaseTimelineElement { + id: string; + name: string; + duration: MediaTime; + startTime: MediaTime; + trimStart: MediaTime; + trimEnd: MediaTime; + sourceDuration?: MediaTime; + animations?: ElementAnimations; +} + +export interface VideoElement extends BaseTimelineElement { + type: "video"; + mediaId: string; + volume?: number; + muted?: boolean; + isSourceAudioEnabled?: boolean; + hidden?: boolean; + retime?: RetimeConfig; + transform: Transform; + opacity: number; + blendMode?: BlendMode; + effects?: Effect[]; + masks?: Mask[]; +} + +export interface ImageElement extends BaseTimelineElement { + type: "image"; + mediaId: string; + hidden?: boolean; + transform: Transform; + opacity: number; + blendMode?: BlendMode; + effects?: Effect[]; + masks?: Mask[]; +} + +export interface TextBackground { + enabled: boolean; + color: string; + cornerRadius?: number; + paddingX?: number; + paddingY?: number; + offsetX?: number; + offsetY?: number; +} + +export interface TextElement extends BaseTimelineElement { + type: "text"; + content: string; + fontSize: number; + fontFamily: string; + color: string; + background: TextBackground; + textAlign: "left" | "center" | "right"; + fontWeight: "normal" | "bold"; + fontStyle: "normal" | "italic"; + textDecoration: "none" | "underline" | "line-through"; + letterSpacing?: number; + lineHeight?: number; + hidden?: boolean; + transform: Transform; + opacity: number; + blendMode?: BlendMode; + effects?: Effect[]; +} + +export interface StickerElement extends BaseTimelineElement { + type: "sticker"; + stickerId: string; + /** Natural dimensions of the sticker asset, stored at insert time. Used by renderer and preview bounds to avoid split-brain geometry. */ + intrinsicWidth?: number; + intrinsicHeight?: number; + hidden?: boolean; + transform: Transform; + opacity: number; + blendMode?: BlendMode; + effects?: Effect[]; +} + +export interface GraphicElement extends BaseTimelineElement { + type: "graphic"; + definitionId: string; + params: ParamValues; + hidden?: boolean; + transform: Transform; + opacity: number; + blendMode?: BlendMode; + effects?: Effect[]; + masks?: Mask[]; +} + +export interface EffectElement extends BaseTimelineElement { + type: "effect"; + effectType: string; + params: ParamValues; +} + +export type ElementUpdatePatch = + | { transform: Transform } + | { opacity: number } + | { volume: number }; + +export type TimelineElement = + | AudioElement + | VideoElement + | ImageElement + | TextElement + | StickerElement + | GraphicElement + | EffectElement; + +export type ElementType = TimelineElement["type"]; + +function elementTypes<T extends ElementType[]>(...types: T): T { + return types; +} + +export const MASKABLE_ELEMENT_TYPES = elementTypes("video", "image", "graphic"); + +export type MaskableElement = Extract< + TimelineElement, + { type: (typeof MASKABLE_ELEMENT_TYPES)[number] } +>; + +export const RETIMABLE_ELEMENT_TYPES = elementTypes("video", "audio"); + +export type RetimableElement = Extract< + TimelineElement, + { type: (typeof RETIMABLE_ELEMENT_TYPES)[number] } +>; + +export const VISUAL_ELEMENT_TYPES = elementTypes( + "video", + "image", + "text", + "sticker", + "graphic", +); + +export type VisualElement = Extract< + TimelineElement, + { type: (typeof VISUAL_ELEMENT_TYPES)[number] } +>; + +export type CreateUploadAudioElement = Omit<UploadAudioElement, "id">; +export type CreateLibraryAudioElement = Omit<LibraryAudioElement, "id">; +export type CreateAudioElement = + | CreateUploadAudioElement + | CreateLibraryAudioElement; +export type CreateVideoElement = Omit<VideoElement, "id">; +export type CreateImageElement = Omit<ImageElement, "id">; +export type CreateTextElement = Omit<TextElement, "id">; +export type CreateStickerElement = Omit<StickerElement, "id">; +export type CreateGraphicElement = Omit<GraphicElement, "id">; +export type CreateEffectElement = Omit<EffectElement, "id">; +export type CreateTimelineElement = + | CreateAudioElement + | CreateVideoElement + | CreateImageElement + | CreateTextElement + | CreateStickerElement + | CreateGraphicElement + | CreateEffectElement; + +export interface ElementDragState { + isDragging: boolean; + elementId: string | null; + dragElementIds: string[]; + dragTimeOffsets: Record<string, MediaTime>; + trackId: string | null; + startMouseX: number; + startMouseY: number; + startElementTime: MediaTime; + clickOffsetTime: MediaTime; + currentTime: MediaTime; + currentMouseY: number; +} + +export type ElementDragView = + | { readonly kind: "idle" } + | { + readonly kind: "dragging"; + readonly anchorElementId: string; + readonly trackId: string; + readonly memberTimeOffsets: ReadonlyMap<string, MediaTime>; + readonly startMouseX: number; + readonly startMouseY: number; + readonly startElementTime: MediaTime; + readonly clickOffsetTime: MediaTime; + readonly currentTime: MediaTime; + readonly currentMouseX: number; + readonly currentMouseY: number; + readonly dropTarget: DropTarget | null; + }; + +export interface DropTarget { + trackIndex: number; + isNewTrack: boolean; + insertPosition: "above" | "below" | null; + xPosition: MediaTime; + targetElement: { elementId: string; trackId: string } | null; +} + +export interface ComputeDropTargetParams { + elementType: ElementType; + mouseX: number; + mouseY: number; + tracks: SceneTracks; + playheadTime: MediaTime; + isExternalDrop: boolean; + elementDuration: MediaTime; + pixelsPerSecond: number; + zoomLevel: number; + verticalDragDirection?: "up" | "down" | null; + startTimeOverride?: MediaTime; + excludeElementId?: string; + targetElementTypes?: string[]; +} + +export interface ClipboardItem { + trackId: string; + trackType: TrackType; + element: CreateTimelineElement; +} diff --git a/apps/web/src/utils/geometry.ts b/apps/web/src/utils/geometry.ts index 23fc4f46..b5c50584 100644 --- a/apps/web/src/utils/geometry.ts +++ b/apps/web/src/utils/geometry.ts @@ -5,8 +5,14 @@ export function dimensionToAspectRatio({ width: number; height: number; }): string { - const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b)); - const divisor = gcd(width, height); + const gcd = ({ + a, + b, + }: { + a: number; + b: number; + }): number => (b === 0 ? a : gcd({ a: b, b: a % b })); + const divisor = gcd({ a: width, b: height }); const aspectWidth = width / divisor; const aspectHeight = height / divisor; return `${aspectWidth}:${aspectHeight}`; diff --git a/apps/web/src/wasm/media-time.ts b/apps/web/src/wasm/media-time.ts index 82f5378d..0ec23cf3 100644 --- a/apps/web/src/wasm/media-time.ts +++ b/apps/web/src/wasm/media-time.ts @@ -26,20 +26,37 @@ export type MediaTime = number & { readonly __mediaTime: unique symbol }; export const TICKS_PER_SECOND = _TICKS_PER_SECOND(); -export const ZERO_MEDIA_TIME = 0 as MediaTime; +function isMediaTime(value: number): value is MediaTime { + return Number.isInteger(value); +} + +function requireMediaTime({ + value, + context, +}: { + value: number; + context: string; +}): MediaTime { + if (!isMediaTime(value)) { + throw new Error(`${context}: expected an integer tick count, got ${value}`); + } + return value; +} + +export const ZERO_MEDIA_TIME = requireMediaTime({ + value: 0, + context: "ZERO_MEDIA_TIME", +}); /** - * Construct a `MediaTime` from a known-integer tick count. Asserts in dev that - * the value is actually an integer; cast in release. Use `roundMediaTime` when - * the input may be fractional. + * Construct a `MediaTime` from a known-integer tick count. Use `roundMediaTime` + * when the input may be fractional. */ export function mediaTime({ ticks }: { ticks: number }): MediaTime { - if (process.env.NODE_ENV !== "production" && !Number.isInteger(ticks)) { - throw new Error( - `mediaTime() requires an integer tick count, got ${ticks}. Use roundMediaTime() for fractional values.`, - ); - } - return ticks as MediaTime; + return requireMediaTime({ + value: ticks, + context: "mediaTime()", + }); } /** @@ -52,9 +69,12 @@ export function mediaTime({ ticks }: { ticks: number }): MediaTime { export function roundMediaTime({ time }: { time: number }): MediaTime { const roundedMagnitude = Math.round(Math.abs(time)); if (roundedMagnitude === 0) { - return 0 as MediaTime; + return ZERO_MEDIA_TIME; } - return (time < 0 ? -roundedMagnitude : roundedMagnitude) as MediaTime; + return requireMediaTime({ + value: time < 0 ? -roundedMagnitude : roundedMagnitude, + context: "roundMediaTime()", + }); } export function mediaTimeFromSeconds({ @@ -68,7 +88,10 @@ export function mediaTimeFromSeconds({ `mediaTimeFromSeconds: rust returned undefined for seconds=${seconds}`, ); } - return result as MediaTime; + return requireMediaTime({ + value: result, + context: "mediaTimeFromSeconds()", + }); } export function mediaTimeToSeconds({ time }: { time: MediaTime }): number { @@ -76,9 +99,7 @@ export function mediaTimeToSeconds({ time }: { time: MediaTime }): number { } /** - * Sum `MediaTime` values. Inputs are integer ticks, so the sum is integer too; - * the cast is a no-op at runtime, only re-establishes the brand for the type - * system. + * Sum `MediaTime` values. Inputs are integer ticks, so the sum is integer too. */ export function addMediaTime({ a, @@ -87,7 +108,10 @@ export function addMediaTime({ a: MediaTime; b: MediaTime; }): MediaTime { - return (a + b) as MediaTime; + return requireMediaTime({ + value: a + b, + context: "addMediaTime()", + }); } export function subMediaTime({ @@ -97,7 +121,10 @@ export function subMediaTime({ a: MediaTime; b: MediaTime; }): MediaTime { - return (a - b) as MediaTime; + return requireMediaTime({ + value: a - b, + context: "subMediaTime()", + }); } export function maxMediaTime({ @@ -107,7 +134,7 @@ export function maxMediaTime({ a: MediaTime; b: MediaTime; }): MediaTime { - return (a > b ? a : b) as MediaTime; + return a > b ? a : b; } export function minMediaTime({ @@ -117,7 +144,7 @@ export function minMediaTime({ a: MediaTime; b: MediaTime; }): MediaTime { - return (a < b ? a : b) as MediaTime; + return a < b ? a : b; } export function clampMediaTime({ @@ -141,7 +168,10 @@ export function roundFrameTime({ time: MediaTime; fps: FrameRate; }): MediaTime { - return (_roundToFrame({ time, rate: fps }) ?? time) as MediaTime; + return requireMediaTime({ + value: _roundToFrame({ time, rate: fps }) ?? time, + context: "roundFrameTime()", + }); } export function roundFrameTicks({ @@ -163,7 +193,10 @@ export function snapSeekMediaTime({ duration: MediaTime; fps: FrameRate; }): MediaTime { - return (_snappedSeekTime({ time, duration, rate: fps }) ?? time) as MediaTime; + return requireMediaTime({ + value: _snappedSeekTime({ time, duration, rate: fps }) ?? time, + context: "snapSeekMediaTime()", + }); } export function lastFrameMediaTime({ @@ -173,7 +206,10 @@ export function lastFrameMediaTime({ duration: MediaTime; fps: FrameRate; }): MediaTime { - return (_lastFrameTime({ duration, rate: fps }) ?? duration) as MediaTime; + return requireMediaTime({ + value: _lastFrameTime({ duration, rate: fps }) ?? duration, + context: "lastFrameMediaTime()", + }); } export function parseMediaTimecode({ @@ -186,5 +222,11 @@ export function parseMediaTimecode({ fps: FrameRate; }): MediaTime | null { const parsedTime = _parseTimecode({ timeCode, format, rate: fps }); - return parsedTime == null ? null : (parsedTime as MediaTime); + if (parsedTime == null) { + return null; + } + return requireMediaTime({ + value: parsedTime, + context: "parseMediaTimecode()", + }); } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 3ac10911..3573338a 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,39 +1,39 @@ -{
- "compilerOptions": {
- "target": "ES2017",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "react-jsx",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": ["./src/*"],
- "content-collections": ["./.content-collections/generated"]
- },
- "forceConsistentCasingInFileNames": true
- },
- "include": [
- "**/*.ts",
- "**/*.tsx",
- "apps/web/.next/types/**/*.ts",
- "next-env.d.ts",
- ".next/types/**/*.ts",
- "src/types/**/*.d.ts",
- "../../use-frame-cache.ts",
- "src/stores/timeline-store.ts",
- ".next/dev/types/**/*.ts"
- ],
- "exclude": ["node_modules"]
-}
+{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"], + "content-collections": ["./.content-collections/generated"] + }, + "forceConsistentCasingInFileNames": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "apps/web/.next/types/**/*.ts", + "next-env.d.ts", + ".next/types/**/*.ts", + "src/types/**/*.d.ts", + "../../use-frame-cache.ts", + "src/stores/timeline-store.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": ["node_modules"] +} @@ -1,11 +1,11 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { "name": "opencut", "dependencies": { - "@biomejs/biome": "^2.4.8", + "opencut": ".", "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", "better-auth": "^1.4.15", @@ -13,8 +13,18 @@ "opencut-wasm": "^0.2.10", }, "devDependencies": { + "@eslint/js": "^9.39.4", + "@next/eslint-plugin-next": "^16.2.4", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", + "globals": "^17.5.0", + "prettier": "^3.8.3", "turbo": "^2.8.20", "typescript": "^6.0.2", + "typescript-eslint": "^8.59.0", }, }, "apps/web": { @@ -154,63 +164,63 @@ "@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.984.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-sdk-sqs": "^3.972.5", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-TDvHpOUWlpanc3xQ5Xw0y8L2hoojBFCCSmXQ/6rKqGOf1ScX3dMA+K9aF0Zp0iwjhSh4VvsHD42esl8XwQZDjA=="], - "@aws-sdk/core": ["@aws-sdk/core@3.973.24", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vvf82RYQu2GidWAuQq+uIzaPz9V0gSCXVqdVzRosgl5rXcspXOpSD3wFreGGW6AYymPr97Z69kjVnLePBxloDw=="], + "@aws-sdk/core": ["@aws-sdk/core@3.974.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.19", "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-lMPlYlYfQdNZhlkJgnkmESwrY+hNh3PljmZ+37oAqLNdJ6rnILAwFSyc6B3bJeDOtMORNnMQIej0aTRuOlDyhQ=="], - "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.5", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg=="], + "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.7", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-cXp0VTDWT76p3hyK5D51yIKEfpf6/zsUvMfaB8CkyqadJxMQ8SbEeVroregmDlZbtG31wkj9ei0WnftmieggLg=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-X/yGB73LmDW/6MdDJGCDzZBUXnM3ys4vs9l+5ZTJmiEswDdP1OjeoAFlFjVGS9o4KB2wZWQ9KOfdVNSSK6Ep3w=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-h694K7+tRuepSRJr09wTvQfaEnjzsKZ5s7fbESrVds02GT/QzViJ94/HCNwM7bUfFxqpPXHxulZfL6Cou0dwPg=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.33", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.25", "tslib": "^2.6.2" } }, "sha512-c0ZF+lwoWVvX5iCaGKL5T/4DnIw88CGqxA0BcBs3U86mIp5EZYPVg+KSPkMXOyokmADvNewiMUfSG2uFwjRp0g=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/credential-provider-env": "^3.972.22", "@aws-sdk/credential-provider-http": "^3.972.24", "@aws-sdk/credential-provider-login": "^3.972.24", "@aws-sdk/credential-provider-process": "^3.972.22", "@aws-sdk/credential-provider-sso": "^3.972.24", "@aws-sdk/credential-provider-web-identity": "^3.972.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-O46fFmv0RDFWiWEA9/e6oW92BnsyAXuEgTTasxHligjn2RCr9L/DK773m/NoFaL3ZdNAUz8WxgxunleMnHAkeQ=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.35", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/credential-provider-env": "^3.972.31", "@aws-sdk/credential-provider-http": "^3.972.33", "@aws-sdk/credential-provider-login": "^3.972.35", "@aws-sdk/credential-provider-process": "^3.972.31", "@aws-sdk/credential-provider-sso": "^3.972.35", "@aws-sdk/credential-provider-web-identity": "^3.972.35", "@aws-sdk/nested-clients": "^3.997.3", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jsU4u/cRkKFLKQS0k918FQ27fzXLG5ENiLWQMYE6581zLeI2hWh04ptlrvZMB3wJT/5d+vSzJk74X1CMFr4y8Q=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-sIk8oa6AzDoUhxsR11svZESqvzGuXesw62Rl2oW6wguZx8i9cdGCvkFg+h5K7iucUZP8wyWibUbJMc+J66cu5g=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.35", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/nested-clients": "^3.997.3", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-5oa3j0cA50jPqgNhZ9XdJVopuzUf1klRb28/2MfLYWWiPi9DRVvbrBWT+DidbHTT36520VuXZJahQwR+YgSjrg=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.25", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.22", "@aws-sdk/credential-provider-http": "^3.972.24", "@aws-sdk/credential-provider-ini": "^3.972.24", "@aws-sdk/credential-provider-process": "^3.972.22", "@aws-sdk/credential-provider-sso": "^3.972.24", "@aws-sdk/credential-provider-web-identity": "^3.972.24", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-m7dR0Dsva2P+VUpL+VkC0WwiDby5pgmWXkRVDB5rlwv0jXJrQJf7YMtCoM8Wjk0H9jPeCYOxOXXcIgp/qp5Alg=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.36", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.31", "@aws-sdk/credential-provider-http": "^3.972.33", "@aws-sdk/credential-provider-ini": "^3.972.35", "@aws-sdk/credential-provider-process": "^3.972.31", "@aws-sdk/credential-provider-sso": "^3.972.35", "@aws-sdk/credential-provider-web-identity": "^3.972.35", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-4nT2T8Z7vH8KE9EdjEsuIlHpZSlcaK2PrKbQBjuUGU46BCCzF3WvP0u0Uiosni3Ykmmn4rWLVawoOCLotUtCbg=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Os32s8/4gTZjBk5BtoS/cuTILaj+K72d0dVG7TCJX/fC4598cxwLDmf1AEHEpER5oL3K//yETjvFaz0V8oO5Xw=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-eKeT4MXumpBJsrDLCYcSzIkFPVTFn/es7It2oogp2OhU/ic7P/+xzFpQx9ZhwtXS57Mc5S42BPWi7lHmvs/nYg=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/token-providers": "3.1015.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-PaFv7snEfypU2yXkpvfyWgddEbDLtgVe51wdZlinhc2doubBjUzJZZpgwuF2Jenl1FBydMhNpMjD6SBUM3qdSA=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.35", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/nested-clients": "^3.997.3", "@aws-sdk/token-providers": "3.1036.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-bCuBdfnj0KGDMdLp6utMTLiJcFN2ek9EgZinxQZZSc3FxjJ/HSqeqab2cjbnoNfy8RM6suDCsRkmVY1izp9I+A=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-J6H4R1nvr3uBTqD/EeIPAskrBtET4WFfNhpFySr2xW7bVZOXpQfPjrLSIx65jcNjBmLXzWq8QFLdVoGxiGG/SA=="], + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.35", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/nested-clients": "^3.997.3", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-swW6Bwvl8lanyEMtZOWE/oR6yqcRQH4HTQZUVsnDVgoXvRjRywpYpLv2BWwjUFyjPrqsdX6FeTkf4tMSe/qFTQ=="], - "@aws-sdk/dynamodb-codec": ["@aws-sdk/dynamodb-codec@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@smithy/core": "^3.23.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-AfkNIk19MOeGXfRyznRMZNOTUt79HU2pl7GP606oUIGELmS495xfvGDGp9nGYbOPxWhXfM7/s51V8+lvmCEwVg=="], + "@aws-sdk/dynamodb-codec": ["@aws-sdk/dynamodb-codec@3.973.5", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@smithy/core": "^3.23.17", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-SNF2VCwK2MA9P3jmz/DW498BK0SkGMyLBeAe+ZJy2fVoEOHq0b/ZdRNw4fQMuq07gdNxLErY9RnXUo6Rdu2QIA=="], - "@aws-sdk/endpoint-cache": ["@aws-sdk/endpoint-cache@3.972.4", "", { "dependencies": { "mnemonist": "0.38.3", "tslib": "^2.6.2" } }, "sha512-GdASDnWanLnHxKK0hqV97xz23QmfA/C8yGe0PiuEmWiHSe+x+x+mFEj4sXqx9IbfyPncWz8f4EhNwBSG9cgYCg=="], + "@aws-sdk/endpoint-cache": ["@aws-sdk/endpoint-cache@3.972.5", "", { "dependencies": { "mnemonist": "0.38.3", "tslib": "^2.6.2" } }, "sha512-itVdge0NozgtgmtbZ25FVwWU3vGlE7x7feE/aOEJNkQfEpbkrF8Rj1QmnK+2blFfYE1xWt/iU+6/jUp/pv1+MA=="], - "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw=="], + "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA=="], - "@aws-sdk/middleware-endpoint-discovery": ["@aws-sdk/middleware-endpoint-discovery@3.972.8", "", { "dependencies": { "@aws-sdk/endpoint-cache": "^3.972.4", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-S0oXx1QbSpMDBMJn4P0hOxW8ieGAdRT+G9NbL+ESWkkoCGf9D++fKYD2fyBGtIy88OrP7wgECpXgGLAcGpIj0A=="], + "@aws-sdk/middleware-endpoint-discovery": ["@aws-sdk/middleware-endpoint-discovery@3.972.11", "", { "dependencies": { "@aws-sdk/endpoint-cache": "^3.972.5", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-vXARCZVFQHdsd6qPPZyC/hh+5x2XsCYKqUQDCqnUlpGpChMpDojOOacQWdLJ+FFXKN8X3cmLOGrtgx/zysCKqQ=="], - "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ=="], + "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ=="], - "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/crc64-nvme": "^3.972.5", "@aws-sdk/types": "^3.973.6", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fhCbZXPAyy8btnNbnBlR7Cc1nD54cETSvGn2wey71ehsM89AKPO8Dpco9DBAAgvrUdLrdHQepBXcyX4vxC5OwA=="], + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.13", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.974.5", "@aws-sdk/crc64-nvme": "^3.972.7", "@aws-sdk/types": "^3.973.8", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-b6QUe2hQX9XsnCzp6mtzVaERhganDKeb8lmGL6pVhr7rRVH9S9keDFW7uKytuuqmcY5943FixoGqn/QL+sbUBA=="], - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="], + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg=="], - "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw=="], + "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ=="], - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="], + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ=="], - "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="], + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ=="], - "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4sXxVC/enYgMkZefNMOzU6C6KtAXEvwVJLgNcUx1dvROH6GvKB5Sm2RGnGzTp0/PwkibIyMw4kOzF8tbLfaBAQ=="], + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.34", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-/UL96JKjsjdodcRRMKl99tLQvK6Oi9ptLC9iU1yiTF/ruaDX0mtBBtnLNZDxIZRJOCVOtB49ed1YaTadqygk8Q=="], - "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.17", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-LnzPRRoDXGtlFV2G1p2rsY6fRKrbf6Pvvc21KliSLw3+NmQca2+Aa1QIMRbpQvZYedsSqkGYwxe+qvXwQ2uxDw=="], + "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.22", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-DtR3mEiOUJcnEX/QuXmvbJto6xvQzp2ftnHb29c0aQYdmmzbKf0gsu9ovx1i/yy4ZR6m0rttTucS0iiP32dlGA=="], - "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw=="], + "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw=="], - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-QxiMPofvOt8SwSynTOmuZfvvPM1S9QfkESBxB22NMHTRXCJhR5BygLl8IXfC4jELiisQgwsgUby21GtXfX3f/g=="], + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.35", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-retry": "^4.3.4", "tslib": "^2.6.2" } }, "sha512-hOFWNOjVmOocpRlrU04nYxjMOeoe0Obu5AXEuhB8zblMCPl3cG1hdluQCZERRKFyhMQjwZnDbhSHjoMUjetFGw=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.3", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.5", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/signature-v4-multi-region": "^3.996.22", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.6.1", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.49", "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-SivE6GP228IVgfsrr2c/vqTg95X0Qj39Yw4uIrcddpkUzIltNMoNOR62leHOLhODfjv9K8X2mPTwS69A5kT0nQ=="], - "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng=="], + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.13", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A=="], "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.984.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-TaWbfYCwnuOSvDSrgs7QgoaoXse49E7LzUkVOUhoezwB7bkmhp+iojADm7UepCEu4021SquD7NG1xA+WCvmldA=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1015.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-3OSD4y110nisRhHzFOjoEeHU4GQL4KpzkX9PxzWaiZe0Yg2+thZKM0Pn9DjYwezH5JYfh/K++xK/SE0IHGrmCQ=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1036.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.5", "@aws-sdk/nested-clients": "^3.997.3", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-aNSJ6jjDYayxN9ZA1JpycVScX93Lx03kKZ1EXt3DGOTahcWVLJj3oLAlop0xKP+vP2Ga2t49p1tEaMkTbCCaZA=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + "@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="], @@ -218,57 +228,81 @@ "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], - "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="], + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g=="], - "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.11", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-1qdXbXo2s5MMLpUvw00284LsbhtlQ4ul7Zzdn5n+7p4WVgCMLqhxImpHIrjSoc72E/fyc4Wq8dLtUld2Gsh+lA=="], + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.21", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Av4UHTcAWgdvbN0IP9pbtf4Qa1+6LtJqQdZWj5pLn5J67w0pnJJAZZ+7JPPcj2KN3378zD2JDM9DwJKEyvyMTQ=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.19", "", { "dependencies": { "@smithy/types": "^4.14.1", "fast-xml-parser": "5.7.1", "tslib": "^2.6.2" } }, "sha512-Cw8IOMdBUEIl8ZlhRC3Dc/E64D5B5/8JhV6vhPLiPfJwcRC84S6F8aBOIi/N4vR9ZyA4I5Cc0Ateb/9EHaJXeQ=="], "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], - "@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@better-auth/core": ["@better-auth/core@1.4.15", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.8", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-uAvq8YA7SaS7v+TrvH/Kwt7LAJihzUqB3FX8VweDsqu3gn5t51M+Bve+V1vVWR9qBAtC6cN68V6b+scxZxDY4A=="], + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - "@better-auth/telemetry": ["@better-auth/telemetry@1.4.15", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.15" } }, "sha512-7NW/2PS4RN85rv+ozpAezP/kSLPZeWkxqcA6RA/CFXqWp2YR2e5q5E6Hym1qBgVBkoAQa3lWFdX3b+jEs+vvrQ=="], + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - "@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="], + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], - "@biomejs/biome": ["@biomejs/biome@2.4.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.8", "@biomejs/cli-darwin-x64": "2.4.8", "@biomejs/cli-linux-arm64": "2.4.8", "@biomejs/cli-linux-arm64-musl": "2.4.8", "@biomejs/cli-linux-x64": "2.4.8", "@biomejs/cli-linux-x64-musl": "2.4.8", "@biomejs/cli-win32-arm64": "2.4.8", "@biomejs/cli-win32-x64": "2.4.8" }, "bin": { "biome": "bin/biome" } }, "sha512-ponn0oKOky1oRXBV+rlSaUlixUxf1aZvWC19Z41zBfUOUesthrQqL3OtiAlSB1EjFjyWpn98Q64DHelhA6jNlA=="], + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ARx0tECE8I7S2C2yjnWYLNbBdDoPdq3oyNLhMglmuctThwUsuzFWRKrHmIGwIRWKz0Mat9DuzLEDp52hGnrxGQ=="], + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Jg9/PsB9vDCJlANE8uhG7qDhb5w0Ix69D7XIIc8IfZPUoiPrbLm33k2Ig3NOJ/7nb3UbesFz3D1aDKm9DvzjhQ=="], + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-5CdrsJct76XG2hpKFwXnEtlT1p+4g4yV+XvvwBpzKsTNLO9c6iLlAxwcae2BJ7ekPGWjNGw9j09T5KGPKKxQig=="], + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-Zo9OhBQDJ3IBGPlqHiTISloo5H0+FBIpemqIJdW/0edJ+gEcLR+MZeZozcUyz3o1nXkVA7++DdRKQT0599j9jA=="], + "@better-auth/core": ["@better-auth/core@1.6.9", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-ADFk5pwmLybmc+LvYvXJ6M1x2oY/EyYLkwLuH0x28FUq12DfjL0wnE7g+WRDf3yozDO+qIxTpFGXDGwLKbfz0w=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-PdKXspVEaMCQLjtZCn6vfSck/li4KX9KGwSDbZdgIqlrizJ2MnMcE3TvHa2tVfXNmbjMikzcfJpuPWH695yJrw=="], + "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-Lcco5hOGrMgc4XKAkvB6x72eQm4wCcya8IevMg4wBHY9W9GVg8pu23rpRX6VsVQSO4Ux13S7lFwUWtF7/r9aKw=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.8", "", { "os": "linux", "cpu": "x64" }, "sha512-Gi8quv8MEuDdKaPFtS2XjEnMqODPsRg6POT6KhoP+VrkNb+T2ywunVB+TvOU0LX1jAZzfBr+3V1mIbBhzAMKvw=="], + "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "kysely": "^0.28.14" }, "optionalPeers": ["kysely"] }, "sha512-gyjuuxJtZ4o9G9z9q4kqn24X2kvMSp7F+KHogYxF03SnXY/2WleAcuj57iC4wP3e9mGDbjPOrnM5K6Kr3Ktdpw=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-LoFatS0tnHv6KkCVpIy3qZCih+MxUMvdYiPWLHRri7mhi2vyOOs8OrbZBcLTUEWCS+ktO72nZMy4F96oMhkOHQ=="], + "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0" } }, "sha512-XmIG4tUnOXZ+KEcWjHUjOI9Z5donD09dC2t/AQTXifAUIqx7cySg86w0KTM09ArzAxRx1fCqO36Wkt5nULnrkQ=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.8", "", { "os": "win32", "cpu": "x64" }, "sha512-vAn7iXDoUbqFXqVocuq1sMYAd33p8+mmurqJkWl6CtIhobd/O6moe4rY5AJvzbunn/qZCdiDVcveqtkFh1e7Hg=="], + "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-h+AiRJ/TsBSi+ZDjySASBpbJ/9QCXBre34PSKgCz7QmTHrFM9Cg2EM4AM7LjR5lPXipEE+2rWPBc9wfnUBjhcw=="], + + "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-XHks01ntK20orqK/jICq8wmEbJ/zT6dct49Fk8zTQKN9QNGDc+Ix5+7z/Kvui0DXGFf790GfvRozquzaLtXa8Q=="], + + "@better-auth/telemetry": ["@better-auth/telemetry@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21" } }, "sha512-0u5zkhSCAQFoN3DHvUkLHOF6MBbVTDAa6mU8mhPwiysdz1x21vMzhzfaAKN/ZGWaQ09v91/F+2qu42G/bhUV4A=="], + + "@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="], + + "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], - "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg=="], + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="], - "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260317.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260424.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-yFR1XaJbSDLg/qbwtrYaU2xwFXatIPKR5nrMQCN1q/m6+Qe/j6r+kCnFEvOJjMZOm9iCKsE6Qly5clgl4u32qw=="], - "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260317.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg=="], + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260424.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LqWKcE7x/9KyC2iQvKPeb20hKST3dYXDZlYTvFymgR1DfLS0OFOCzVGTloVNd7WqvK4SkdzBYfxo7QMIAeBK0w=="], - "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260317.1", "", { "os": "linux", "cpu": "x64" }, "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug=="], + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260424.1", "", { "os": "linux", "cpu": "x64" }, "sha512-YlEBFbAYZHe/ylzl8WEYQEU/jr+0XMqXaST2oBk5oVjksdb1NGuJaggluCdZAzuJJ8UqdTmyhY5u/qrasbiFWA=="], - "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260317.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw=="], + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260424.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-qJ0X0m6cL8fWDUPDg8K4IxYZXNJI6XbeOihqjnqKbAClrjdPDn8VUSd+z2XiCQ5NylMtMrpa/skC9UfaR6mh8g=="], - "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260317.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ=="], + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260424.1", "", { "os": "win32", "cpu": "x64" }, "sha512-tZ7Z9qmYNAP6z1/+8r/zKbk8F8DZmpmwNzMeN+zkde2Wnhfr3FBqOkJXT/5zmli8HPoWrIXxSiyqcNDMy8V2Zg=="], - "@content-collections/core": ["@content-collections/core@0.14.1", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "camelcase": "^8.0.0", "chokidar": "^4.0.3", "esbuild": "^0.25.11", "gray-matter": "^4.0.3", "p-limit": "^6.1.0", "picomatch": "^4.0.2", "pluralize": "^8.0.0", "serialize-javascript": "^6.0.2", "tinyglobby": "^0.2.5", "yaml": "^2.4.5" }, "peerDependencies": { "typescript": "^5.0.2" } }, "sha512-mfviKnONzVcr1D3uasZFEk0E9N4a7liCra06imQrmKF07nGDuvrRKUFoUwUc2IGrW3EYqKF/KV06zfJRzNe3sg=="], + "@content-collections/core": ["@content-collections/core@0.14.3", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "camelcase": "^8.0.0", "chokidar": "^4.0.3", "esbuild": "^0.25.11", "gray-matter": "^4.0.3", "p-limit": "^6.1.0", "picomatch": "^4.0.2", "pluralize": "^8.0.0", "serialize-javascript": "^7.0.3", "tinyglobby": "^0.2.5", "yaml": "^2.4.5" }, "peerDependencies": { "typescript": "^5.0.2" } }, "sha512-nbgfRswxudlKDBEO2AXVGGfsFMlenV2iVjNDddQGWD0qk3lBDkTRJy0AqdBvBOccB8/cj95nJyXEVocRRNyUcA=="], "@content-collections/integrations": ["@content-collections/integrations@0.5.0", "", { "peerDependencies": { "@content-collections/core": "0.x" } }, "sha512-1en7r518sct0Y8CQ5IsuuBN4uAmtNLaWuxmseW43OxeXyj43Uu2aPBfbopjL4b5xH8WZBdDrrPmikgOl42U46A=="], @@ -280,9 +314,9 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - "@ecies/ciphers": ["@ecies/ciphers@0.2.5", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A=="], + "@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="], - "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], @@ -340,13 +374,31 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@floating-ui/core": ["@floating-ui/core@1.7.2", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - "@floating-ui/dom": ["@floating-ui/dom@1.7.2", "", { "dependencies": { "@floating-ui/core": "^1.7.2", "@floating-ui/utils": "^0.2.10" } }, "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA=="], + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.4", "", { "dependencies": { "@floating-ui/dom": "^1.7.2" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw=="], + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], - "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], + "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@hello-pangea/dnd": ["@hello-pangea/dnd@18.0.1", "", { "dependencies": { "@babel/runtime": "^7.26.7", "css-box-model": "^1.2.1", "raf-schd": "^4.0.3", "react-redux": "^9.2.0", "redux": "^5.0.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ=="], @@ -354,11 +406,21 @@ "@hugeicons/react": ["@hugeicons/react@1.1.6", "", { "peerDependencies": { "react": ">=16.0.0" } }, "sha512-c2LhXJMAW5wN1pC/smBXG0YPqUON6ceR/ZdXHCjEI9KvB+hjtqYjmzIxok5hAQOeXGz0WtORgCQMzqewFKAZwg=="], - "@huggingface/jinja": ["@huggingface/jinja@0.5.3", "", {}, "sha512-asqfZ4GQS0hD876Uw4qiUb7Tr/V5Q+JZuo2L+BtdrD4U40QU58nIRq3ZSgAzJgT874VLjhGVacaYfrdpXtEvtA=="], + "@huggingface/jinja": ["@huggingface/jinja@0.5.8", "", {}, "sha512-ZdElB7DPS7QQS8ZnFc5RPPtkg+eN11z8AmIZWAyes6pSbwXqiFB/POVevvm01begdSX1ho9Gxln/F6qlQMsuaA=="], "@huggingface/transformers": ["@huggingface/transformers@3.8.1", "", { "dependencies": { "@huggingface/jinja": "^0.5.3", "onnxruntime-node": "1.21.0", "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4", "sharp": "^0.34.1" } }, "sha512-tsTk4zVjImqdqjS8/AOZg2yNLd1z9S5v+7oUPpXaasDRwEDhB+xnglK1k5cad26lL5/ZIaeREgWWy0bs9y9pPA=="], - "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -412,7 +474,7 @@ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -422,55 +484,59 @@ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@napi-rs/canvas": ["@napi-rs/canvas@0.1.100", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.100", "@napi-rs/canvas-darwin-arm64": "0.1.100", "@napi-rs/canvas-darwin-x64": "0.1.100", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", "@napi-rs/canvas-linux-arm64-musl": "0.1.100", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", "@napi-rs/canvas-linux-x64-gnu": "0.1.100", "@napi-rs/canvas-linux-x64-musl": "0.1.100", "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", "@napi-rs/canvas-win32-x64-msvc": "0.1.100" } }, "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA=="], - "@napi-rs/canvas": ["@napi-rs/canvas@0.1.92", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.92", "@napi-rs/canvas-darwin-arm64": "0.1.92", "@napi-rs/canvas-darwin-x64": "0.1.92", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.92", "@napi-rs/canvas-linux-arm64-gnu": "0.1.92", "@napi-rs/canvas-linux-arm64-musl": "0.1.92", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.92", "@napi-rs/canvas-linux-x64-gnu": "0.1.92", "@napi-rs/canvas-linux-x64-musl": "0.1.92", "@napi-rs/canvas-win32-arm64-msvc": "0.1.92", "@napi-rs/canvas-win32-x64-msvc": "0.1.92" } }, "sha512-q7ZaUCJkEU5BeOdE7fBx1XWRd2T5Ady65nxq4brMf5L4cE1VV/ACq5w9Z5b/IVJs8CwSSIwc30nlthH0gFo4Ig=="], + "@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.100", "", { "os": "android", "cpu": "arm64" }, "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ=="], - "@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.92", "", { "os": "android", "cpu": "arm64" }, "sha512-rDOtq53ujfOuevD5taxAuIFALuf1QsQWZe1yS/N4MtT+tNiDBEdjufvQRPWZ11FubL2uwgP8ApYU3YOaNu1ZsQ=="], + "@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@0.1.100", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A=="], - "@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@0.1.92", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4PT6GRGCr7yMRehp42x0LJb1V0IEy1cDZDDayv7eKbFUIGbPFkV7CRC9Bee5MPkjg1EB4ZPXXUyy3gjQm7mR8Q=="], + "@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@0.1.100", "", { "os": "darwin", "cpu": "x64" }, "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw=="], - "@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@0.1.92", "", { "os": "darwin", "cpu": "x64" }, "sha512-5e/3ZapP7CqPtDcZPtmowCsjoyQwuNMMD7c0GKPtZQ8pgQhLkeq/3fmk0HqNSD1i227FyJN/9pDrhw/UMTkaWA=="], + "@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@0.1.100", "", { "os": "linux", "cpu": "arm" }, "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA=="], - "@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@0.1.92", "", { "os": "linux", "cpu": "arm" }, "sha512-j6KaLL9iir68lwpzzY+aBGag1PZp3+gJE2mQ3ar4VJVmyLRVOh+1qsdNK1gfWoAVy5w6U7OEYFrLzN2vOFUSng=="], + "@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@0.1.100", "", { "os": "linux", "cpu": "arm64" }, "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw=="], - "@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@0.1.92", "", { "os": "linux", "cpu": "arm64" }, "sha512-s3NlnJMHOSotUYVoTCoC1OcomaChFdKmZg0VsHFeIkeHbwX0uPHP4eCX1irjSfMykyvsGHTQDfBAtGYuqxCxhQ=="], + "@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@0.1.100", "", { "os": "linux", "cpu": "arm64" }, "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ=="], - "@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@0.1.92", "", { "os": "linux", "cpu": "arm64" }, "sha512-xV0GQnukYq5qY+ebkAwHjnP2OrSGBxS3vSi1zQNQj0bkXU6Ou+Tw7JjCM7pZcQ28MUyEBS1yKfo7rc7ip2IPFQ=="], + "@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@0.1.100", "", { "os": "linux", "cpu": "none" }, "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw=="], - "@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@0.1.92", "", { "os": "linux", "cpu": "none" }, "sha512-+GKvIFbQ74eB/TopEdH6XIXcvOGcuKvCITLGXy7WLJAyNp3Kdn1ncjxg91ihatBaPR+t63QOE99yHuIWn3UQ9w=="], + "@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@0.1.100", "", { "os": "linux", "cpu": "x64" }, "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg=="], - "@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@0.1.92", "", { "os": "linux", "cpu": "x64" }, "sha512-tFd6MwbEhZ1g64iVY2asV+dOJC+GT3Yd6UH4G3Hp0/VHQ6qikB+nvXEULskFYZ0+wFqlGPtXjG1Jmv7sJy+3Ww=="], + "@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@0.1.100", "", { "os": "linux", "cpu": "x64" }, "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA=="], - "@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@0.1.92", "", { "os": "linux", "cpu": "x64" }, "sha512-uSuqeSveB/ZGd72VfNbHCSXO9sArpZTvznMVsb42nqPP7gBGEH6NJQ0+hmF+w24unEmxBhPYakP/Wiosm16KkA=="], + "@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@0.1.100", "", { "os": "win32", "cpu": "arm64" }, "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw=="], - "@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@0.1.92", "", { "os": "win32", "cpu": "arm64" }, "sha512-20SK5AU/OUNz9ZuoAPj5ekWai45EIBDh/XsdrVZ8le/pJVlhjFU3olbumSQUXRFn7lBRS+qwM8kA//uLaDx6iQ=="], + "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.100", "", { "os": "win32", "cpu": "x64" }, "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA=="], - "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.92", "", { "os": "win32", "cpu": "x64" }, "sha512-KEhyZLzq1MXCNlXybz4k25MJmHFp+uK1SIb8yJB0xfrQjz5aogAMhyseSzewo+XxAq3OAOdyKvfHGNzT3w1RPg=="], + "@next/env": ["@next/env@16.2.4", "", {}, "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw=="], - "@next/env": ["@next/env@16.1.3", "", {}, "sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA=="], + "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.4", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-tOX826JJ96gYK/go18sPUgMq9FK1tqxBFfUCEufJb5XIkWFFmpgU7mahJANKGkHs7F41ir3tReJ3Lv5La0RvhA=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw=="], - "@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="], + "@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="], "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], - "@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="], + "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], + + "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], "@node-minify/core": ["@node-minify/core@8.0.6", "", { "dependencies": { "@node-minify/utils": "8.0.6", "glob": "9.3.5", "mkdirp": "1.0.4" } }, "sha512-/vxN46ieWDLU67CmgbArEvOb41zlYFOkOtr9QW9CnTrBLuTyGgkyNWC2y5+khvRw3Br58p2B5ZVSx/PxCTru6g=="], @@ -478,11 +544,19 @@ "@node-minify/utils": ["@node-minify/utils@8.0.6", "", { "dependencies": { "gzip-size": "6.0.0" } }, "sha512-csY4qcR7jUwiZmkreNTJhcypQfts2aY2CK+a+rXgXUImZiZiySh0FvwHjRnlqWKvg+y6ae9lHFzDRjBTmqlTIQ=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@opencut/web": ["@opencut/web@workspace:apps/web"], - "@opennextjs/aws": ["@opennextjs/aws@3.9.16", "", { "dependencies": { "@ast-grep/napi": "^0.40.5", "@aws-sdk/client-cloudfront": "3.984.0", "@aws-sdk/client-dynamodb": "3.984.0", "@aws-sdk/client-lambda": "3.984.0", "@aws-sdk/client-s3": "3.984.0", "@aws-sdk/client-sqs": "3.984.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.3", "aws4fetch": "^1.0.20", "chalk": "^5.6.2", "cookie": "^1.0.2", "esbuild": "0.25.4", "express": "^5.1.0", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.1.0", "yaml": "^2.8.1" }, "peerDependencies": { "next": "~15.0.8 || ~15.1.12 || ~15.2.9 || ~15.3.9 || ~15.4.11 || ~15.5.10 || ~16.0.11 || ^16.1.5" }, "bin": { "open-next": "dist/index.js" } }, "sha512-jQQStCysIllNCPqz5W2KSguXpr+ETlOcD8SyNu+h9zwpRVYk4uEPQge+ErG3avI5xsT8vKA7EGLYG59dhj/B6Q=="], + "@opennextjs/aws": ["@opennextjs/aws@3.10.4", "", { "dependencies": { "@ast-grep/napi": "^0.40.5", "@aws-sdk/client-cloudfront": "3.984.0", "@aws-sdk/client-dynamodb": "3.984.0", "@aws-sdk/client-lambda": "3.984.0", "@aws-sdk/client-s3": "3.984.0", "@aws-sdk/client-sqs": "3.984.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.3", "aws4fetch": "^1.0.20", "chalk": "^5.6.2", "cookie": "^1.0.2", "esbuild": "0.25.4", "express": "^5.1.0", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.1.0", "yaml": "^2.8.1" }, "peerDependencies": { "next": ">=15.5.15 <16 || >=16.2.3" }, "bin": { "open-next": "dist/index.js" } }, "sha512-xVmWHGdptJgVhQivuoeAYqsWpIgGoDEeZJC6AYMgvQYisDicGuS7gh10Z8MEmrgsJxNGdZ0arCCDieGxM88afw=="], - "@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.18.0", "", { "dependencies": { "@ast-grep/napi": "^0.40.5", "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.9.16", "cloudflare": "^4.4.1", "comment-json": "^4.5.1", "enquirer": "^2.4.1", "glob": "^12.0.0", "ts-tqdm": "^0.8.6", "yargs": "^18.0.0" }, "peerDependencies": { "next": "~15.0.8 || ~15.1.12 || ~15.2.9 || ~15.3.9 || ~15.4.11 || ~15.5.10 || ~16.0.11 || ^16.1.5", "wrangler": "^4.65.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-JM236YHnKzroFAZqst1t28ZGOShvnkVUDtjrp7TJ/W2P3RLo4b6npJ8VEXOn6frs6lsUfR5rvsKYLYb7h1GIJQ=="], + "@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.19.4", "", { "dependencies": { "@ast-grep/napi": "^0.40.5", "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.10.4", "ci-info": "^4.2.0", "cloudflare": "^4.4.1", "comment-json": "^4.5.1", "enquirer": "^2.4.1", "glob": "^12.0.0", "ts-tqdm": "^0.8.6", "yargs": "^18.0.0" }, "peerDependencies": { "next": ">=15.5.15 <16 || >=16.2.3", "wrangler": "^4.84.1" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-sRJmLFz6DqGzoToSTPyyQhxozXGgF/YsnHpgiCe2XTugfZZnfetMoI4BolFP65XAzIV3tdSSzCb+etK5s8UEUg=="], + + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], @@ -632,75 +706,73 @@ "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], - "@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="], - "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="], "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="], - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.17", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ=="], - "@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + "@smithy/core": ["@smithy/core@3.23.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ=="], - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="], + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="], - "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="], + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.14", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ=="], - "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="], + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA=="], - "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="], + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.14", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw=="], - "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="], + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.14", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg=="], - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], - "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="], + "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.15", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA=="], - "@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="], + "@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], - "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="], + "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ=="], - "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="], + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], - "@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="], + "@smithy/md5-js": ["@smithy/md5-js@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA=="], - "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="], + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.32", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/middleware-serde": "^4.2.20", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q=="], - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="], + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.5", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.3.0", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.4", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-wnYOpB5vATFKWrY2Z9Alb0KhjZI6AbzU6Fbz3Hq2GnURdRYWB4q+qWivQtSTwXcmWUA3MZ6krfwL6Cq5MAbxsA=="], - "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.20", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ=="], - "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="], + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="], - "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.6.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg=="], - "@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + "@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], - "@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="], - "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="], + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], - "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="], + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], - "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="], + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.3.0", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-9jKsBYQRPR0xBLgc2415RsA5PIcP2sis4oBdN9s0D13cg1B1284mNTjx9Yc+BEERXzuPm5ObktI96OxsKh8E9A=="], - "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="], + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], - "@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="], - "@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="], + "@smithy/smithy-client": ["@smithy/smithy-client@4.12.13", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/middleware-endpoint": "^4.4.32", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.25", "tslib": "^2.6.2" } }, "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA=="], - "@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + "@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="], - "@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + "@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="], "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], @@ -712,25 +784,25 @@ "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="], + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.49", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw=="], - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="], + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.54", "", { "dependencies": { "@smithy/config-resolver": "^4.4.17", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw=="], - "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="], + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg=="], "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], - "@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], - "@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="], + "@smithy/util-retry": ["@smithy/util-retry@4.3.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.3.0", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-FY1UQQ1VFmMwiYp1GVS4MeaGD5O0blLNYK0xCRHU+mJgeoH/hSY8Ld8sJWKQ6uznkh14HveRGQJncgPyNl9J+A=="], - "@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="], + "@smithy/util-stream": ["@smithy/util-stream@4.5.25", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA=="], "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="], + "@smithy/util-waiter": ["@smithy/util-waiter@4.2.16", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-GtclrKoZ3Lt7jPQ7aTIYKfjY92OgceScftVnkTsG8e1KV8rkvZgN+ny6YSRhd9hxB8rZtwVbmln7NTvE5O3GmQ=="], "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], @@ -740,57 +812,57 @@ "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], - "@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="], + "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", "tailwindcss": "4.2.1" } }, "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw=="], + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "postcss": "^8.5.6", "tailwindcss": "4.2.4" } }, "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg=="], "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], "@tsconfig/node18": ["@tsconfig/node18@1.0.3", "", {}, "sha512-RbwvSJQsuN9TB04AQbGULYfOGE/RnSFk/FLQ5b0NmDf5Kx2q/lABZbHQPKCO1vZ6Fiwkplu+yb9pGdLy1iGseQ=="], - "@turbo/darwin-64": ["@turbo/darwin-64@2.8.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ=="], + "@turbo/darwin-64": ["@turbo/darwin-64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg=="], - "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.8.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw=="], + "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw=="], - "@turbo/linux-64": ["@turbo/linux-64@2.8.20", "", { "os": "linux", "cpu": "x64" }, "sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA=="], + "@turbo/linux-64": ["@turbo/linux-64@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA=="], - "@turbo/linux-arm64": ["@turbo/linux-arm64@2.8.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA=="], + "@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g=="], - "@turbo/windows-64": ["@turbo/windows-64@2.8.20", "", { "os": "win32", "cpu": "x64" }, "sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw=="], + "@turbo/windows-64": ["@turbo/windows-64@2.9.6", "", { "os": "win32", "cpu": "x64" }, "sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g=="], - "@turbo/windows-arm64": ["@turbo/windows-arm64@2.8.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw=="], + "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A=="], "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], "@types/culori": ["@types/culori@4.0.1", "", {}, "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ=="], - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], "@types/dom-mediacapture-transform": ["@types/dom-mediacapture-transform@0.1.11", "", { "dependencies": { "@types/dom-webcodecs": "*" } }, "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ=="], @@ -802,17 +874,19 @@ "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], + "@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], - "@types/pg": ["@types/pg@8.15.4", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg=="], + "@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="], - "@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], @@ -820,13 +894,33 @@ "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/type-utils": "8.59.0", "@typescript-eslint/utils": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.0", "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0" } }, "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.0", "@typescript-eslint/tsconfig-utils": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], "@upstash/core-analytics": ["@upstash/core-analytics@0.0.10", "", { "dependencies": { "@upstash/redis": "^1.28.3" } }, "sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ=="], - "@upstash/ratelimit": ["@upstash/ratelimit@2.0.6", "", { "dependencies": { "@upstash/core-analytics": "^0.0.10" }, "peerDependencies": { "@upstash/redis": "^1.34.3" } }, "sha512-Uak5qklMfzFN5RXltxY6IXRENu+Hgmo9iEgMPOlUs2etSQas2N+hJfbHw37OUy4vldLRXeD0OzL+YRvO2l5acg=="], + "@upstash/ratelimit": ["@upstash/ratelimit@2.0.8", "", { "dependencies": { "@upstash/core-analytics": "^0.0.10" }, "peerDependencies": { "@upstash/redis": "^1.34.3" } }, "sha512-YSTMBJ1YIxsoPkUMX/P4DDks/xV5YYCswWMamU8ZIfK9ly6ppjRnVOyBhMDXBmzjODm4UQKcxsJPvaeFAijp5w=="], - "@upstash/redis": ["@upstash/redis@1.35.4", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-WE1ZnhFyBiIjTDW13GbO6JjkiMVVjw5VsvS8ENmvvJsze/caMQ5paxVD44+U68IUVmkXcbsLSoE+VIYsHtbQEw=="], + "@upstash/redis": ["@upstash/redis@1.37.0", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-LqOJ3+XWPLSZ2rGSed5DYG3ixybxb8EhZu3yQqF7MdZX1wLBG/FRcI6xcUZXHy/SS7mmXWyadrud0HJHkOc+uw=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -834,33 +928,63 @@ "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], + + "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], + "array-timsort": ["array-timsort@1.0.3", "", {}, "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ=="], + "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], + + "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], + + "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], + + "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], + + "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], + + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + "axe-core": ["axe-core@4.11.3", "", {}, "sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.23", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g=="], - "better-auth": ["better-auth@1.4.15", "", { "dependencies": { "@better-auth/core": "1.4.15", "@better-auth/telemetry": "1.4.15", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.8", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-XZr4GnFPbjvf8wip8AAjTrpGNn3Sba600zT+DgsR3NNCMWCt9aD8+nuRah6BHwHWnVP1nfnby07tPmti72SRBw=="], + "better-auth": ["better-auth@1.6.9", "", { "dependencies": { "@better-auth/core": "1.6.9", "@better-auth/drizzle-adapter": "1.6.9", "@better-auth/kysely-adapter": "1.6.9", "@better-auth/memory-adapter": "1.6.9", "@better-auth/mongo-adapter": "1.6.9", "@better-auth/prisma-adapter": "1.6.9", "@better-auth/telemetry": "1.6.9", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.5", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.14", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-EBFURtglyiEZxbx4NJBoqUD8J65dX24yC+6I9AUbIXNgUkt76mshzGbHkxZ3n/lB7Dwq3kBC+hHt0hUQsnL7HA=="], - "better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="], + "better-call": ["better-call@1.3.5", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA=="], "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], @@ -872,7 +996,11 @@ "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], - "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + "brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -880,17 +1008,21 @@ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="], + "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -904,6 +1036,8 @@ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], @@ -916,6 +1050,10 @@ "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], @@ -924,10 +1062,14 @@ "comment-json": ["comment-json@4.6.2", "", { "dependencies": { "array-timsort": "^1.0.3", "esprima": "^4.0.1" } }, "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w=="], - "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], @@ -944,17 +1086,27 @@ "culori": ["culori@4.0.2", "", {}, "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw=="], + "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], + + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], + + "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], + + "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], + "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], - "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], @@ -970,11 +1122,13 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - "drizzle-kit": ["drizzle-kit@0.31.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="], + "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], - "drizzle-orm": ["drizzle-orm@0.44.3", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-8nIiYQxOpgUicEL04YFojJmvC4DNO4KoyXsEIqN44+g6gNBr6hmVpWk3uyAt4CaTiRGDwoU+alfqNNeonLAFOQ=="], + "drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -984,17 +1138,19 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "electron-to-chromium": ["electron-to-chromium@1.5.344", "", {}, "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg=="], + "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], "embla-carousel-react": ["embla-carousel-react@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA=="], "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="], - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], + "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], @@ -1002,35 +1158,65 @@ "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "es-iterator-helpers": ["es-iterator-helpers@1.3.2", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0" } }, "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], + + "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], + + "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], + + "eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="], + + "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], @@ -1040,18 +1226,40 @@ "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], - "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + "fast-xml-builder": ["fast-xml-builder@1.1.5", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA=="], + + "fast-xml-parser": ["fast-xml-parser@5.7.1", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.5", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "feed": ["feed@5.1.0", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-qGNhgYygnefSkAHHrNHqC7p3R8J0/xQDS/cYUud8er/qD9EFGWyCdUDfULHTJQN1d3H3WprzVwMc9MfB4J50Wg=="], + "feed": ["feed@5.2.1", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-jTynzYPWs9ALjro0GW8j7sv9y7cJBeOdD4Y88kVqYy/eyusIX3g+499JiTDIlD9Ge/unebx57T4Uzo6vpYvMtA=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + "flatbuffers": ["flatbuffers@25.9.23", "", {}, "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ=="], + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], @@ -1062,7 +1270,7 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - "framer-motion": ["framer-motion@12.23.6", "", { "dependencies": { "motion-dom": "^12.23.6", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw=="], + "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], @@ -1072,6 +1280,14 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], + + "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], @@ -1084,14 +1300,20 @@ "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], - "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], + + "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob": ["glob@12.0.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + "globals": ["globals@17.5.0", "", {}, "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -1104,13 +1326,19 @@ "gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="], + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], @@ -1134,6 +1362,10 @@ "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], + + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], @@ -1148,73 +1380,157 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], + + "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], + + "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], + + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], + + "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], + + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], + + "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], + + "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], + + "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], + + "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], + + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], - "kysely": ["kysely@0.28.9", "", {}, "sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA=="], + "kysely": ["kysely@0.28.16", "", {}, "sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww=="], - "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], + "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], + "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -1222,7 +1538,7 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="], @@ -1232,7 +1548,7 @@ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], @@ -1242,7 +1558,7 @@ "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], - "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="], + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], @@ -1250,12 +1566,14 @@ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - "mediabunny": ["mediabunny@1.29.1", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-RrlKs69MxRGa/l9cMGeI4hzuTSgchOGFHk9lIAuu9EcbSdJ05gDbRsTY67dojAiyUP3ic4PExij5OqDxqSv82Q=="], + "mediabunny": ["mediabunny@1.41.0", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-cJWBHvAyRNgTbsx8Z2oHptX/PdiywwsS+GIvv2k9eioXSOzzAemBsHwf7jM6fvW3b65azvZGP9RLuO1DI8JmdA=="], "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], @@ -1298,54 +1616,72 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "miniflare": ["miniflare@4.20260317.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260317.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-qNL+yWAFMX6fr0pWU6Lx1vNpPobpnDSF1V8eunIckWvoIQl8y1oBjL2RJFEGY3un+l3f9gwW9dirDPP26usYJQ=="], + "miniflare": ["miniflare@4.20260424.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.8", "workerd": "1.20260424.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-B6MKBBd5TJ19daUc3Ae9rWctn1nDA/VCXykXfCsp9fTxyfGxnZY27tJs1caxgE9MWEMMKGbGHouqVtgKbKGxmw=="], - "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], - "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], "mnemonist": ["mnemonist@0.38.3", "", { "dependencies": { "obliterator": "^1.6.1" } }, "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw=="], - "motion": ["motion@12.23.6", "", { "dependencies": { "framer-motion": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-6U55IW5i6Vut2ryKEhrZKg55490k9d6qdGXZoNSf98oQgDj5D7bqTnVJotQ6UW3AS6QfbW6KSLa7/e1gy+a07g=="], + "motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="], - "motion-dom": ["motion-dom@12.23.6", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w=="], + "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], - "motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="], + "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], + "nanoid": ["nanoid@5.1.9", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw=="], + + "nanostores": ["nanostores@1.3.0", "", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="], - "nanostores": ["nanostores@1.1.0", "", {}, "sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "next": ["next@16.1.3", "", { "dependencies": { "@next/env": "16.1.3", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.3", "@next/swc-darwin-x64": "16.1.3", "@next/swc-linux-arm64-gnu": "16.1.3", "@next/swc-linux-arm64-musl": "16.1.3", "@next/swc-linux-x64-gnu": "16.1.3", "@next/swc-linux-x64-musl": "16.1.3", "@next/swc-win32-arm64-msvc": "16.1.3", "@next/swc-win32-x64-msvc": "16.1.3", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw=="], + "next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="], "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="], + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + + "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], + + "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], + + "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + "obliterator": ["obliterator@1.6.1", "", {}, "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -1360,39 +1696,53 @@ "onnxruntime-web": ["onnxruntime-web@1.22.0-dev.20250409-89f8206ba4", "", { "dependencies": { "flatbuffers": "^25.1.24", "guid-typescript": "^1.0.9", "long": "^5.2.3", "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4", "platform": "^1.3.6", "protobufjs": "^7.2.4" } }, "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ=="], + "opencut": ["opencut@root:", {}], + "opencut-wasm": ["opencut-wasm@0.2.10", "", {}, "sha512-dy+Z9SWwpjLjgmTAMQoMMIUmdbUk9OXoWXLoacl9xT9TCrekIGSeMM0F7bJ1H3VJwwUxbGasaVibvn5AfmeZrg=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "path-expression-matcher": ["path-expression-matcher@1.2.0", "", {}, "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], + "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="], - "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], + "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], - "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], + "pg-connection-string": ["pg-connection-string@2.12.0", "", {}, "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ=="], "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], - "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], + "pg-pool": ["pg-pool@3.13.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA=="], - "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + "pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="], "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], @@ -1400,59 +1750,71 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="], "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="], "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], - "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], - "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], - "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "protobufjs": ["protobufjs@7.5.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="], "raf-schd": ["raf-schd@4.0.3", "", {}, "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="], - "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], - "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], "react-day-picker": ["react-day-picker@8.10.1", "", { "peerDependencies": { "date-fns": "^2.28.0 || ^3.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA=="], - "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], + + "react-hook-form": ["react-hook-form@7.74.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-yR6wHr99p9wFv686jhRWVSFhUvDvNbdUf2dKlbno8/VKOCuoNobDGC6S+M2dua9A9Yo8vpcrp8assIYbsZCQ9g=="], - "react-hook-form": ["react-hook-form@7.60.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-SBrYOvMbDB7cV8ZfNpaiLcgjH/a1c7aK0lK+aNigpf4xWLO8q+o4tcvVurv3c4EOyzn/3dCsYt4GKD42VvJ/+A=="], + "react-icons": ["react-icons@5.6.0", "", { "peerDependencies": { "react": "*" } }, "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA=="], - "react-icons": ["react-icons@5.5.0", "", { "peerDependencies": { "react": "*" } }, "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw=="], + "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], - "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], @@ -1466,6 +1828,10 @@ "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], + + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "rehype-autolink-headings": ["rehype-autolink-headings@7.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-is-element": "^3.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw=="], "rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], @@ -1480,25 +1846,37 @@ "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + "resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="], + + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], - "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], @@ -1506,11 +1884,17 @@ "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], - "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], + "serialize-javascript": ["serialize-javascript@7.0.5", "", {}, "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw=="], "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], - "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], + "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], + + "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], @@ -1522,7 +1906,7 @@ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], @@ -1548,8 +1932,22 @@ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], + + "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], + + "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], + + "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], + + "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], + + "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1558,31 +1956,37 @@ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], - "strnum": ["strnum@2.2.2", "", {}, "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "style-to-js": ["style-to-js@1.1.17", "", { "dependencies": { "style-to-object": "1.0.9" } }, "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA=="], + "strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="], - "style-to-object": ["style-to-object@1.0.9", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], - "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], - "tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="], + "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], "terser": ["terser@5.16.9", "", { "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], @@ -1592,40 +1996,62 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + "ts-tqdm": ["ts-tqdm@0.8.6", "", {}, "sha512-3X3M1PZcHtgQbnwizL+xU8CAgbYbeLHrrDwL9xxcZZrV5J+e7loJm1XrXozHjSkl44J0Zg0SgA8rXbh83kCkcQ=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "turbo": ["turbo@2.8.20", "", { "optionalDependencies": { "@turbo/darwin-64": "2.8.20", "@turbo/darwin-arm64": "2.8.20", "@turbo/linux-64": "2.8.20", "@turbo/linux-arm64": "2.8.20", "@turbo/windows-64": "2.8.20", "@turbo/windows-arm64": "2.8.20" }, "bin": { "turbo": "bin/turbo" } }, "sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ=="], + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], + + "turbo": ["turbo@2.9.6", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.6", "@turbo/darwin-arm64": "2.9.6", "@turbo/linux-64": "2.9.6", "@turbo/linux-arm64": "2.9.6", "@turbo/windows-64": "2.9.6", "@turbo/windows-arm64": "2.9.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], - "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], + + "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], + + "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], + + "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "typescript-eslint": ["typescript-eslint@8.59.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.0", "@typescript-eslint/parser": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw=="], + + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], - "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="], + "undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="], - "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], - "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], - "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="], "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], @@ -1634,7 +2060,7 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], @@ -1644,9 +2070,9 @@ "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], - "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="], + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "wavesurfer.js": ["wavesurfer.js@7.10.0", "", {}, "sha512-GiyAHdorqGtUYG5fe4BfTf5lmtSLhrXoHeNlMsR80JOiOZxOrIOv9QaIR8RnqlleJ6D8R9cqvZKR9lfJcWcapg=="], + "wavesurfer.js": ["wavesurfer.js@7.12.6", "", {}, "sha512-zSxPgOFprtyJ31ppHQF0+E9jAmjAhi1rR36yIW6h1GOYdpRxDe6mbkYtlChqLK0Iz8ROBweiEFw2zus7tDFibA=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], @@ -1658,9 +2084,19 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "workerd": ["workerd@1.20260317.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260317.1", "@cloudflare/workerd-darwin-arm64": "1.20260317.1", "@cloudflare/workerd-linux-64": "1.20260317.1", "@cloudflare/workerd-linux-arm64": "1.20260317.1", "@cloudflare/workerd-windows-64": "1.20260317.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g=="], + "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], + + "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], + + "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - "wrangler": ["wrangler@4.77.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260317.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260317.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260317.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-E2Gm69+K++BFd3QvoWjC290RPQj1vDOUotA++sNHmtKPb7EP6C8Qv+1D5Ii73tfZtyNgakpqHlh8lBBbVWTKAQ=="], + "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "workerd": ["workerd@1.20260424.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260424.1", "@cloudflare/workerd-darwin-arm64": "1.20260424.1", "@cloudflare/workerd-linux-64": "1.20260424.1", "@cloudflare/workerd-linux-arm64": "1.20260424.1", "@cloudflare/workerd-windows-64": "1.20260424.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-oKsB0Xo/mfkYMdSACoS06XZg09VUK4rXwHfF/1t3P++sMbwzf4UHQvMO57+zxpEB2nVrY/ZkW0bYFGq4GdAFSQ=="], + + "wrangler": ["wrangler@4.85.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260424.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260424.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260424.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-93cwt2RPb1qdcmEgPzH7ybiLN4BIKoWpscIX6SywjHrQOeIZrQk2haoc3XMLKtQTmzapxza9OuDD+kMHpsuuhg=="], "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], @@ -1676,7 +2112,7 @@ "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], @@ -1690,7 +2126,9 @@ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "zustand": ["zustand@5.0.6", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A=="], + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + + "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], @@ -1700,11 +2138,11 @@ "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="], + "@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.2", "tslib": "^2.6.2" } }, "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g=="], - "@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="], + "@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.22", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.34", "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-/rXhMXteD+BqhFd0nYprAgcZ/KtU+963uftPqd3tiFcFfooHZINXUGtOmo2SQjRVauCTNqIEzkwuSETdZFqTTA=="], - "@better-auth/core/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.2", "tslib": "^2.6.2" } }, "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g=="], "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], @@ -1714,20 +2152,24 @@ "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "@node-minify/core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], - "@node-minify/core/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "@opencut/web/next": ["next@16.1.3", "", { "dependencies": { "@next/env": "16.1.3", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.3", "@next/swc-darwin-x64": "16.1.3", "@next/swc-linux-arm64-gnu": "16.1.3", "@next/swc-linux-arm64-musl": "16.1.3", "@next/swc-linux-x64-gnu": "16.1.3", "@next/swc-linux-x64-musl": "16.1.3", "@next/swc-win32-arm64-msvc": "16.1.3", "@next/swc-win32-x64-msvc": "16.1.3", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw=="], - "@opencut/web/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "@opencut/web/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "@opennextjs/aws/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@opennextjs/aws/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "@radix-ui/react-accordion/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-alert-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1820,54 +2262,62 @@ "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@types/pg/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@upstash/core-analytics/@upstash/redis": ["@upstash/redis@1.35.1", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-sIMuAMU9IYbE2bkgDby8KLoQKRiBMXn0moXxqLvUmQ7VUu2CvulZLtK8O0x3WQZFvvZhU5sRC2/lOVZdGfudkA=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "better-auth/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "body-parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "cloudflare/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], - "cmdk/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw=="], - - "cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "drizzle-kit/esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="], - "eciesjs/@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], "eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "finalhandler/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "global-agent/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "onnxruntime-web/onnxruntime-common": ["onnxruntime-common@1.22.0-dev.20250409-89f8206ba4", "", {}, "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ=="], + "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "path-scurry/lru-cache": ["lru-cache@11.3.5", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="], + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "radix-ui/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1876,22 +2326,24 @@ "radix-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "react-day-picker/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], - "router/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], - "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "send/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "tsx/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + "wrangler/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "wrap-ansi/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -1952,6 +2404,26 @@ "@node-minify/core/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "@opencut/web/next/@next/env": ["@next/env@16.1.3", "", {}, "sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA=="], + + "@opencut/web/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA=="], + + "@opencut/web/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q=="], + + "@opencut/web/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA=="], + + "@opencut/web/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg=="], + + "@opencut/web/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ=="], + + "@opencut/web/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow=="], + + "@opencut/web/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw=="], + + "@opencut/web/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ=="], + + "@opencut/web/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "@opennextjs/aws/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], "@opennextjs/aws/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], @@ -2064,81 +2536,75 @@ "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@types/pg/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "cloudflare/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - "cmdk/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], - - "cmdk/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ=="], - - "cmdk/@radix-ui/react-dialog/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA=="], - - "cmdk/@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "cmdk/@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "glob/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], - "cmdk/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="], + "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="], + "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="], + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="], + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="], + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="], + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="], + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="], + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="], + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="], + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="], + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="], + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="], + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="], + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="], + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="], + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - "drizzle-kit/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="], + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="], + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - "drizzle-kit/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="], + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="], + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - "drizzle-kit/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="], + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="], + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="], + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="], + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], - "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="], + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], @@ -2200,12 +2666,16 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@node-minify/core/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@node-minify/core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], "@node-minify/core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "@node-minify/core/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "@node-minify/core/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "@opencut/web/next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "@node-minify/core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], } } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..fd56f7d0 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,96 @@ +import js from "@eslint/js"; +import next from "@next/eslint-plugin-next"; +import eslintConfigPrettier from "eslint-config-prettier/flat"; +import jsxA11y from "eslint-plugin-jsx-a11y"; +import react from "eslint-plugin-react"; +import reactHooks from "eslint-plugin-react-hooks"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import preferObjectParams from "./eslint/rules/prefer-object-params.mjs"; + +const webFiles = ["apps/web/src/**/*.{ts,tsx}"]; + +const opencutEslintPlugin = { + meta: { + name: "eslint-plugin-opencut", + version: "0.0.0", + }, + rules: { + "prefer-object-params": preferObjectParams, + }, +}; + +function scopeToWebFiles(config) { + return { + ...config, + files: webFiles, + }; +} + +export default [ + { + ignores: ["**/.next/**", "**/node_modules/**", "**/dist/**", "**/build/**"], + }, + { + files: webFiles, + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + ...globals.browser, + ...globals.node, + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + settings: { + react: { + version: "detect", + }, + }, + }, + scopeToWebFiles(js.configs.recommended), + ...tseslint.configs.recommended.map(scopeToWebFiles), + scopeToWebFiles(react.configs.flat.recommended), + scopeToWebFiles(react.configs.flat["jsx-runtime"]), + scopeToWebFiles(reactHooks.configs.flat["recommended-latest"]), + scopeToWebFiles(jsxA11y.flatConfigs.recommended), + scopeToWebFiles(next.configs["core-web-vitals"]), + { + files: webFiles, + plugins: { + opencut: opencutEslintPlugin, + }, + rules: { + "@typescript-eslint/no-empty-object-type": "warn", + "@typescript-eslint/no-unsafe-type-assertion": "error", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "no-empty": "warn", + "opencut/prefer-object-params": "error", + + // `react/prop-types` is for the JS-era React workflow where runtime + // `propTypes` declarations are the prop contract. In this TS-only + // scope the prop types already are the contract; the rule's only + // effect is false positives when it can't trace destructured props + // back to a `propTypes` definition that doesn't exist. + "react/prop-types": "off", + }, + }, + scopeToWebFiles(eslintConfigPrettier), +]; diff --git a/eslint/rules/__tests__/prefer-object-params.test.mjs b/eslint/rules/__tests__/prefer-object-params.test.mjs new file mode 100644 index 00000000..7f4bc947 --- /dev/null +++ b/eslint/rules/__tests__/prefer-object-params.test.mjs @@ -0,0 +1,112 @@ +import { afterAll, describe, it } from "bun:test"; +import { RuleTester } from "eslint"; +import tseslint from "typescript-eslint"; +import rule from "../prefer-object-params.mjs"; + +RuleTester.describe = describe; +RuleTester.it = it; +RuleTester.itOnly = it.only; +RuleTester.afterAll = afterAll; + +const ruleTester = new RuleTester({ + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { jsx: true }, + }, + }, +}); + +const error = { messageId: "preferObjectParams" }; + +ruleTester.run("prefer-object-params", rule, { + valid: [ + { code: "function f() {}" }, + { code: "function f(a) {}" }, + { code: "const f = (a) => a;" }, + { code: "const f = function (a) { return a; };" }, + { code: "function f({ a, b }) { return a + b; }" }, + { code: "function f({ a, b }: { a: number; b: number }) { return a + b; }" }, + + // Direct callbacks: positional args are dictated by the caller. + { code: "[1, 2, 3].reduce((acc, cur) => acc + cur, 0);" }, + { code: "arr.map((value, index) => value + index);" }, + { code: "new Promise((resolve, reject) => {});" }, + { code: "maybeFn?.((a, b) => a + b);" }, + + // Inline option-object callbacks: caller dictates the signature + // (e.g. zustand's `persist`, tanstack-query's `onSuccess`). + { code: "someAPI({ on: (a, b) => a + b });" }, + { + code: "persist((set, get) => ({}), { migrate: (state, version) => state });", + }, + { code: "new Foo({ handler: (a, b) => a + b });" }, + { code: "maybeFn?.({ handler: (a, b) => a + b });" }, + + // Deeply-nested inline option callbacks still qualify: the entire + // option tree is supplied at the call site, so the caller dictates + // every signature inside it. Real example: better-auth's + // `betterAuth({ rateLimit: { customStorage: { set: (k, v) => ... }}})`. + { code: "someAPI({ outer: { handler: (a, b) => a + b } });" }, + + // Type predicates require a positional subject. + { + code: "function isFoo(x: unknown, ctx: Ctx): x is Foo { return true; }", + }, + { + code: "const isFoo = (x: unknown, ctx: Ctx): x is Foo => true;", + }, + ], + invalid: [ + { + code: "function f(a, b) { return a + b; }", + errors: [error], + }, + { + code: "const f = (a, b) => a + b;", + errors: [error], + }, + { + code: "const f = function (a, b) { return a + b; };", + errors: [error], + }, + { + code: "function f(a, b, c) { return a + b + c; }", + errors: [error], + }, + { + code: "function formatTime(seconds: number, fps: number) { return seconds * fps; }", + errors: [error], + }, + // A callback assigned to a name and passed indirectly is still a + // signature the author controls; the carve-out is intentionally narrow + // to direct callbacks. + { + code: "const cb = (a, b) => a + b; arr.reduce(cb, 0);", + errors: [error], + }, + // Pre-declared option object: once the object is named, its callback + // signatures are the author's to evolve. + { + code: "const opts = { handler: (a, b) => a + b }; someAPI(opts);", + errors: [error], + }, + // The error must point at the function's declaration line, so a + // `// eslint-disable-next-line` placed above the declaration (the + // conventional spot) suppresses the violation even when the + // signature spans multiple lines. Reporting at a parameter row + // instead leaves the directive inert and `reportUnusedDisableDirectives` + // then flags every suppressed call site. + { + code: [ + "function f(", + "\ta: number,", + "\tb: number,", + ") { return a + b; }", + ].join("\n"), + errors: [{ messageId: "preferObjectParams", line: 1 }], + }, + ], +}); diff --git a/eslint/rules/prefer-object-params.mjs b/eslint/rules/prefer-object-params.mjs new file mode 100644 index 00000000..a5a8caae --- /dev/null +++ b/eslint/rules/prefer-object-params.mjs @@ -0,0 +1,107 @@ +export default { + meta: { + type: "suggestion", + docs: { + description: + "Prefer a single destructured object parameter over multiple positional parameters", + }, + schema: [], + messages: { + preferObjectParams: + "Use a single destructured object parameter instead of multiple positional parameters. Example: function someFunction({ name, age }: { name: string, age: number }) {}", + }, + }, + create(context) { + function isCallArgument(node) { + return ( + node && + (node.type === "CallExpression" || + node.type === "NewExpression" || + node.type === "OptionalCallExpression") + ); + } + + function isDirectCallback(node) { + const parent = node.parent; + return ( + isCallArgument(parent) && parent.arguments.includes(node) + ); + } + + // Some APIs receive callbacks via a property somewhere inside an inline + // option object (e.g. `persist(state, { migrate: (s, v) => ... })` or + // `betterAuth({ rateLimit: { customStorage: { set: (k, v) => ... }}})`). + // The caller still dictates the signature, so the same carve-out as + // `isDirectCallback` applies. Pre-declared option objects don't qualify — + // once you name the object, the signature is yours to evolve. + function isInlineOptionCallback(node) { + let current = node; + let property = current.parent; + if ( + !property || + property.type !== "Property" || + property.value !== current + ) { + return false; + } + + let objectExpression = property.parent; + while (objectExpression?.type === "ObjectExpression") { + const parent = objectExpression.parent; + if (isCallArgument(parent) && parent.arguments.includes(objectExpression)) { + return true; + } + + if ( + parent?.type !== "Property" || + parent.value !== objectExpression + ) { + return false; + } + + property = parent; + objectExpression = property.parent; + } + + return false; + } + + // Type predicates (`x is Foo`) require a positional subject by language + // rule, so the readability/evolution argument doesn't apply. AGENTS.md + // carves these out explicitly. + function isTypePredicate(node) { + return node.returnType?.typeAnnotation?.type === "TSTypePredicate"; + } + + function reportIfNeeded(node) { + if ( + node.params.length <= 1 || + isDirectCallback(node) || + isInlineOptionCallback(node) || + isTypePredicate(node) + ) { + return; + } + + // Report from the function's first line through params[1] so a + // conventional `// eslint-disable-next-line` placed above the + // declaration suppresses the violation. The directive matches + // against `loc.start.line`, which must land on the function + // declaration line — not on a later parameter row. + context.report({ + node, + loc: { + start: node.loc.start, + end: node.params[1].loc.end, + }, + messageId: "preferObjectParams", + }); + } + + return { + ArrowFunctionExpression: reportIfNeeded, + FunctionDeclaration: reportIfNeeded, + FunctionExpression: reportIfNeeded, + }; + }, +}; diff --git a/package.json b/package.json index 941dd0ad..0e58b6bb 100644 --- a/package.json +++ b/package.json @@ -13,26 +13,36 @@ "dev:tools": "turbo run dev --filter=@opencut/tools", "dev:wasm": "cargo watch -w rust/crates -w rust/wasm/src -s 'wasm-pack build rust/wasm --target bundler --out-dir pkg'", "dev:web": "turbo run dev --filter=@opencut/web", - "format:web": "biome format apps/web/src/services/renderer --write --max-diagnostics=1000", + "format:web": "prettier apps/web/src/services/renderer --write", "generate:fonts": "npx tsx apps/web/scripts/generate-font-sprites.ts", - "lint:web": "biome lint apps/web/src --max-diagnostics=1000", - "lint:web:fix": "biome lint apps/web/src --write --unsafe --max-diagnostics=1000", + "lint:web": "eslint apps/web/src --ext .ts,.tsx", + "lint:web:fix": "eslint apps/web/src --ext .ts,.tsx --fix", "preview:web": "turbo run preview --filter=@opencut/web", "publish:wasm": "bun run build:wasm && npm publish rust/wasm/pkg --access public", "start:tools": "turbo run start --filter=@opencut/tools", "test": "bun test" }, "dependencies": { - "@biomejs/biome": "^2.4.8", "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", "better-auth": "^1.4.15", "next": "^16.1.3", + "opencut": ".", "opencut-wasm": "^0.2.10" }, "devDependencies": { + "@eslint/js": "^9.39.4", + "@next/eslint-plugin-next": "^16.2.4", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", + "globals": "^17.5.0", + "prettier": "^3.8.3", "turbo": "^2.8.20", - "typescript": "^6.0.2" + "typescript": "^6.0.2", + "typescript-eslint": "^8.59.0" }, "trustedDependencies": [ "@tailwindcss/oxide" @@ -0,0 +1,977 @@ + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/actions/keybindings/migrations/v2-to-v3.ts + 10:13 error Unsafe type assertion: type 'V2State' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 21:13 error Unsafe type assertion: type 'ShortcutKey' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 21:35 error Unsafe type assertion: type 'TActionWithOptionalArgs' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/actions/keybindings/migrations/v3-to-v4.ts + 11:13 error Unsafe type assertion: type 'V3State' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 20:13 error Unsafe type assertion: type 'ShortcutKey' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 20:35 error Unsafe type assertion: type 'TActionWithOptionalArgs' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/actions/keybindings/migrations/v4-to-v5.ts + 9:13 error Unsafe type assertion: type 'V4State' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/actions/keybindings/migrations/v5-to-v6.ts + 9:13 error Unsafe type assertion: type 'V5State' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/actions/keybindings/migrations/v6-to-v7.ts + 9:13 error Unsafe type assertion: type 'V6State' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 12:20 error Unsafe type assertion: type 'ShortcutKey[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 13:29 error Unsafe type assertion: type 'never' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/actions/registry.ts + 14:1 error Unused eslint-disable directive (no problems were reported from 'opencut/prefer-object-params') + 17:2 error Use a single destructured object parameter instead of multiple positional parameters. Example: function someFunction({ name, age }: { name: string, age: number }) {} opencut/prefer-object-params + 20:23 error Unsafe type assertion: type 'ActionHandler' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 28:1 error Unused eslint-disable directive (no problems were reported from 'opencut/prefer-object-params') + 31:2 error Use a single destructured object parameter instead of multiple positional parameters. Example: function someFunction({ name, age }: { name: string, age: number }) {} opencut/prefer-object-params + 36:23 error Unsafe type assertion: type 'ActionHandler' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 57:1 error Unused eslint-disable directive (no problems were reported from 'opencut/prefer-object-params') + 60:2 error Use a single destructured object parameter instead of multiple positional parameters. Example: function someFunction({ name, age }: { name: string, age: number }) {} opencut/prefer-object-params + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/actions/use-action-handler.ts + 11:1 error Unused eslint-disable directive (no problems were reported from 'opencut/prefer-object-params') + 14:2 error Use a single destructured object parameter instead of multiple positional parameters. Example: function someFunction({ name, age }: { name: string, age: number }) {} opencut/prefer-object-params + 24:24 error Unsafe type assertion: type 'TActionFunc<A>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 27:5 error Unsafe type assertion: type '(handlerParameters_0: TArgOfAction<A>, handlerParameters_1?: TInvocationTrigger | undefined) => void' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/animation/interpolation.ts + 163:10 error Unsafe type assertion: the original type is assignable to the constraint of type 'TChannel', but 'TChannel' could be instantiated with a different subtype of its constraint @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/animation/keyframe-query.ts + 116:26 error Unsafe type assertion: type 'Record<string, AnimationComponentValue | undefined>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/animation/keyframes.ts + 23:2 warning 'getBezierPoint' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + 518:14 error Unsafe type assertion: type 'string | boolean' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 524:14 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 637:12 error Unsafe type assertion: type 'AnimationChannel' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 671:12 error Unsafe type assertion: type 'AnimationChannel' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 875:14 error Unsafe type assertion: type 'AnimationChannel' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 971:13 error Unsafe type assertion: type 'string | boolean' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 980:13 error Unsafe type assertion: type 'string | boolean' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 1067:25 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/animation/property-registry.ts + 129:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 145:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 159:50 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 172:50 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 185:50 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 196:30 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 206:29 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 217:28 error Unsafe type assertion: type 'string' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 231:51 error Unsafe type assertion: type 'string' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 246:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 261:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 276:53 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 291:53 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 310:58 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/animation/resolve.ts + 60:26 error Unsafe type assertion: type 'Record<string, AnimationComponentValue | undefined>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 75:9 error Unsafe type assertion: type 'AnimationValueForPath<TPath>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/animation/types.ts + 1:15 warning 'ParamValues' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + 104:18 warning An interface declaring no members is equivalent to its supertype @typescript-eslint/no-empty-object-type + 146:18 warning An interface declaring no members is equivalent to its supertype @typescript-eslint/no-empty-object-type + 149:18 warning An interface declaring no members is equivalent to its supertype @typescript-eslint/no-empty-object-type + 157:18 warning An interface declaring no members is equivalent to its supertype @typescript-eslint/no-empty-object-type + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/app/brand/page.tsx + 193:48 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/app/contributors/page.tsx + 48:24 error Unsafe assertion from `any` detected: consider using type guards or a safer assertion @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/app/editor/[project_id]/page.tsx + 40:20 error Unsafe type assertion: type 'string' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/app/layout.tsx + 5:10 warning 'ChangelogNotification' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/app/privacy/page.tsx + 60:18 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 60:64 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 172:6 error Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations react/jsx-no-target-blank + 189:6 error Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations react/jsx-no-target-blank + 205:6 error Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations react/jsx-no-target-blank + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/app/projects/page.tsx + 880:15 error Unsafe type assertion: type 'MouseEvent<HTMLButtonElement, MouseEvent>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 886:15 error Unsafe type assertion: type 'KeyboardEvent<HTMLButtonElement>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 981:24 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + 981:38 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/app/terms/page.tsx + 54:15 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 54:55 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 56:26 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + 56:32 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + 56:42 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 112:9 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 113:9 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 130:6 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + 130:12 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + 130:73 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 146:6 error Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations react/jsx-no-target-blank + 164:12 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 169:12 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 183:12 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + 203:6 error Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations react/jsx-no-target-blank + 219:6 error Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations react/jsx-no-target-blank + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/auth/server.ts + 31:12 error Unsafe type assertion: type '{ key: string; count: number; lastRequest: number; } | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/blog/query.ts + 77:10 error Unsafe assertion from `any` detected: consider using type guards or a safer assertion @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/changelog/components/changelog-notification.tsx + 45:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/changelog/components/changelog-notification.tsx:45:3 + 43 | } + 44 | +> 45 | setRelease(latest); + | ^^^^^^^^^^ Avoid calling setState() directly within an effect + 46 | }, []); + 47 | + 48 | if (!release) return null; react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/clipboard/handlers/index.ts + 48:32 error Unsafe type assertion: type 'never' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/scene/remove-bookmark.ts + 13:33 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/scene/toggle-bookmark.ts + 13:33 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/effects/add-effect.ts + 52:14 error Unsafe type assertion: type 'VisualElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/effects/remove-effect.ts + 50:14 error Unsafe type assertion: type 'VisualElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/effects/reorder-effect.ts + 57:14 error Unsafe type assertion: type 'VisualElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/effects/toggle-effect.ts + 52:14 error Unsafe type assertion: type 'VisualElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/effects/update-effect-params.ts + 70:15 error Unsafe type assertion: type 'VisualElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/masks/delete-custom-mask-points.ts + 104:15 error Unsafe type assertion: type 'MaskableElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/masks/insert-custom-mask-point.ts + 134:15 error Unsafe type assertion: type 'MaskableElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/masks/remove-mask.ts + 50:15 error Unsafe type assertion: type 'MaskableElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/commands/timeline/element/masks/toggle-mask-inverted.ts + 61:15 error Unsafe type assertion: type 'MaskableElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/editor/mobile-gate.tsx + 23:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/editor/mobile-gate.tsx:23:3 + 21 | const isMobile = window.innerWidth < 1024; + 22 | const acknowledged = localStorage.getItem(STORAGE_KEY) === "true"; +> 23 | setShow(isMobile && !acknowledged); + | ^^^^^^^ Avoid calling setState() directly within an effect + 24 | }, []); + 25 | + 26 | if (show === null) return null; react-hooks/set-state-in-effect + 55:18 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/editor/panels/assets/assets-panel-store.tsx + 14:2 warning 'ColorsIcon' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + 34:2 error Component definition is missing display name react/display-name + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/editor/panels/assets/views/settings/background.tsx + 90:3 error 'backgrounds' is missing in props validation react/prop-types + 91:3 error 'currentBackgroundColor' is missing in props validation react/prop-types + 92:3 error 'isColorBackground' is missing in props validation react/prop-types + 93:3 error 'onSelect' is missing in props validation react/prop-types + 94:3 error 'useBackgroundColor' is missing in props validation react/prop-types + 104:17 error 'backgrounds.map' is missing in props validation react/prop-types + 110:53 error 'currentBackgroundColor.toLowerCase' is missing in props validation react/prop-types + 225:6 error Unsafe type assertion: type '{ blurIntensity: number; }' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 230:6 error Unsafe type assertion: type '{ color: string; }' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/editor/panels/properties/empty-view.tsx + 13:43 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/footer.tsx + 83:9 error Unsafe type assertion: type 'Category[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/header.tsx + 19:28 warning 'SITE_URL' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/providers/editor-provider.tsx + 137:3 error Error: This value cannot be modified + +Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed. + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/providers/editor-provider.tsx:137:3 + 135 | + 136 | useEffect(() => { +> 137 | editor.command.isRippleEnabled = rippleEditingEnabled; + | ^^^^^^^^^^^^^^ `editor` cannot be modified + 138 | }, [editor, rippleEditingEnabled]); + 139 | + 140 | useEffect(() => { react-hooks/immutability + 144:5 error Unsafe type assertion: type '{ returnValue: string; }' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/storage-provider.tsx + 76:5 warning React Hook useEffect has a missing dependency: 'editor.project'. Either include it or remove the dependency array react-hooks/exhaustive-deps + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/alert.tsx + 48:2 error Headings must have content and the content must be accessible by a screen reader jsx-a11y/heading-has-content + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/color-picker.tsx + 81:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/color-picker.tsx:81:3 + 79 | + 80 | useEffect(() => { +> 81 | setInputValue(formatColorValue({ hex: value, format: colorFormat })); + | ^^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 82 | }, [value, colorFormat]); + 83 | + 84 | useEffect(() => { react-hooks/set-state-in-effect + 376:23 error Unsafe type assertion: type 'ColorFormat' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 435:4 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/color-picker.tsx:435:4 + 433 | + 434 | useEffect(() => { +> 435 | setInputValue(value); + | ^^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 436 | }, [value]); + 437 | + 438 | const commitInputValue = (raw: string) => { react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/dropdown-menu.tsx + 145:6 error Unsafe type assertion: type 'ReactElement<{ children?: ReactNode; }, string | JSXElementConstructor<any>>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 148:7 error Unsafe type assertion: type 'ReactElement<{ children?: ReactNode; }, string | JSXElementConstructor<any>>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/font-picker.tsx + 80:4 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/font-picker.tsx:80:4 + 78 | useEffect(() => { + 79 | if (!open) { +> 80 | setSearch(""); + | ^^^^^^^^^ Avoid calling setState() directly within an effect + 81 | setActiveTab("all"); + 82 | } + 83 | }, [open]); react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/form.tsx + 59:6 error Unsafe assertion from `any` detected: consider using type guards or a safer assertion @typescript-eslint/no-unsafe-type-assertion + 113:2 error Unsafe type assertion: type 'FormFieldContextValue<FieldValues, string>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 157:2 error Unsafe type assertion: type 'FormItemContextValue' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/number-field.tsx + 134:2 warning 'ref' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars + 148:4 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/number-field.tsx:148:4 + 146 | useLayoutEffect(() => { + 147 | if (!suffix) { +> 148 | setSuffixLeft(0); + | ^^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 149 | return; + 150 | } + 151 | if (!ghostRef.current || !inputRef.current) return; react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/prose.tsx + 92:19 error Unsafe type assertion: type 'HastRootNode' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/resizable.tsx + 23:2 warning 'withHandle' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/sonner.tsx + 13:11 error Unsafe type assertion: type '"system" | "light" | "dark" | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/components/ui/table.tsx + 72:6 error 'className' is missing in props validation react/prop-types + 87:6 error 'className' is missing in props validation react/prop-types + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/core/managers/audio-manager.ts + 228:12 warning Empty block statement no-empty + 473:17 error Unsafe type assertion: type '{ mode?: unknown; } | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/core/managers/playback-manager.ts + 246:28 warning 'time' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars + 252:30 warning 'time' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/core/managers/project-manager.ts + 558:24 error Unsafe type assertion: type '[TProjectSortKey, "desc" | "asc"]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/core/managers/renderer-manager.ts + 9:10 warning 'frameRateToFloat' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/core/managers/timeline-manager.ts + 562:6 error Unsafe type assertion: type 'AnimationValueForPath<AnimationPath> | null' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 722:24 error Unsafe type assertion: type '"trimEnd" | "trimStart" | "animations" | "type" | "id" | "name" | "duration" | "startTime" | "sourceDuration"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 728:27 error Unsafe type assertion: type 'Partial<TimelineElement>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 787:9 error Unsafe type assertion: type 'TimelineElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/editor/panel-store.ts + 46:19 error Unsafe type assertion: type '{ panels?: Partial<PanelSizes> | null | undefined; toolsPanel?: number | undefined; previewPanel?: number | undefined; propertiesPanel?: number | undefined; ... 4 more ...; properties?: number | undefined; } | null | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/feedback/components/feedback-popover.tsx + 35:20 error Unsafe assertion from `any` detected: consider using type guards or a safer assertion @typescript-eslint/no-unsafe-type-assertion + 191:10 warning Compilation Skipped: Use of incompatible library + +This API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized. + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/feedback/components/feedback-popover.tsx:191:10 + 189 | )} + 190 | <div className="flex gap-2"> +> 191 | {!form.watch("message").trim() && ( + | ^^^^ React Hook Form's `useForm()` API returns a `watch()` function which cannot be memoized safely. + 192 | <Button + 193 | type="button" + 194 | variant="outline" react-hooks/incompatible-library + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/fonts/use-font-atlas.ts + 23:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/fonts/use-font-atlas.ts:23:3 + 21 | if (!open || atlas) return; + 22 | +> 23 | setStatus("loading"); + | ^^^^^^^^^ Avoid calling setState() directly within an effect + 24 | loadFontAtlas().then((data) => { + 25 | if (data) { + 26 | setAtlas(data); react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/gradients/canvas.ts + 463:20 error Unsafe type assertion: type 'PositionKeyword' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 464:20 error Unsafe type assertion: type 'PositionKeyword' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 631:18 error Unsafe type assertion: type 'PositionKeyword' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/gradients/parser.ts + 331:17 error Unsafe type assertion: type 'Shape | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 347:18 error Unsafe type assertion: type 'Shape | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/graphics/definitions/ellipse.ts + 50:23 error Unsafe type assertion: type 'GraphicStrokeAlign' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/graphics/definitions/polygon.ts + 176:23 error Unsafe type assertion: type 'GraphicStrokeAlign' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/graphics/definitions/rectangle.ts + 61:23 error Unsafe type assertion: type 'GraphicStrokeAlign' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/graphics/definitions/star.ts + 72:23 error Unsafe type assertion: type 'GraphicStrokeAlign' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/graphics/stroke.ts + 75:21 error Unsafe type assertion: type 'GraphicRenderContext | null' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/hooks/use-mobile.ts + 14:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/hooks/use-mobile.ts:14:3 + 12 | }; + 13 | mql.addEventListener("change", onChange); +> 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + | ^^^^^^^^^^^ Avoid calling setState() directly within an effect + 15 | return () => mql.removeEventListener("change", onChange); + 16 | }, []); + 17 | react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/__tests__/snap.test.ts + 153:9 error Unsafe type assertion: type '[{ x: number; y: number; }, { x: number; y: number; }]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/components/masks-tab.tsx + 222:19 error Unsafe type assertion: type 'Partial<MaskableElement>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/custom-path.ts + 20:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 44:9 error Unsafe type assertion: type 'CustomMaskHandlePart' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/cinematic-bars.ts + 98:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 114:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/custom.ts + 451:19 error Unsafe type assertion: type 'CustomMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 474:19 error Unsafe type assertion: type 'CustomMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/diamond.ts + 72:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 84:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/ellipse.ts + 39:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 55:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/heart.ts + 114:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 126:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/rectangle.ts + 69:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 81:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/split.ts + 78:41 error Unsafe type assertion: type 'SplitMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 272:43 error Unsafe type assertion: type 'SplitMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 297:43 error Unsafe type assertion: type 'SplitMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/star.ts + 112:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 124:19 error Unsafe type assertion: type 'RectangleMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/definitions/text.ts + 374:19 error Unsafe type assertion: type 'TextMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 395:19 error Unsafe type assertion: type 'TextMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/index.ts + 17:9 error Unsafe type assertion: type 'Mask' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/registry.ts + 101:51 error Unsafe type assertion: type 'never' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 104:18 error Unsafe type assertion: type 'never' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 104:48 error Unsafe type assertion: type 'never' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 108:41 error Unsafe type assertion: the original type is assignable to the constraint of type 'TParams', but 'TParams' could be instantiated with a different subtype of its constraint @typescript-eslint/no-unsafe-type-assertion + 114:42 error Unsafe type assertion: type 'never' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/snap.ts + 316:10 error Unsafe type assertion: type 'MaskSnapResult<TParams>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 325:10 error Unsafe type assertion: type 'MaskSnapResult<TParams>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 328:9 error Unsafe type assertion: type 'MaskSnapResult<TParams>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/masks/use-mask-handles.ts + 140:22 error Unsafe type assertion: type 'MaskableElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 199:8 warning The 'customMaskPointIds' conditional could make the dependencies of useEffect Hook (at line 253) change on every render. To fix this, wrap the initialization of 'customMaskPointIds' in its own useMemo() Hook react-hooks/exhaustive-deps + 199:8 warning The 'customMaskPointIds' conditional could make the dependencies of useCallback Hook (at line 444) change on every render. To fix this, wrap the initialization of 'customMaskPointIds' in its own useMemo() Hook react-hooks/exhaustive-deps + 407:27 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 437:26 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 569:13 error Unsafe type assertion: type 'SplitMaskParams | RectangleMaskParams | TextMaskParams | CustomMaskParams' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/media/processing.ts + 10:18 warning An interface declaring no members is equivalent to its supertype @typescript-eslint/no-empty-object-type + 199:19 error Unsafe type assertion: type 'HTMLVideoElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/media/use-paste-media.ts + 43:26 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/components/overlay-layer.tsx + 157:5 error Unsafe type assertion: type '[PreviewOverlayHudAnchor, PreviewOverlayInstance[]][]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/components/preview-viewport.tsx + 467:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/components/preview-viewport.tsx:467:3 + 465 | + 466 | useEffect(() => { +> 467 | setZoomState(1); + | ^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 468 | setCenter({ + 469 | x: canvasWidth / 2, + 470 | y: canvasHeight / 2, react-hooks/set-state-in-effect + 477:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/components/preview-viewport.tsx:477:3 + 475 | + 476 | useEffect(() => { +> 477 | setCenter((previousCenter) => + | ^^^^^^^^^ Avoid calling setState() directly within an effect + 478 | clampViewportCenter({ + 479 | canvasHeight, + 480 | canvasWidth, react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/components/toolbar.tsx + 25:10 warning 'GridPopover' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + 26:10 warning 'usePreviewStore' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/controllers/preview-interaction-controller.ts + 354:19 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/controllers/transform-handle-controller.ts + 384:13 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 418:13 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 459:13 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/preview/preview-store.ts + 86:19 error Unsafe type assertion: type 'PersistedPreviewState | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/project/components/delete-project-dialog.tsx + 63:13 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + 63:20 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/ripple/diff.ts + 1:45 warning 'TimelineTrack' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/selection/selectable-surface.tsx + 212:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/selection/selectable-surface.tsx:212:3 + 210 | + 211 | useEffect(() => { +> 212 | setSelectionState((state) => + | ^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 213 | pruneSelection({ + 214 | state, + 215 | orderedIds, react-hooks/set-state-in-effect + 229:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/selection/selectable-surface.tsx:229:3 + 227 | } + 228 | +> 229 | setHighlightedId(revealId); + | ^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 230 | getItemElement(revealId)?.scrollIntoView({ block: "center" }); + 231 | + 232 | const timer = setTimeout(() => { react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/renderer/canvas-renderer.ts + 43:18 error Unsafe type assertion: type 'OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 71:18 error Unsafe type assertion: type 'OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/renderer/compositor/frame-descriptor.ts + 421:26 error Unsafe type assertion: type 'OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D | null' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 472:22 error Unsafe type assertion: type 'OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D | null' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/renderer/compositor/wasm-compositor.ts + 94:5 error Unsafe assertion from `any` detected: consider using type guards or a safer assertion @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/renderer/effect-preview.ts + 110:15 error Unsafe type assertion: type 'OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D | null' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 149:10 error Unsafe type assertion: type 'OffscreenCanvas | HTMLCanvasElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/renderer/mask-feather.ts + 14:9 error Unsafe type assertion: type 'OffscreenCanvas | HTMLCanvasElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/renderer/nodes/base-node.ts + 12:28 error Unsafe type assertion: the original type is assignable to the constraint of type 'Params', but 'Params' could be instantiated with a different subtype of its constraint @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/renderer/nodes/graphic-node.ts + 55:15 error Unsafe type assertion: type 'OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D | null' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/components/storage-persistence-dialog.tsx + 21:22 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/indexeddb-adapter.ts + 30:17 error Unsafe type assertion: type 'IDBOpenDBRequest' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 88:38 error Unsafe type assertion: type 'string[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts + 25:12 error Unsafe type assertion: type 'unknown[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 35:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 51:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts + 28:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 38:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 47:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 48:23 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 61:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 62:23 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 70:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 80:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 105:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 118:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 122:23 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 138:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 139:23 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 186:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 188:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 243:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 245:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 305:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 306:19 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 307:21 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v15-to-v16.test.ts + 65:5 error Unsafe type assertion: type '{ tracks: { type: string; }[]; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v16-to-v17.test.ts + 101:6 error Unsafe type assertion: type '{ tracks: { elements: { masks: { params: Record<string, unknown>; }[]; }[]; }[]; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v18-to-v19.test.ts + 30:5 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 33:5 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 36:5 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v19-to-v20.test.ts + 86:5 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 86:6 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 95:5 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 95:6 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 95:7 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 124:5 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 124:6 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 124:7 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts + 20:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 27:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 39:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 48:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 58:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 94:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 125:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 159:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts + 30:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 31:22 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 48:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 49:22 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 68:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v21-to-v22.test.ts + 80:18 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 81:18 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 82:20 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 83:22 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 84:20 error Unsafe type assertion: type 'Record<string, Record<string, unknown>>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 85:20 error Unsafe type assertion: type 'Record<string, Record<string, unknown>>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v22-to-v23.test.ts + 104:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 107:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 110:29 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 117:18 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 129:18 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 130:20 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 138:22 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 139:20 error Unsafe type assertion: type 'Record<string, Record<string, unknown>>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts + 62:18 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 63:18 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 64:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 65:20 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 66:17 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 67:24 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 68:27 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v27-to-v28.test.ts + 99:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 102:29 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 110:18 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 121:18 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 122:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 123:20 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 131:22 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 132:20 error Unsafe type assertion: type 'Record<string, Record<string, unknown>>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 207:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 210:29 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 220:18 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 224:18 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 225:21 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 226:20 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v3-to-v4.test.ts + 58:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 61:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 64:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 108:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 111:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 114:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v4-to-v5.test.ts + 52:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 55:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 58:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 105:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 108:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 111:4 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts + 17:4 error Unsafe type assertion: type '{ bookmarks: unknown[]; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 26:4 error Unsafe type assertion: type '{ bookmarks: unknown[]; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 87:4 error Unsafe type assertion: type '{ bookmarks: unknown[]; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/__tests__/v8-to-v9.test.ts + 74:4 error Unsafe type assertion: type '{ tracks: { elements: unknown[]; }[]; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 76:20 error Unsafe type assertion: type '{ background: { enabled: boolean; color: string; }; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 90:10 error Unsafe type assertion: type 'Record<string, unknown>[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 119:20 error Unsafe type assertion: type '{ background: { enabled: boolean; }; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 120:4 error Unsafe type assertion: type '{ tracks: { elements: unknown[]; }[]; }[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts + 318:12 error Unsafe type assertion: type 'LegacyMediaTrack' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 357:24 error Unsafe type assertion: type 'LegacyMediaElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 445:24 error Unsafe type assertion: type 'LegacyTextElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 492:16 error Unsafe type assertion: type '"left" | "right" | "center"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 496:17 error Unsafe type assertion: type '"bold" | "normal"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 500:16 error Unsafe type assertion: type '"normal" | "italic"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 504:21 error Unsafe type assertion: type '"none" | "underline" | "line-through"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 541:25 error Unsafe type assertion: type 'LegacyAudioElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts + 97:48 error Unsafe type assertion: type 'Record<string, unknown> & { position: unknown; }' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/transformers/v21-to-v22.ts + 60:11 warning 'LegacyVectorKeyframe' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts + 19:20 error Unsafe type assertion: type 'Record<string, unknown>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts + 173:4 error Unsafe type assertion: type '"bold" | "normal"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/opfs-adapter.ts + 23:9 error Unsafe type assertion: type 'Error' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 50:9 error Unsafe type assertion: type 'Error' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/service.ts + 34:16 error Unsafe assertion from `any` detected: consider using type guards or a safer assertion @typescript-eslint/no-unsafe-type-assertion + 270:16 error Unsafe type assertion: type 'TScene[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/use-local-storage.ts + 23:25 error Unsafe assertion from `any` detected: consider using type guards or a safer assertion @typescript-eslint/no-unsafe-type-assertion + 25:5 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/storage/use-local-storage.ts:25:5 + 23 | const parsedValue = JSON.parse(storedValue) as T; + 24 | valueRef.current = parsedValue; +> 25 | setValue(parsedValue); + | ^^^^^^^^ Avoid calling setState() directly within an effect + 26 | } + 27 | } catch { + 28 | // localstorage might be unavailable react-hooks/set-state-in-effect + 48:9 error Unsafe type assertion: type '(previousValue: T) => T' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/services/transcription/worker.ts + 60:17 error Unsafe type assertion: type 'AutomaticSpeechRecognitionPipeline' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/sounds/components/assets-view.tsx + 194:16 error Unsafe type assertion: type 'UIEvent<HTMLDivElement, UIEvent>' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/stickers/components/assets-view.tsx + 77:38 error Unsafe type assertion: type '"all" | "flags" | "shapes"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 301:19 error Unsafe type assertion: type '"all" | "flags" | "shapes"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 341:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/stickers/components/assets-view.tsx:341:3 + 339 | } + 340 | +> 341 | setHasImageError(false); + | ^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 342 | }, [item.id]); + 343 | + 344 | const displayName = item.name; react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/stickers/index.ts + 196:20 error Unsafe type assertion: type '"all" | "flags" | "shapes"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 264:21 error Unsafe type assertion: type '"all" | "flags" | "shapes"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/stickers/stickers-store.ts + 214:25 error Unsafe type assertion: type '{ selectedCategory?: string | undefined; recentStickers?: string[] | undefined; }' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 223:12 error Unsafe type assertion: type '"all" | "flags" | "shapes"' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/subtitles/ass.ts + 314:9 error Unsafe type assertion: the original type is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of its constraint @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/text/layout.ts + 32:4 error Unsafe type assertion: type 'CanvasRenderingContext2D & { letterSpacing: string; }' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/animation-properties.ts + 12:10 warning 'isAnimationPropertyPath' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + 128:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 144:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 158:50 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 171:50 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 184:50 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 195:30 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 205:29 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 216:28 error Unsafe type assertion: type 'string' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 230:51 error Unsafe type assertion: type 'string' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 245:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 260:54 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 275:53 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 290:53 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 309:58 error Unsafe type assertion: type 'number' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/bookmarks/components/bookmarks.tsx + 99:40 error Unsafe type assertion: type 'Node' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 103:40 error Unsafe type assertion: type 'Node' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 308:3 error Error: Calling setState synchronously within an effect can trigger cascading renders + +Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following: +* Update external systems with the latest state from React. +* Subscribe for updates from some external system, calling setState in a callback function when external state changes. + +Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/bookmarks/components/bookmarks.tsx:308:3 + 306 | + 307 | useEffect(() => { +> 308 | setDraftColorHex( + | ^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect + 309 | (bookmark.color ?? DEFAULT_TIMELINE_BOOKMARK_COLOR) + 310 | .replace("#", "") + 311 | .toUpperCase(), react-hooks/set-state-in-effect + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts + 54:8 warning The 'bookmarks' logical expression could make the dependencies of useCallback Hook (at line 123) change on every render. Move it inside the useCallback callback. Alternatively, wrap the initialization of 'bookmarks' in its own useMemo() Hook react-hooks/exhaustive-deps + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/components/audio-volume-line.tsx + 245:5 error Visible, non-interactive elements with click handlers must have at least one keyboard listener jsx-a11y/click-events-have-key-events + 245:5 error Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element jsx-a11y/no-static-element-interactions + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/components/drop-target.ts + 119:8 warning 'mainTrackIndex' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/components/index.tsx + 515:8 error Visible, non-interactive elements with click handlers must have at least one keyboard listener jsx-a11y/click-events-have-key-events + 515:8 error Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element jsx-a11y/no-static-element-interactions + 895:9 error Visible, non-interactive elements with click handlers must have at least one keyboard listener jsx-a11y/click-events-have-key-events + 895:9 error Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element jsx-a11y/no-static-element-interactions + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/components/timeline-element.tsx + 810:3 error Visible, non-interactive elements with click handlers must have at least one keyboard listener jsx-a11y/click-events-have-key-events + 810:3 error Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element jsx-a11y/no-static-element-interactions + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/components/timeline-track.tsx + 65:4 error Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element jsx-a11y/no-static-element-interactions + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/controllers/playhead-controller.ts + 145:45 error Unsafe type assertion: type 'Node' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/controllers/seek-controller.ts + 162:18 error Unsafe type assertion: type 'HTMLElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/controllers/zoom-controller.ts + 280:46 error Unsafe type assertion: type 'Node' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/element-utils.ts + 211:11 error Unsafe type assertion: type 'ParamValues' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/hooks/use-element-preview.ts + 25:4 error Unsafe type assertion: type 'T | undefined' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/index.ts + 27:23 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/placement/apply.ts + 9:2 warning 'TimelineTrack' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars + 123:13 error Unsafe type assertion: type 'AudioElement[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 140:15 error Unsafe type assertion: type '(VideoElement | ImageElement)[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 145:15 error Unsafe type assertion: type 'TextElement[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 150:15 error Unsafe type assertion: type '(StickerElement | GraphicElement)[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 155:15 error Unsafe type assertion: type 'EffectElement[]' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/timeline/update-pipeline.ts + 144:20 error Unsafe type assertion: type 'TimelineElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/utils/browser.ts + 40:12 error Unsafe type assertion: type 'HTMLInputElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 44:12 error Unsafe type assertion: type 'HTMLTextAreaElement' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +/home/heart/coding/video-editor-oss.worktrees/biome-to-eslint/apps/web/src/wasm/media-time.ts + 29:32 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 42:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 55:10 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 57:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 71:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 90:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 100:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 144:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 166:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 176:9 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + 189:38 error Unsafe type assertion: type 'MediaTime' is more narrow than the original type @typescript-eslint/no-unsafe-type-assertion + +✖ 418 problems (390 errors, 28 warnings) + 10 errors and 0 warnings potentially fixable with the `--fix` option. + |
