ronantakizawa/codereview-bench huggingface dataset:
Repository: moeru-ai/airi
File: packages/stage-ui/src/composables/use-optimistic.ts (TypeScript)

Code before:
 * Integrates with useAsyncState for loading/error tracking.
 */
export function useOptimistic<T, R = T>(options: UseOptimisticOptions<T, R>) {
  const { apply, action, onSuccess, onError } = options

  return useAsyncState(async () => {
    const rollback = await apply()

    try {
      const result = await action()
      if (onSuccess) {
        return await onSuccess(result)
      }
      return result as unknown as R
    }
    catch (err) {
      if (typeof rollback === 'function') {
        await rollback()
      }
      if (onError) {
        await onError(err)
      }
      throw err
    }
  }, { immediate: true })
}


Review comment (suggestion):
```suggestion
export interface UseOptimisticOptions<T, R, E = unknown> {
  /**
   * The optimistic update logic.
   * Should return a rollback function.
   */
  apply: () => Promise<(() => Promise<void> | void)> | (() => Promise<void> | void)
  /**
   * The actual async task (e.g., API call).
   */
  action: () => Promise<T>
  /**
   * Optional callback after successful action to refine state (e.g., replacing temp IDs).
   */
  onSuccess?: (result: T) => Promise<R> | R
  /**
   * Optional callback on error. Rollback is handled automatically.
   */
  onError?: (error?: E | null) => void | Promise<void>
}

/**
 * A wrapper for performing optimistic updates with automatic rollback.
 * Integrates with useAsyncState for loading/error tracking.
 */
export function useOptimistic<T, R = T, E = unknown>(options: UseOptimisticOptions<T, R>) {
  const { apply, action, onSuccess, onError } = options

  return useAsyncState(async () => {
    const rollback = await apply()

    try {
      const result = await action()
      if (onSuccess) {
        return await onSuccess(result)
      }
      return result as unknown as R
    }
    catch (err: E) {
      if (typeof rollback === 'function') {
        await rollback()
      }
      if (onError) {
        await onError(err)
      }
      throw err
    }
  }, { immediate: true })
}
```

Code after:
  lazy?: boolean
}

/**
 * A wrapper for performing optimistic mutations with automatic rollback.
 * Integrates with useAsyncState for loading/error tracking.
 * TODO: use https://pinia-colada.esm.dev/guide/mutations.html instead.
 */
export function useOptimisticMutation<T, R = T, E = unknown>(options: UseOptimisticMutationOptions<T, R, E>) {
  const { apply, action, onSuccess, onError, lazy = false } = options

  return useAsyncState(async () => {
    const rollback = await apply()

    try {
      const result = await action()
      if (onSuccess) {
        return await onSuccess(result)
      }
      return result as unknown as R
    }
    catch (err) {
      if (typeof rollback === 'function') {
        await rollback()
      }
      if (onError) {
        await onError(err as E)
      }
      throw err
    }
  }, { immediate: !lazy })
}
Repository: moeru-ai/airi
File: packages/stage-ui/src/composables/use-optimistic.ts (TypeScript)

Code before:

export interface UseOptimisticOptions<T, R, E = unknown> {
  /**
   * The optimistic update logic.
   * Should return a rollback function.
   */
  apply: () => Promise<(() => Promise<void> | void)> | (() => Promise<void> | void)
  /**
   * The actual async task (e.g., API call).
   */
  action: () => Promise<T>
  /**
   * Optional callback after successful action to refine state (e.g., replacing temp IDs).
   */
  onSuccess?: (result: T) => Promise<R> | R
  /**
   * Optional callback on error. Rollback is handled automatically.
   */
  onError?: (error?: E | null) => void | Promise<void>
}

/**
 * A wrapper for performing optimistic updates with automatic rollback.
 * Integrates with useAsyncState for loading/error tracking.
 */
export function useOptimistic<T, R = T, E = unknown>(options: UseOptimisticOptions<T, R>) {
  const { apply, action, onSuccess, onError } = options

  return useAsyncState(async () => {
    const rollback = await apply()

    try {
      const result = await action()
      if (onSuccess) {
        return await onSuccess(result)
      }
      return result as unknown as R
    }
    catch (err: E) {
      if (typeof rollback === 'function') {
        await rollback()
      }
      if (onError) {
        await onError(err)
      }
      throw err
    }
  }, { immediate: true })
}


Review comment (suggestion):
```suggestion
export function useOptimistic<T, R = T, E = unknown>(options: UseOptimisticOptions<T, R, E>) {
```

