Skip to content

Stability audit: Utilities/Image (Image, ImageProvider, imageHandler) #3891

Description

@eliseo-juan

Stability Audit — Utilities/Image

Components audited: Image (Utilities/Image/), ImageProvider, useImageContext (implemented in src/lib/imageHandler.tsx)


BLOCKING findings

# Severity Area File:line Description Suggested fix
B-01 BLOCKING Naming imageHandler.tsx:37 Image component is not prefixed F0 — AGENTS.md requires all public components start with F0 Rename to F0Image; rename ImageProviderF0ImageProvider, useImageContextuseF0ImageContext
B-02 BLOCKING Naming Utilities/Image/ folder Component folder is not named F0Image/ — must match component name Rename folder to F0Image/
B-03 BLOCKING TypeScript imageHandler.tsx:9,30,32 All three public types use type instead of interface (ImageContextValue, ImageProps, SrcProps) — AGENTS.md: "prefer interfaces over types for public APIs" Convert all three to interface
B-04 BLOCKING Props imageHandler.tsx:30 className is not blocked on the public Image component — AGENTS.md: "No className on public components" Omit className via Omit<ImgHTMLAttributes<…>, "className"> or the privateProps pattern
B-05 BLOCKING React imageHandler.tsx:15 React.FC<…> used but React namespace is not imported — TypeScript compile error Replace with a typed plain function: export function F0ImageProvider({ children, ...value }: …): ReactNode
B-06 BLOCKING forwardRef imageHandler.tsx:44 displayName is never explicitly set on the forwardRef component — AGENTS.md: "set displayName" Add Image.displayName = "F0Image" (post-rename) after the forwardRef call
B-07 BLOCKING Structure Utilities/Image/ Wrong folder layout: missing __tests__/, __stories__/, implementation file (F0Image.tsx), and types.ts — all required by AGENTS.md Create correct folder layout; move implementation out of lib/
B-08 BLOCKING Testing Utilities/Image/index.spec.tsx, lib/imageHandler.spec.tsx Both test files use .spec.tsx extension — AGENTS.md: "never .spec.ts" Rename both to .test.tsx
B-09 BLOCKING Testing Utilities/Image/index.spec.tsx Test file at component root, not in __tests__/ subfolder Move to F0Image/__tests__/F0Image.test.tsx
B-10 BLOCKING Exports components/exports.ts Image, ImageProvider, types, and hook are not exported from the public package surface — AGENTS.md: "Components in components/ must be exported in exports.ts" Add export * from "./Utilities/F0Image" to exports.ts
B-11 BLOCKING Exports Utilities/Image/index.tsx:1 index.tsx only re-exports Image; ImageProvider, useImageContext, and all public types are inaccessible from the public API Export all public symbols from index.tsx
B-12 BLOCKING Architecture src/lib/imageHandler.tsx Public component implementation lives in lib/ (internal utilities), not components/ — AGENTS.md: "lib/ is for internal utilities" Move implementation to components/Utilities/F0Image/F0Image.tsx; leave a @deprecated re-export shim in lib/ during migration
B-13 BLOCKING Context imageHandler.tsx:23–28 useImageContext spreads undefined context value ({ ...undefined }{}) instead of using the ?? fallback pattern documented in AGENTS.md Change to return useContext(ImageContext) ?? {}
B-14 BLOCKING Imports Utilities/Image/index.tsx:1 Deep relative import ../../../lib/imageHandler instead of @/ alias or sibling file Once implementation is collocated, use export * from "./F0Image"
B-15 BLOCKING A11y imageHandler.tsx:30 alt is string | undefined (optional in ImgHTMLAttributes) — TypeScript does not prevent omitting alt, which is a WCAG 1.1.1 (Level A) violation Change to alt: string in the public props type (empty string is still allowed for decorative images)
B-16 BLOCKING A11y ProductUpdates/index.tsx:249 <Image> rendered with no alt attribute in production code — confirmed missing alt on an informative thumbnail image Pass alt={title} to this usage; once B-15 is fixed TypeScript will enforce this
B-17 BLOCKING A11y imageHandler.tsx:30, index.stories.tsx No decorative image pattern (alt="" + optional role="presentation") is defined, documented, or story-demonstrated Add discriminated union or documented guidance; add a Decorative story
B-18 BLOCKING A11y imageHandler.tsx:32–43 SrcProps allows the transformer to return src: undefined (because it is Pick from ImgHTMLAttributes where src is optional), silently breaking the image for AT users Tighten SrcProps to { src: string; srcSet?: string; sizes?: string } and add runtime console.warn guard
B-19 BLOCKING Storybook index.stories.tsx Story file at component root, not in __stories__/ subfolder Move to F0Image/__stories__/F0Image.stories.tsx
B-20 BLOCKING Storybook index.stories.tsx (missing) No Snapshot story with withSnapshot({}) — component is invisible to Chromatic Add export const Snapshot: Story = { parameters: withSnapshot({}) }
B-21 BLOCKING Storybook index.stories.tsx:8 title: "Image" — does not match folder structure and conflicts with other image-related stories Change to title: "Utilities/Image"
B-22 BLOCKING Storybook index.stories.tsx (missing) No story demonstrates ImageProvider / src transform — the component's only distinctive feature Add a WithImageProvider story with an inline decorators showing the transform
B-23 BLOCKING Testing Utilities/Image/index.spec.tsx:1,8 render and screen imported from @testing-library/react — must use zeroRender from @/testing/test-utils Replace import
B-24 BLOCKING Testing Utilities/Image/index.spec.tsx:4 Relative import path "../../../lib/imageHandler" — must use @/lib/imageHandler alias Fix import
B-25 BLOCKING Testing lib/imageHandler.spec.tsx:1 Same render/screen from @testing-library/react violation Replace import

