Recipes, contracts, and implementation tests
View .mdRecipes 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
Section titled “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
Section titled “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.
// 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
Section titled “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:
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
Section titled “Read the rubric before running it”npx kismet recipes explainnpx kismet recipes explain --recipe curated-groupsThe 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.
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
Section titled “Test once or on every save”Install the browser once:
npx playwright install chromiumRun the app with deterministic fixture data and test every declared recipe:
npx kismet recipes test --data fixturesRun one recipe, keep testing after source changes, or inspect the browser:
npx kismet recipes test --recipe curated-groups --output .kismet/artifactsnpx kismet recipes dev --recipe curated-groupsnpx kismet recipes test --recipe curated-groups --headedPass --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
Section titled “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.