> ## Documentation Index
> Fetch the complete documentation index at: https://docs.napps.io/llms.txt
> Use this file to discover all available pages before exploring further.

# @napps/component-extension contracts

These are the callback contracts used by native app extensions. Export the function or handler
required by the selected extension page; the type definitions below describe the values passed to
it and the mutations it may return.

Async callbacks share the host's extension timeout. Keep work bounded and return within the
documented three-second budget. A timed-out or rejected promise is treated as a failed callback and
native behavior continues according to the extension contract.

## Product listing formatter

```ts theme={null}
type ProductListingBaseDataEditor = {
  readonly product: Product;
  setVisible(isVisible: boolean): void;
  isVisible(): boolean;
  setVendor(vendor?: string): void;
  setStampImage(url?: string): void;
  getTags(): ProductTag[];
  replaceTags(input: ProductTag[]): void;
  addTag(input: ProductTag): void;
  removeTag(input: ProductTag): void;
  removeTagByType(type: string): void;
  clearTags(): void;
};

export function formatProducts(
  products: ProductListingBaseDataEditor[]
): void | Promise<void>;
```

The editor is mutable for the duration of the callback. Make formatting idempotent because the
same listing can be formatted more than once.

## Collection filters

```ts theme={null}
interface CollectionProductsFiltersContext {
  readonly collection: CollectionProductsCollectionInfo;
  readonly trigger: CollectionProductsFiltersTrigger;
  readonly filters: CollectionProductsFiltersEditor;
}

interface CollectionProductsFiltersEditor {
  readonly available: AvailableCollectionProductsFilters;
  hideFilterById(filterId: string): boolean;
  hideFilterByLabel(filterLabel: string): boolean;
  showFilterById(filterId: string): boolean;
  showFilterByLabel(filterLabel: string): boolean;
  isFilterHiddenById(filterId: string): boolean;
  isFilterHiddenByLabel(filterLabel: string): boolean;
  renameFilterById(filterId: string, title: string): boolean;
  renameFilterByLabel(filterLabel: string, title: string): boolean;
  clearFilterRenameById(filterId: string): boolean;
  clearFilterRenameByLabel(filterLabel: string): boolean;
  renameValueById(filterId: string, value: string, label: string): boolean;
  renameValueByLabel(filterLabel: string, value: string, label: string): boolean;
  addValueById(filterId: string, value: string): boolean;
  addValueByLabel(filterLabel: string, value: string): boolean;
  removeValueById(filterId: string, value: string): boolean;
  removeValueByLabel(filterLabel: string, value: string): boolean;
  clearById(filterId: string): boolean;
  clearByLabel(filterLabel: string): boolean;
  hasValueById(filterId: string, value: string): boolean;
  hasValueByLabel(filterLabel: string, value: string): boolean;
  setPriceRange(min: number, max: number): void;
  clearPriceRange(): void;
  setOrder(order: FilterOrder): boolean;
  clearOrder(): boolean;
}

export function prepareCollectionProductsFilters(
  context: CollectionProductsFiltersContext
): void | Promise<void>;
```

`trigger` is either `"Initial"` or `"UserFiltersChanged"`. The callback can change the editor
before the product query runs. Boolean mutators return `false` when the requested filter or value
does not exist.

## Product detail handler

```ts theme={null}
interface ProductDetailHandler {
  onFormat?(draft: ProductDetailInfoState): void | Promise<void>;
  onDestroyed?(): void | Promise<void>;
  onVariantSelected?(): void;
  onVariantsAvailability?(
    event: ProductDetailEventArgs<VariantsAvailabilityEventArgs>
  ): void | Promise<void>;
  onBeforeProductAddToCart?(
    event: ProductDetailEventArgs<OnBeforeAddToCartEventArgs>
  ): boolean | Promise<boolean>;
  onProductSelected?(
    event: ProductDetailEventArgs<ProductVariantEventArgs>
  ): boolean | Promise<boolean>;
  onProductClicked?(
    event: ProductDetailEventArgs<ProductEventArgs>
  ): boolean | Promise<boolean>;
  onProductQuickAddClicked?(
    event: ProductDetailEventArgs<ProductEventArgs>
  ): boolean | Promise<boolean>;
  onAddedToCart?(
    event: ProductDetailEventArgs<ProductVariantEventArgs>
  ): boolean | Promise<boolean>;
  onExternalNavigationRequestClicked?(
    event: ProductDetailEventArgs<ExternalNavigationRequestEventArgs>
  ): boolean | Promise<boolean>;
  onCollectionClicked?(
    event: ProductDetailEventArgs<CollectionClickedEventArgs>
  ): boolean | Promise<boolean>;
  onVendorClicked?(
    event: ProductDetailEventArgs<VendorClickedEventArgs>
  ): boolean | Promise<boolean>;
}

interface ProductDetailEventArgs<T> {
  data: T;
}
```

