Skip to content

Inspector image crop writes shared component styles to all instances #213

@whjstc

Description

@whjstc

Summary

When cropping an image through the Inspector, Open Slide writes objectFit, objectPosition, and objectViewBox back to the JSX source location for the selected <img>. If that image is rendered from a reused component, the source location is the shared component implementation, so cropping one rendered instance changes every instance in the deck.

This is easy to trigger with common slide code that wraps screenshots in a reusable component.

Environment

  • Package: @open-slide/core
  • Version observed: 1.10.0

Minimal reproduction

const ScreenshotFrame = ({ src, alt }: { src: string; alt: string }) => (
  <div style={{ width: 700, height: 500, overflow: "hidden" }}>
    <img
      src={src}
      alt={alt}
      style={{ width: "100%", height: "100%", objectFit: "contain" }}
    />
  </div>
);

const PageA: Page = () => <ScreenshotFrame src={imageA} alt="A" />;
const PageB: Page = () => <ScreenshotFrame src={imageB} alt="B" />;

Steps:

  1. Open PageA in the editor.
  2. Use Inspector -> Crop on the rendered image.
  3. Apply and save the crop.
  4. Open PageB.

Actual behavior

The crop styles are written to the <img> inside ScreenshotFrame, so both PageA and PageB are cropped. In a larger deck this can silently alter many pages at once.

Expected behavior

Cropping a selected image should not silently change other rendered image instances. Ideally it should affect only the selected rendered instance. If Open Slide cannot safely rewrite only that instance, it should warn or block the operation.

Why this happens

The Inspector style writeback appears to be keyed by source line:column. Reused components render multiple DOM nodes that share the same source location, so a style operation on one instance is applied to the shared JSX implementation.

The editor already has some instance-aware handling for text edits via per-instance ids, but crop/style operations are still source-location based.

Suggested fix

A conservative fix would be:

  1. Before applying crop style ops, check whether the selected image's data-slide-loc appears on multiple rendered DOM nodes in the current deck/page scope.
  2. If multiple rendered nodes share that source location, warn the user that this crop will affect all instances.
  3. Default to blocking the save unless the user explicitly chooses an "apply to all instances" action.

A fuller fix could support instance-level rewrites, but arbitrary React components may not expose a safe style / imageStyle prop path. Warning/blocking would already prevent accidental deck-wide damage.

Workaround

Inline each crop-target image directly in the page JSX instead of rendering it from a reused component. That gives each image a unique source location, so crop writeback only affects the intended image.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions