Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions registry/packages/recipe.book/recipe.book.contract.intent
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
AIM: recipe.book#contract@1.5

CONTRACT CreateRecipe {
SUMMARY: "Creates a new recipe with its ingredients and steps."
INPUT {
title: string required
description: string optional
servings: integer optional
prepTime: integer required
cookTime: integer required
difficulty: string optional
category: string optional
tags: json optional
imageUrl: string optional
ingredients: json required
steps: json required
}
EXPECTS {
- "Title must not be empty or purely whitespace"
- "At least one ingredient must be provided"
- "At least one step must be provided"
- "prepTime and cookTime must be non-negative integers"
}
ENSURES {
- PERSISTS "New Recipe with provided metadata"
- PERSISTS "Ingredient records linked to the new Recipe"
- PERSISTS "Step records linked to the new Recipe with sequential orderIndex"
}
RETURNS {
- "The created Recipe with its ingredients and steps"
}
}

CONTRACT GetRecipe {
SUMMARY: "Retrieves a single recipe with all its ingredients and steps."
INPUT {
recipeId: uuid required
}
EXPECTS {
- "Recipe record must exist"
}
ENSURES {
- CALLS "Recipe read model with joined ingredients and steps"
}
RETURNS {
- "The full Recipe including ordered ingredients and steps"
}
}

CONTRACT UpdateRecipe {
SUMMARY: "Updates an existing recipe and optionally replaces its ingredients or steps."
INPUT {
recipeId: uuid required
title: string optional
description: string optional
servings: integer optional
prepTime: integer optional
cookTime: integer optional
difficulty: string optional
category: string optional
tags: json optional
imageUrl: string optional
ingredients: json optional
steps: json optional
}
EXPECTS {
- "Recipe record must exist"
- "If title is provided, it must not be empty or purely whitespace"
- "If ingredients are provided, at least one must be included"
- "If steps are provided, at least one must be included"
}
ENSURES {
- UPDATES "Recipe fields that are provided in the input"
- UPDATES "Ingredient records when ingredients are provided (full replacement)"
- UPDATES "Step records when steps are provided (full replacement)"
}
RETURNS {
- "The updated Recipe with its ingredients and steps"
}
}

CONTRACT DeleteRecipe {
SUMMARY: "Permanently removes a recipe and all its associated data."
INPUT {
recipeId: uuid required
}
EXPECTS {
- "Recipe record must exist"
}
ENSURES {
- DELETES "All Ingredient records linked to the Recipe"
- DELETES "All Step records linked to the Recipe"
- DELETES "The Recipe record"
}
RETURNS {
- "Confirmation of deletion"
}
}

CONTRACT ToggleFavorite {
SUMMARY: "Toggles the favorite status of a recipe."
INPUT {
recipeId: uuid required
}
EXPECTS {
- "Recipe record must exist"
}
ENSURES {
- UPDATES "Recipe.isFavorite to the opposite of its current value"
}
RETURNS {
- "The updated Recipe"
}
}

CONTRACT ImportRecipe {
SUMMARY: "Imports a recipe from an external URL by extracting structured data from the page."
INPUT {
url: string required
}
EXPECTS {
- "URL must be valid and reachable"
}
ENSURES {
- CALLS "ExtractRecipeFromUrl flow to parse recipe data from the page"
- PERSISTS "New Recipe with extracted metadata and sourceUrl set to the input URL"
- PERSISTS "Ingredient records linked to the new Recipe"
- PERSISTS "Step records linked to the new Recipe with sequential orderIndex"
}
RETURNS {
- "The created Recipe with its ingredients and steps"
}
}

CONTRACT ListRecipes {
SUMMARY: "Returns a filtered and optionally sorted list of recipes."
INPUT {
searchTerm: string optional
tag: string optional
category: string optional
favoritesOnly: boolean optional
}
ENSURES {
- CALLS "Recipe read model with applied filters"
}
RETURNS {
- "List of Recipe summaries matching the filters"
}
}
53 changes: 53 additions & 0 deletions registry/packages/recipe.book/recipe.book.intent
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
AIM: recipe.book#intent@1.5

INTENT RecipeBook {
SUMMARY: "A personal recipe collection app for organizing, browsing, and managing favorite recipes."

REQUIREMENTS {
- "Users can create new recipes with ingredients, steps, and metadata."
- "Users can view a recipe with its full ingredient list and step-by-step instructions."
- "Users can edit existing recipes to update any field."
- "Users can delete recipes, which removes all associated ingredients and steps."
- "Users can search recipes by name or filter by tag."
- "Users can mark recipes as favorites for quick access."
- "Users can import a recipe by pasting a URL from an external recipe site."
- "Users can view a list of their favorite recipes."
- "Users can filter recipes by category (e.g. lunch, dessert, pasta)."
- "Recipe data is persisted to a local file on disk (no external database)."
}

TESTS {
- "Creating a recipe with an empty title is rejected."
- "Creating a recipe with zero ingredients is rejected."
- "Creating a recipe with zero steps is rejected."
- "Deleting a recipe also removes its associated ingredients and steps."
- "Toggling favorite on a non-existent recipe returns an error."
- "Duplicate recipe titles within the same collection are allowed."
- "Importing from an invalid or unreachable URL returns a user-friendly error."
- "Importing from a page with no recognizable recipe data returns an extraction failure message."
}

FLOW ExtractRecipeFromUrl {
SUMMARY: "Fetches a web page and extracts structured recipe data for import."
TRIGGER {
- "Invoked by ImportRecipe contract"
}
STEPS {
- "FETCH HTML content from the provided URL"
- "SEARCH for structured recipe data via JSON-LD or Microdata markup"
- "BRANCH to raw HTML parsing when no structured data is found"
- "MAP extracted fields to Recipe, Ingredient, and Step schemas"
- "CALL CreateRecipe with the mapped data and sourceUrl"
}
ON_ERROR {
- "RETURN user-friendly extraction failure message when recipe data cannot be identified"
- "RETURN connection error message when the URL is unreachable"
}
}
}

INCLUDES {
schema: "recipe.book.schema.intent"
contract: "recipe.book.contract.intent"
view: "recipe.book.view.intent"
}
41 changes: 41 additions & 0 deletions registry/packages/recipe.book/recipe.book.schema.intent
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
AIM: recipe.book#schema@1.5

SCHEMA Recipe {
SUMMARY: "The core entity representing a single recipe in the collection."
ATTRIBUTES {
id: uuid required generated immutable
title: string required max(150)
description: string optional max(500)
servings: integer required min(1) default(1)
prepTime: integer required min(0)
cookTime: integer required min(0)
difficulty: string required enum('EASY', 'MEDIUM', 'HARD') default('MEDIUM')
category: string optional max(50)
tags: json optional
imageUrl: string optional
sourceUrl: string optional
isFavorite: boolean required default(false)
createdAt: timestamp generated immutable
}
}

SCHEMA Ingredient {
SUMMARY: "A single ingredient entry belonging to a recipe."
ATTRIBUTES {
id: uuid required generated immutable
name: string required max(100)
quantity: string required
unit: string optional max(30)
recipeId: uuid required ref(Recipe.id)
}
}

SCHEMA Step {
SUMMARY: "A single instruction step belonging to a recipe, ordered by index."
ATTRIBUTES {
id: uuid required generated immutable
orderIndex: integer required min(0)
instruction: string required max(1000)
recipeId: uuid required ref(Recipe.id)
}
}
76 changes: 76 additions & 0 deletions registry/packages/recipe.book/recipe.book.view.intent
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
AIM: recipe.book#view@1.5

VIEW RecipeList {
SUMMARY: "The main browsing interface displaying all recipes with search and filter options."
DISPLAY {
- "A grid or list of Recipe cards showing title, difficulty, prepTime, cookTime, and tags"
- "A search bar for filtering recipes by name"
- "Tag-based filter chips for narrowing results"
- "Category filter chips for narrowing results (e.g. lunch, dessert, pasta)"
- "A toggle to show only favorite recipes"
- "Visual indicator for favorite status on each card"
- "Empty state message when no recipes match the current filters"
}
ACTIONS {
- "Clicking 'Add Recipe' -> CALL CreateRecipe via RecipeForm"
- "Clicking a recipe card -> navigate to RecipeDetail"
- "Clicking the favorite icon on a card -> CALL ToggleFavorite"
- "Typing in the search bar -> CALL ListRecipes with searchTerm"
- "Selecting a tag filter -> CALL ListRecipes with tag"
- "Selecting a category filter -> CALL ListRecipes with category"
- "Clicking 'Import from URL' -> open ImportRecipeModal"
}
}

VIEW RecipeDetail {
SUMMARY: "Full display of a single recipe with ingredients, steps, and metadata."
DISPLAY {
- "Recipe title, description, and image"
- "Metadata badges for servings, prepTime, cookTime, and difficulty"
- "Ordered list of Ingredient records with quantity and unit"
- "Numbered list of Step records ordered by orderIndex"
- "Tags displayed as chips"
- "Favorite status indicator"
}
ACTIONS {
- "Clicking 'Edit' -> navigate to RecipeForm with existing data"
- "Clicking 'Delete' -> confirm and CALL DeleteRecipe"
- "Clicking the favorite icon -> CALL ToggleFavorite"
}
}

VIEW RecipeForm {
SUMMARY: "Form interface for creating or editing a recipe with dynamic ingredient and step rows."
DISPLAY {
- "Text inputs for title, description, servings, prepTime, and cookTime"
- "Dropdown selector for difficulty level"
- "Text input for category (e.g. lunch, dessert, pasta)"
- "Tag input with autocomplete from existing tags"
- "Image URL input with preview"
- "Dynamic list of ingredient rows with name, quantity, and unit fields"
- "Dynamic list of step rows with instruction field, reorderable by drag"
- "Add buttons to append new ingredient or step rows"
}
ACTIONS {
- "Clicking 'Save' -> CALL CreateRecipe or CALL UpdateRecipe"
- "Clicking 'Cancel' -> navigate back without saving"
- "Clicking remove on an ingredient row -> remove it from the form"
- "Clicking remove on a step row -> remove it from the form"
- "Dragging a step row -> reorder steps"
}
}

VIEW ImportRecipeModal {
SUMMARY: "Modal dialog for importing a recipe from an external URL."
DISPLAY {
- "Text input for pasting a recipe URL"
- "Loading spinner while fetching and extracting recipe data"
- "Preview of extracted recipe title, ingredients, and steps before saving"
- "Error message when extraction fails or URL is invalid"
}
ACTIONS {
- "Clicking 'Import' -> CALL ImportRecipe with the entered URL"
- "Clicking 'Save' after preview -> confirm and persist the imported recipe"
- "Clicking 'Cancel' -> close modal without saving"
}
}