Callbacks that support cancellation suppress the native action when they return `false`. There is
no `preventDefault()` method on the event object. `onAddedToCart` and `onProductSelected` receive
`{ product, productVariant }`; `onBeforeProductAddToCart` is the callback whose payload uses
`{ product, variant, quantity }`.

### PDP formatting and media tags

```ts theme={null}
interface ProductDetailInfoState {
  readonly mediaItems: ProductMediaItem[];
  setTitle(title: string): void;
  setVendor(vendor?: string): void;
  setSku(sku?: string): void;
  setStampImage(url?: string): void;
  clearLowStockInfo(): void;
  getTags(): ProductTag[];
  addTag(input: ProductTag): void;
  removeTag(input: ProductTag): void;
  removeTagByType(type: string): void;
  replaceTags(input: ProductTag[]): void;
  clearTags(): void;
  getMediaTags(mediaID: string): MediaTag[];
  addMediaTag(mediaID: string, tag: MediaTag): void;
  setMediaTags(mediaID: string, tags: MediaTag[]): void;
  removeMediaTag(mediaID: string, tagID: string): void;
  clearMediaTags(mediaID: string): void;
  clearAllMediaTags(): void;
}

interface ProductMediaItem {
  id: string;
  alt: string;
  contentType: string;
  previewImageUrl?: string | null;
  type: string;
}

interface MediaTag {
  id: string;
  text?: string | null;
  textColor?: string | null;
  backgroundColor?: string | null;
  iconUrl?: string | null;
  iconColor?: string | null;
}
```

Media is native-owned: handlers can inspect the gallery and manage tags, but cannot reorder or add
media. A tag with the same ID on one media item is replaced. A tag must provide text, an icon, or
both.

## Cart operations

```ts theme={null}
interface CartOperations {
  addLineItem(
    merchandiseID: string,
    attributes: Record<string, string>,
    quantity: number,
    sellingPlanID?: string,
    parentLineItemID?: string,
    parentMerchandiseID?: string
  ): CartOperation;
  removeLineItem(lineItemID: string): CartOperation;
  updateLineItem(
    lineItemID: string,
    attributes?: Record<string, string>,
    merchandiseID?: string,
    quantity?: number,
    sellingPlanID?: string
  ): CartOperation;
  updateCartAttributes(attributes: Record<string, string>): CartOperation;
  setMetaField(key: string, type: string, value: string): CartOperation;
  deleteMetaField(key: string): CartOperation;
  updateNotes(notes: string): CartOperation;
}

interface CartOperation {}
```

Use the returned operations from the cart extension. Operations are applied by the native runner;
do not mutate a `CartOperation` after returning it.

## Cart line-item formatter

```ts theme={null}
interface CartLineItemsFormatterContext {
  readonly checkout: Cart;
  readonly lineItems: CartLineItemDataEditor[];
  addInfoBanner(banner: CartInfoBanner): void;
  removeInfoBanner(id: string): void;
  clearInfoBanners(): void;
  setCheckoutEnabled(enabled: boolean): void;
}
```

```ts theme={null}
interface CartLineItemDataEditor {
  readonly id: string;
  readonly quantity: number;
  readonly productID: string;
  readonly product?: Product | null;
  readonly variantID: string;
  readonly variant?: ProductVariant | null;
  readonly attributes: Record<string, string>;
  readonly properties: Record<string, string>;
  readonly totalAmount: number;
  readonly subtotalAmount: number;
  setName(name: string): void;
  setImageUrl(url?: string): void;
  setQuantityEditable(editable: boolean): void;
  addExtraInfo(info: CartLineItemExtraInfo): void;
  replaceExtraInfos(infos: CartLineItemExtraInfo[]): void;
  clearExtraInfos(): void;
}
```

The formatter may change presentation and checkout availability. The line item’s product and
variant references can be `null` when the underlying catalog object no longer exists.

## Product-card bridge

Product-card modules receive their products through `ComponentContext.cards`.

```ts theme={null}
interface ProductCardsBridge {
  readonly surface: string;
  subscribe(callback: (payload: ProductCardsPayload) => void): void;
}

type ProductCardsPayload = {
  op?: "append" | "reset" | "slide";
  dropLeading?: number;
  products: ProductCardItem[];
  loadingCount: number;
};

interface ProductCardItem {
  id: string;
  title: string;
  vendor: string;
  imageUrl: string;
  formattedPrice: string;
  formattedCompareAtPrice: string;
  hasDiscount: boolean;
  discountPercentage: number;
  isAvailable: boolean;
  isWishListed: boolean;
  stampImageUrl: string;
}
```

The bridge invokes the subscriber immediately and keeps only the latest subscriber. Render one
root child per slot, with real products first and loading placeholders last.