SUGGESTION findings

# Severity Area File:line Description Suggested fix
S-01 SUGGESTION Storybook index.stories.tsx:14 "no-sidebar" tag is inconsistent with "stable" — unique combination in the entire codebase; causes the component to be hidden from the sidebar Remove "no-sidebar" from tags
S-02 SUGGESTION Storybook index.stories.tsx (missing) No argTypesImgHTMLAttributes exposes 40+ HTML attributes, flooding the Controls panel Add argTypes describing src, alt, width, height, loading, srcSet, sizes and disabling irrelevant event handlers
S-03 SUGGESTION Storybook index.stories.tsx (missing) No stories for srcSet/sizes or loading="lazy" — both are first-class features in SrcProps Add WithSrcSet and LazyLoading stories
S-04 SUGGESTION Storybook index.stories.tsx (missing) No parameters.docs.description.component prose on the ImageProvider transform pattern Add component description in meta
S-05 SUGGESTION Storybook index.stories.tsx:4 Icon imported from @factorialco/f0-core/assets/icons/app/heart.svg raw asset path — use @/icons/app typed import instead Replace with import from @/icons/app
S-06 SUGGESTION Code imageHandler.tsx:19 ImageProvider spreads all remaining props into the context via ...value — future new props would be leaked into context without filtering Destructure explicitly: const { src } = value; <ImageContext.Provider value={{ src }}>
S-07 SUGGESTION Code imageHandler.tsx:15 React.FC is used — AGENTS.md convention is to use typed plain functions instead Convert to export function F0ImageProvider(…): ReactNode
S-08 SUGGESTION Code index.tsx:1 Selective named re-export instead of export * — new exports added to implementation must be manually synced After collocating implementation, use export * from "./F0Image"
S-09 SUGGESTION Props imageHandler.tsx:30 DataAttributes from @/global.types not included in ImageProps — other F0 components intersect it Add & DataAttributes to F0ImageProps
S-10 SUGGESTION Naming imageHandler.tsx:32 SrcProps is too generic — should be F0ImageSrcProps and live in types.ts Rename and move
S-11 SUGGESTION A11y imageHandler.tsx:30 No dev-mode console.warn when both width and height are missing (CLS risk) Add if (!props.width && !props.height) console.warn(…) in development
S-12 SUGGESTION A11y index.stories.tsx (missing) No play function with checkA11y — axe regressions will go undetected in CI Add axe-playwright play function to Basic and Decorative stories
S-13 SUGGESTION A11y index.stories.tsx (missing) No loading="lazy" story (best practice guidance for off-screen images) Add LazyLoading story
S-14 SUGGESTION Testing index.spec.tsx:7 async keyword with no await expression — dead weight Remove async
S-15 SUGGESTION Testing index.spec.tsx (whole file) Near-duplicate of lib/imageHandler.spec.tsx — barrel re-export has no own logic; component-level test adds zero incremental coverage Consolidate into one __tests__/F0Image.test.tsx; delete the duplicate
S-16 SUGGESTION Testing imageHandler.tsx:37 forwardRef ref forwarding is never tested — regression would go undetected Add const ref = createRef<HTMLImageElement>(); render(<F0Image ref={ref} src="x.png" alt="x" />); expect(ref.current?.tagName).toBe("IMG")
S-17 SUGGESTION Testing lib/imageHandler.spec.tsx Not inside a __tests__/ folder — lib/ test convention is inconsistent; linkHandler.spec.tsx has the same issue Establish lib/__tests__/ convention and migrate all lib tests together