Code after:

export interface UseOptimisticMutationOptions<T, R, E = unknown> {
  /**
   * The optimistic update logic.
   * Should return a rollback function.
   */
  apply: () => Promise<(() => Promise<void> | void)> | (() => Promise<void> | void)
  /**
   * The actual async task (e.g., API call).
   */
  action: () => Promise<T>
  /**
   * Optional callback after successful action to refine state (e.g., replacing temp IDs).
   */
  onSuccess?: (result: T) => Promise<R> | R
  /**
   * Optional callback on error. Rollback is handled automatically.
   */
  onError?: (error?: E | null) => void | Promise<void>

  /**
   * Whether to execute the action lazily.
   */
  lazy?: boolean
}

/**
 * A wrapper for performing optimistic mutations with automatic rollback.
 * Integrates with useAsyncState for loading/error tracking.
 * TODO: use https://pinia-colada.esm.dev/guide/mutations.html instead.
 */
export function useOptimisticMutation<T, R = T, E = unknown>(options: UseOptimisticMutationOptions<T, R, E>) {
  const { apply, action, onSuccess, onError, lazy = false } = options

  return useAsyncState(async () => {
    const rollback = await apply()

    try {
      const result = await action()
      if (onSuccess) {
        return await onSuccess(result)
      }
      return result as unknown as R
    }
    catch (err) {
      if (typeof rollback === 'function') {
        await rollback()
      }
      if (onError) {
        await onError(err as E)
      }
Repository: motion-canvas/motion-canvas
File: packages/create/index.js (JavaScript)

Code before:
#!/usr/bin/env node
//@ts-check
import prompts from 'prompts';
import fs from 'fs';
import path from 'path';
import {fileURLToPath} from 'node:url';

const FILES_TO_MODIFY = {
  gitignore: '.gitignore',
  '.gitkeep': false,
};

const MANIFEST = JSON.parse(
  fs.readFileSync(
    path.resolve(fileURLToPath(import.meta.url), '../package.json'),
    'utf-8',
  ),
);

(async () => {
  const response = await prompts([
    {
      type: 'text',
      name: 'path',
      message: 'Project path (Leave empty to use the current directory)',
      validate: value => {
        let dir = value.trim() || '.';
        if (!fs.existsSync(dir)) {
          return true;
        }
        if (!fs.lstatSync(dir).isDirectory()) {
          return `Project path "${dir}" must be a valid directory.`;
        }
        if (fs.readdirSync(dir).length > 0) {
          return dir === '.'
            ? 'Current directory must be empty.'
            : `Target directory "${dir}" must be empty.`;
        }
        return true;
      },
      format: value => path.resolve(value),
    },
    {
      type: 'text',
      name: 'name',
      message: 'Project name',
      initial: value => {
        const base = path.parse(value).base;
        return isValidPackageName(base) ? base : 'my-project';
      },

Review comment (suggestion):
I believe if you set the `initial` value it will pop it in parenthesis, like 
```bash
Project path (c:/repos/my-project/)
```
That would probably be more standard than telling them to leave the entry empty to get the current directory.

Code after:
#!/usr/bin/env node
//@ts-check
import prompts from 'prompts';
import fs from 'fs';
import path from 'path';
import {fileURLToPath} from 'node:url';
import kleur from 'kleur';

const FILES_TO_MODIFY = {
  gitignore: '.gitignore',
  '.gitkeep': false,
};

const MANIFEST = JSON.parse(
  fs.readFileSync(
    path.resolve(fileURLToPath(import.meta.url), '../package.json'),
    'utf-8',
  ),
);

(async () => {
  const response = await prompts([
    {
      type: 'text',
      name: 'name',
      message: 'Project name',
      initial: 'my-animation',
      validate: value =>
        isValidPackageName(value)
          ? true
          : 'Project name must be a valid npm package name.',
    },
    {
      type: 'text',
      name: 'path',
      message: 'Project path',

      initial: value => {
        return path.normalize(value.replace('@', ''));
      },

      validate: value => {
        let dir = path.normalize(value.trim());
        if (!fs.existsSync(dir)) {
          return true;
        }
        if (!fs.lstatSync(dir).isDirectory()) {
          return `Project path "${dir}" must be a valid directory.`;
        }
        if (fs.readdirSync(dir).length > 0) {
Repository: motion-canvas/motion-canvas
File: packages/create/index.js (JavaScript)

Code before:
          return true;
        }
        if (!fs.lstatSync(dir).isDirectory()) {
          return `Project path "${dir}" must be a valid directory.`;
        }
        if (fs.readdirSync(dir).length > 0) {
          return dir === '.'
            ? 'Current directory must be empty.'
            : `Target directory "${dir}" must be empty.`;
        }
        return true;
      },
      format: value => path.resolve(value),
    },
    {
      type: 'text',
      name: 'name',
      message: 'Project name',
      initial: value => {
        const base = path.parse(value).base;
        return isValidPackageName(base) ? base : 'my-project';
      },
      validate: value =>
        isValidPackageName(value)
          ? true
          : 'Project name must be a valid package name.',
    },
    {
      type: 'select',
      name: 'language',
      message: 'Language',
      choices: [
        {
          title: 'TypeScript (Recommended)',
          value: 'ts',
        },
        {
          title: 'JavaScript',
          value: 'js',
        },
      ],
    },
  ]);

  if (!response.language) {
    console.log('Scaffolding aborted.');
    return;
  }

  const templateDir = path.resolve(
    fileURLToPath(import.meta.url),

Review comment (suggestion):
"Project name must be a valid **npm** package name"

Optionally you could bring in `validate-npm-package-name` which has custom error messages, but _that_ feels like overkill.

Code after:
        isValidPackageName(value)
          ? true
          : 'Project name must be a valid npm package name.',
    },
    {
      type: 'text',
      name: 'path',
      message: 'Project path',

      initial: value => {
        return path.normalize(value.replace('@', ''));
      },

      validate: value => {
        let dir = path.normalize(value.trim());
        if (!fs.existsSync(dir)) {
          return true;
        }
        if (!fs.lstatSync(dir).isDirectory()) {
          return `Project path "${dir}" must be a valid directory.`;
        }
        if (fs.readdirSync(dir).length > 0) {
          return dir === '.'
            ? 'Current directory must be empty.'
            : `Target directory "${dir}" must be empty.`;
        }
        return true;
      },
      format: value => path.resolve(value),
    },
    {
      type: 'select',
      name: 'language',
      message: 'Language',
      choices: [
        {
          title: 'TypeScript (Recommended)',
          value: 'ts',
        },
        {
          title: 'JavaScript',
          value: 'js',
        },
      ],
    },
  ]);

  if (!response.language) {
    console.log(kleur.red('× Scaffolding aborted by the user.\n'));
    return;
  }
Repository: motion-canvas/motion-canvas
File: packages/create/index.js (JavaScript)

Code before:
#!/usr/bin/env node
//@ts-check
import prompts from 'prompts';
import fs from 'fs';
import path from 'path';
import {fileURLToPath} from 'node:url';

const FILES_TO_MODIFY = {
  gitignore: '.gitignore',
  '.gitkeep': false,
};

const MANIFEST = JSON.parse(
  fs.readFileSync(
    path.resolve(fileURLToPath(import.meta.url), '../package.json'),
    'utf-8',
  ),
);

(async () => {
  const response = await prompts([
    {
      type: 'text',
      name: 'path',
      message: 'Project path (Leave empty to use the current directory)',
      validate: value => {
        let dir = value.trim() || '.';
        if (!fs.existsSync(dir)) {
          return true;
        }
        if (!fs.lstatSync(dir).isDirectory()) {
          return `Project path "${dir}" must be a valid directory.`;
        }
        if (fs.readdirSync(dir).length > 0) {
          return dir === '.'
            ? 'Current directory must be empty.'
            : `Target directory "${dir}" must be empty.`;
        }
        return true;
      },
      format: value => path.resolve(value),
    },
    {
      type: 'text',
      name: 'name',
      message: 'Project name',

Review comment (suggestion):
You could consider inverting the order of `Project name` and `Project path`. So the user types in a name first and then you set the initial path to being the name concatenated to the current directory. I think that's easier than typing in a path and getting the name by default. Also, the current system works best by creating a directory, moving to that directory, and then starting the scaffolding, but we can save people those steps. 

Code after:
#!/usr/bin/env node
//@ts-check
import prompts from 'prompts';
import fs from 'fs';
import path from 'path';
import {fileURLToPath} from 'node:url';
import kleur from 'kleur';

const FILES_TO_MODIFY = {
  gitignore: '.gitignore',
  '.gitkeep': false,
};

const MANIFEST = JSON.parse(
  fs.readFileSync(
    path.resolve(fileURLToPath(import.meta.url), '../package.json'),
    'utf-8',
  ),
);

(async () => {
  const response = await prompts([
    {
      type: 'text',
      name: 'name',
      message: 'Project name',
      initial: 'my-animation',
      validate: value =>
        isValidPackageName(value)
          ? true
          : 'Project name must be a valid npm package name.',
    },
    {
      type: 'text',
      name: 'path',
      message: 'Project path',

      initial: value => {
        return path.normalize(value.replace('@', ''));
      },

      validate: value => {
        let dir = path.normalize(value.trim());
        if (!fs.existsSync(dir)) {
          return true;
        }
Repository: motion-canvas/motion-canvas
File: packages/2d/src/components/Image.ts (TypeScript)

Code before:
    context.drawImage(image, width / -2, height / -2, width, height);
    super.draw(context);
  }

  protected override applyLayoutChanges() {
    this.applySrc();
  }

  @computed()
  protected applySrc() {
    const src = this.src();
    const image = <HTMLImageElement>this.layout.element;
    image.src = src;
    return image;
  }

  protected override collectAsyncResources(deps: Promise<any>[]) {
    super.collectAsyncResources(deps);
    const image = this.applySrc();
    if (!image.complete) {
      deps.push(
        new Promise(resolve => {
          image.addEventListener('load', () => resolve(this));
        }),
      );
    }
  }
}


Review comment (bug):
There should be an error event on there as well, in case they spell the image name wrong or something.

Code after:
    context.drawImage(image, width / -2, height / -2, width, height);
    super.draw(context);
  }

  protected override applyLayoutChanges() {
    this.applySrc();
  }

  @computed()
  protected applySrc() {
    const src = this.src();
    const image = <HTMLImageElement>this.layout.element;
    image.src = src;
    return image;
  }

  protected override collectAsyncResources(deps: Promise<any>[]) {
    super.collectAsyncResources(deps);
    const image = this.applySrc();
    if (!image.complete) {
      deps.push(
        new Promise((resolve, reject) => {
          image.addEventListener('load', resolve);
          image.addEventListener('error', reject);
        }),
      );
    }
  }
}
Repository: motion-canvas/motion-canvas
File: packages/2d/src/components/Latex.ts (TypeScript)

Code before:
      return Latex.latexPool[src];
    }

    // Convert to TeX, look for any errors
    const tex = this.tex();
    const svg = adaptor.innerHTML(jaxDocument.convert(tex, this.renderProps()));
    if (svg.includes('data-mjx-error')) {
      const errors = svg.match(/data-mjx-error="(.*?)"/);
      if (errors && errors.length > 0) {
        throw new Error(`Invalid MathJax: ${errors[1]}`);
      }
    }

    // Encode to raw base64 image format
    const text = `data:image/svg+xml;base64,${btoa(
      `<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n${svg}`,
    )}`;
    const image = document.createElement('img');

    image.src = text;
    if (!image.complete) {
      DependencyContext.collectPromise(
        new Promise((resolve, reject) => {
          image.addEventListener('load', resolve);
          image.addEventListener('error', reject);
        }),
      );
    }

    Latex.latexPool[svg] = image;
    return image;
  }
}


Review comment (suggestion):
I don't think you need this whole async setup. Since we're setting the `src` to a base64 string, the image should be ready synchronously. I think we could simply create one image per Latex node and keep reusing it:

```ts
protected imageElement = document.createElement('img');

protected override image(): HTMLImageElement {
  // generate the source
  this.imageElement.src = text;
  return this.imageElement;
}
  ```

Code after:
    const src = `${this.tex()}::${JSON.stringify(this.options())}`;
    if (Latex.svgContentsPool[src]) {
      this.imageElement.src = Latex.svgContentsPool[src];
      return this.imageElement;
    }

    // Convert to TeX, look for any errors
    const tex = this.tex();
    const svg = adaptor.innerHTML(jaxDocument.convert(tex, this.options()));
    if (svg.includes('data-mjx-error')) {
      const errors = svg.match(/data-mjx-error="(.*?)"/);
      if (errors && errors.length > 0) {
        useLogger().error(`Invalid MathJax: ${errors[1]}`);
      }
    }

    // Encode to raw base64 image format
    const text = `data:image/svg+xml;base64,${btoa(
      `<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n${svg}`,
    )}`;
    Latex.svgContentsPool[src] = text;
    const image = document.createElement('img');
    image.src = text;
    image.src = text;
    if (!image.complete) {
      DependencyContext.collectPromise(
        new Promise((resolve, reject) => {
          image.addEventListener('load', resolve);
          image.addEventListener('error', reject);
        }),
      );
    }

    return image;
  }
}
Repository: motion-canvas/motion-canvas
File: packages/examples/src/scenes/tex.tsx (TypeScript)

Code before:
import {Latex} from '@motion-canvas/2d/lib/components';
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
import {waitFor} from '@motion-canvas/core/lib/flow';
import {createRef} from '@motion-canvas/core/lib/utils';

export default makeScene2D(function* (view) {
  const tex = createRef<Latex>();
  view.add(
    <Latex
      // highlight-start
      ref={tex}
      tex="{\color{white} x = \sin \left( \frac{\pi}{2} \right)}"
      y={200}
      width={400} // height and width can calculate based on each other
    />,
  );

  yield* waitFor(2);
  yield* tex().opacity(0, 1);
});


Review comment (suggestion):
You need to also add a project file for this and add it to `vite.config.ts`
You can check if the examples work by compiling them: `npm run examples:build`
They should load in the docs as expected.

Code after:
import {Latex} from '@motion-canvas/2d/lib/components';
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
import {waitFor} from '@motion-canvas/core/lib/flow';
import {createRef} from '@motion-canvas/core/lib/utils';

export default makeScene2D(function* (view) {
  const tex = createRef<Latex>();
  view.add(
    <Latex
      ref={tex}
      tex="{\color{white} x = \sin \left( \frac{\pi}{2} \right)}"
      y={0}
      width={400} // height and width can calculate based on each other
    />,
  );

  yield* waitFor(2);
  yield* tex().opacity(0, 1);
  yield* waitFor(2);
  yield* tex().opacity(1, 1);
});
Repository: motion-canvas/motion-canvas
File: packages/core/src/scenes/GeneratorScene.ts (TypeScript)

Code before:
        stack: promises[0].stack,
        inspect: promises[0].owner?.key ?? undefined,
      });
    }

    if (result.done) {
      this.state = SceneState.Finished;
    }
  }

  public async reset(previousScene: Scene | null = null) {
    this.counters = {};
    this.previousScene = previousScene;
    this.random = new Random(this.meta.getData().seed!);
    this.runner = threads(
      () => this.runnerFactory(this.getView()),
      thread => {
        this.thread.current = thread;
      },
    );
    if (this.cache.current.transitionDuration === 0) {
      this.state = SceneState.AfterTransitionIn;
    } else {
      this.state = SceneState.Initial;
    }
    this.variables.destroySignals();
    this.afterReset.dispatch();
    await this.next();
  }

  public getSize(): Vector2 {
    return this.project.getSize();
  }

  public isAfterTransitionIn(): boolean {
    return this.state === SceneState.AfterTransitionIn;
  }

  public canTransitionOut(): boolean {
    return (
      this.state === SceneState.CanTransitionOut ||
      this.state === SceneState.Finished
    );
  }

  public isFinished(): boolean {
    return this.state === SceneState.Finished;
  }

  public enterAfterTransitionIn() {
    if (this.state === SceneState.Initial) {

Review comment (suggestion):
Similarly to `TimeEvents`, `Variables` should subscribe to scene events themselves.
In the constructor of `Varialbes` you should do:
```ts
scene.onReset.subscribe(this.handleReset);
```
`GeneratorScene` is only the default implementation of the `Scene` interface. There will be more in the future. Doing registration in the `Variables` class makes it easier to reuse them in different scene implementations.

Code after:
        stack: promises[0].stack,
        inspect: promises[0].owner?.key ?? undefined,
      });
    }

    if (result.done) {
      this.state = SceneState.Finished;
    }
  }

  public async reset(previousScene: Scene | null = null) {
    this.counters = {};
    this.previousScene = previousScene;
    this.random = new Random(this.meta.getData().seed!);
    this.runner = threads(
      () => this.runnerFactory(this.getView()),
      thread => {
        this.thread.current = thread;
      },
    );
    if (this.cache.current.transitionDuration === 0) {
      this.state = SceneState.AfterTransitionIn;
    } else {
      this.state = SceneState.Initial;
    }
    this.afterReset.dispatch();
    await this.next();
  }

  public getSize(): Vector2 {
    return this.project.getSize();
  }

  public isAfterTransitionIn(): boolean {
    return this.state === SceneState.AfterTransitionIn;
  }

  public canTransitionOut(): boolean {
    return (
      this.state === SceneState.CanTransitionOut ||
      this.state === SceneState.Finished
    );
  }

  public isFinished(): boolean {
    return this.state === SceneState.Finished;
  }

  public enterAfterTransitionIn() {
    if (this.state === SceneState.Initial) {
      this.state = SceneState.AfterTransitionIn;