# Recipes, contracts, and implementation tests

> Declare what a custom component does, preserve exact Kismet data provenance, and run the SDK's behavioral tests locally and in CI.




Recipes let a custom site keep its own routes, components, JSX, and CSS while
proving that the finished experience still works. The declaration belongs on
the component subtree that implements the behavior. It is not inferred from a
page filename, so a recipe may live in a page, a shared component, a modal, or a
layout.

## Contract versus recipe

| Term | Meaning | Example |
|---|---|---|
| Contract | One replaceable, observable capability | A property card identifies and opens one rental. |
| Recipe | A complete user outcome composed from contracts, API operations, and flows | A curated group row contains only that group's property cards. |
| Implementation | Your stable name for the component subtree that provides the behavior | `website/curated-group-row` |

Kismet Elements will implement the same SDK contracts automatically. If you
build custom UI, declare the recipe and its contracts directly. Both paths are
graded against the same outcomes.

## Declare the behavior where it lives

This example declares `curated-groups` in a reusable row component. It also
binds the visible group description and each property card to exact SDK data.

```tsx
// Documentation example for thoughts/ledgers/LEDGER-kismet-recipe-conformance.md.
import { Kismet } from '@kismet-tech/sdk/react';
import type {
  BookableProductGroup,
  VacationRentalSummary,
} from '@kismet-tech/sdk/contracts';

export function PropertyGroupRow({
  group,
  rentals,
}: {
  group: BookableProductGroup;
  rentals: readonly VacationRentalSummary[];
}) {
  return (
    <Kismet.Recipe
      as="section"
      recipe="curated-groups"
      implementation="website/curated-group-row"
      subject={group}
    >
      <Kismet.Contract
        contract="property-group"
        implementation="website/curated-group-row"
        subject={group}
      >
        <Kismet.Field
          as="h2"
          entity={group}
          field="name"
          implementation="website/curated-group-row/name"
        />
        <Kismet.Field
          as="p"
          entity={group}
          field="description"
          implementation="website/curated-group-row/description"
          transform={Kismet.transforms.trim}
        />
        <Kismet.Field
          as="p"
          entity={group}
          field="memberCount"
          implementation="website/curated-group-row/member-count"
          render={(memberCount) => `${memberCount} stays`}
        />
        <ul>
          {rentals.map((rental) => (
            <Kismet.Contract
              as="li"
              key={rental.id}
              contract="property-card"
              implementation="website/property-card"
              subject={rental}
            >
              <Kismet.Field
                as="h3"
                entity={rental}
                field="name"
                implementation="website/property-card/name"
              />
              <Kismet.Field
                entity={rental}
                field="media.hero.url"
                implementation="website/property-card/lead-image"
                render={(leadImageUrl) =>
                  leadImageUrl ? (
                    <img src={leadImageUrl} alt={rental.name} />
                  ) : null
                }
              />
              <Kismet.Field
                entity={rental}
                field="slug"
                implementation="website/property-card/link"
                render={(rentalSlug) => (
                  <a href={`/properties/${rentalSlug}`}>View stay</a>
                )}
              />
            </Kismet.Contract>
          ))}
        </ul>
      </Kismet.Contract>
    </Kismet.Recipe>
  );
}
```

`Kismet.Field` records the entity type, stable entity ID, exact property path,
source kind, implementation ID, and named transforms in rendered metadata. A
browser extension or coding agent can therefore distinguish “edit this group
description” from “change this component's typography.” A transform such as
`Kismet.transforms.trim` changes presentation without hiding that the source is
still `bookableProductGroup.description`.

Use `Kismet.Derived` when visible output combines multiple source fields. Do not
claim a direct field binding for calculated or locally authored text.

## Customize without weakening the base contract

Styling and component composition are unrestricted. To add requirements for a
reusable custom implementation, extend a base contract through the recipes
entry point:

```ts
import {
  extendKismetContract,
  getKismetContract,
} from '@kismet-tech/sdk/recipes';

const propertyCardContract = getKismetContract('property-card');
if (!propertyCardContract) throw new Error('Missing property-card contract.');

export const premiumPropertyCardContract = extendKismetContract(
  propertyCardContract,
  {
    id: 'website/premium-property-card',
    version: '1.0.0',
    title: 'Premium property card',
    additionalProhibited: ['Do not hide the canonical property link.'],
  }
);
```

Extensions may add fields, dependencies, acceptance criteria, and
prohibitions. They cannot remove or weaken the base requirements. Define a new
independent contract if the behavior is genuinely different.

## Read the rubric before running it

```sh
npx kismet recipes explain
npx kismet recipes explain --recipe curated-groups
```

The explanation prints, in plain language, the expected outcome, evidence used
to prove it, and the negative control the grader must reject. The shipped
catalog currently contains 37 recipes (23 runner-tested) and 76 executable criteria, including
configured checkout handoffs and privacy-safe property sharing.

For the `configured-checkout` recipe, follow [Integrate Kismet Checkout](https://developers.kismet.travel/guides/checkout-integration.md).
It tests selected-stay handoff and re-quoting, not payment or reservation
confirmation. Keep the checkout product, its payment rail, and any reviewed
partner-engine integration separate from this URL-level contract.

## Test once or on every save

Install the browser once:

```sh
npx playwright install chromium
```

Run the app with deterministic fixture data and test every declared recipe:

```sh
npx kismet recipes test --data fixtures
```

Run one recipe, keep testing after source changes, or inspect the browser:

```sh
npx kismet recipes test --recipe curated-groups --output .kismet/artifacts
npx kismet recipes dev --recipe curated-groups
npx kismet recipes test --recipe curated-groups --headed
```

Pass `--url http://127.0.0.1:3000` to test an app server you already started.
Without `--url`, the runner starts the local Next.js app and the deterministic
test-data server. `--data test` (the default) serves local test data;
`--data live` talks to the hosted API with either a TEST or a LIVE key;
`--data fixtures` is a deprecated alias of `--data test`. The flag never
changes the pass condition to a fixed portfolio size.

The runner discovers internal routes, finds declarations anywhere in the
rendered component tree, observes SDK operations, checks browser and HTTP
errors, evaluates the recipe scenarios, and can save screenshots plus a
Playwright trace. Exit `0` means the implementation passed, `1` means the app
failed a criterion, and `2` means the test infrastructure failed.

## What belongs in CI

`npx kismet init` writes agent guidance and a `kismet-check.yml` workflow. The
workflow installs Chromium and runs `npx kismet verify --env production` — one
scorecard that
aggregates the site check (`kismet check`) and the recipe suite
(`kismet recipes test`), so a site failing `site/llms-txt` cannot ship with
green recipes. Keep that deterministic fixture gate required on pull requests;
use a bounded TEST-key smoke check separately for hosted image and schema
compatibility.

The deterministic checks should own facts code can prove: data membership,
links, exact field provenance, API operations, structured data, Markdown twins,
`llms.txt`, browser errors, and flow state. Use a model judge only for a criterion
whose correct outcome is genuinely qualitative, and keep its plain-language
rubric and evidence visible beside the deterministic criteria.