Reviewer sections

Code Review

  • B-01 to B-06 (naming, type vs interface, React.FC, displayName, className)
  • B-07, B-12 (folder structure, implementation in wrong location)
  • B-11, B-14 (incomplete exports, relative import path)
  • B-13 (context spread pattern)
  • B-18 (SrcProps allows undefined src)
  • S-06 to S-10

Accessibility

  • B-15 (alt not required at type level)
  • B-16 (production usage missing alt)
  • B-17 (no decorative image pattern)
  • B-18 (undefined src from transformer)
  • S-11 (width/height CLS warning)
  • S-12, S-13 (missing axe play function, lazy loading story)

Storybook

  • B-19 (stories not in stories/)
  • B-20 (missing Snapshot story)
  • B-21 (wrong title)
  • B-22 (no ImageProvider story)
  • S-01 to S-05 (no-sidebar tag, argTypes, srcSet story, docs description, icon import)

Test Coverage

  • B-08, B-09 (spec extension, wrong folder)
  • B-23 to B-25 (wrong render/screen/import in test files)
  • S-14 to S-17 (async without await, duplicate test, forwardRef untested, lib tests location)

Ready-to-run fix prompt

You are fixing the Utilities/Image component group in packages/react/src/components/Utilities/Image/ and packages/react/src/lib/imageHandler.tsx.
Read packages/react/AGENTS.md first.

Fix ALL of the following in order:

1. Rename `Image` → `F0Image`, `ImageProvider` → `F0ImageProvider`, `useImageContext` → `useF0ImageContext`, `ImageContextValue` → `F0ImageContextValue`, `ImageProps` → `F0ImageProps`, `SrcProps` → `F0ImageSrcProps` everywhere.
2. Rename folder `Utilities/Image/` → `Utilities/F0Image/`.
3. Convert all three `type` declarations to `interface` in imageHandler.tsx.
4. Block `className` in the public `F0ImageProps` interface via Omit.
5. Replace `React.FC<…>` with a typed plain function for `F0ImageProvider`.
6. Add `F0Image.displayName = "F0Image"` after the forwardRef call.
7. Move the implementation from `src/lib/imageHandler.tsx` into `src/components/Utilities/F0Image/F0Image.tsx` and `types.ts`; leave a `@deprecated` re-export shim in `lib/imageHandler.tsx`.
8. Create the full correct folder layout: `F0Image/__tests__/`, `F0Image/__stories__/`, `F0Image/F0Image.tsx`, `F0Image/types.ts`, `F0Image/index.tsx`.
9. Update `index.tsx` to `export * from "./F0Image"` and export all public types.
10. Add export to `src/components/exports.ts`.
11. Fix the context hook to use `return useContext(F0ImageContext) ?? {}`.
12. Fix `SrcProps` to `{ src: string; srcSet?: string; sizes?: string }` (non-optional src).
13. Add runtime `console.warn` guard when transformer returns falsy `src`.
14. Enforce `alt: string` (not `string | undefined`) in `F0ImageProps`.
15. Fix the `<Image>` in `ProductUpdates/index.tsx` to pass `alt={title}`.
16. Move story to `F0Image/__stories__/F0Image.stories.tsx`; update `title: "Utilities/Image"`.
17. Add `Snapshot`, `WithImageProvider`, `Decorative`, `WithSrcSet`, and `LazyLoading` stories.
18. Remove `"no-sidebar"` tag; add `parameters.docs.description.component` prose.
19. Add `argTypes` for key props; replace the icon import with `@/icons/app`.
20. Rename `Utilities/Image/index.spec.tsx` → `F0Image/__tests__/F0Image.test.tsx` and fix all imports (`zeroRender`, `@/testing/test-utils`, `@/lib/imageHandler`).
21. Rename `lib/imageHandler.spec.tsx` → (consolidate into F0Image/__tests__/ and delete duplicate).
22. Remove `async` from the synchronous test.
23. Add `forwardRef` ref-forwarding test.
24. Add `ImageProvider` spread-explicit destructuring fix.
25. Add `DataAttributes` to `F0ImageProps`.
26. Explicitly destructure `src` in `F0ImageProvider` body to avoid leaking future props into context.

After all fixes: run `pnpm tsc --noEmit` and `pnpm test` from packages/react/ and ensure zero errors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions