feat(app): add recipe content panel with grid and detail views

- Add RecipeGrid with search, meta badges (time/servings/calories), ingredient preview
- Add RecipeDetail with ingredient checklist, scaling slider, numbered steps
- Add generateRecipeFallback SVG for recipe cards (text-only style)
- Wire recipe extraction into content panel tab system
- Add isRecipeQuery/isRecipeLikeResponse classifiers in contentFiltering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-06 16:27:20 +00:00
co-authored by Claude Opus 4.6
parent 2304e5904e
commit dc4c80a08b
7 changed files with 363 additions and 5 deletions
@@ -92,6 +92,11 @@
:place="selectedPlace"
@back="closePlaceDetail"
/>
<RecipeDetail
v-else-if="selectedRecipe"
:recipe="selectedRecipe"
@back="closeRecipeDetail"
/>
<AppDetail
v-else-if="selectedApp"
:app="selectedApp"
@@ -166,6 +171,12 @@
:title="panelTitle"
@select-place="openPlaceDetail"
/>
<RecipeGrid
v-else-if="activeTab === 'recipe'"
:recipes="panelRecipes"
:title="panelTitle"
@select-recipe="openRecipeDetail"
/>
<NewsGrid
v-else-if="activeTab === 'news'"
:articles="panelWebResults"
@@ -227,6 +238,8 @@ import ImageGrid from './ImageGrid.vue'
import ImageDetail from './ImageDetail.vue'
import PlaceGrid from './PlaceGrid.vue'
import PlaceDetail from './PlaceDetail.vue'
import RecipeGrid from './RecipeGrid.vue'
import RecipeDetail from './RecipeDetail.vue'
import PodcastGrid from './PodcastGrid.vue'
import PodcastDetail from './PodcastDetail.vue'
import NewsGrid from './NewsGrid.vue'
@@ -271,6 +284,7 @@ const {
panelWebsites,
panelImages,
panelPlaces,
panelRecipes,
panelApps,
panelMagazineSections,
panelMagazineHeroImage,
@@ -286,6 +300,7 @@ const {
selectedImage,
selectedPlace,
selectedArticle,
selectedRecipe,
selectedApp,
selectedDesignSystemItem,
setActiveTab,
@@ -303,6 +318,8 @@ const {
closeImageDetail,
openPlaceDetail,
closePlaceDetail,
openRecipeDetail,
closeRecipeDetail,
openAppDetail,
closeAppDetail,
closeArticleDetail,
@@ -316,7 +333,7 @@ const {
} = useContentPanel()
const hasDetailOpen = computed(() =>
!!(selectedFilm.value || selectedBook.value || selectedTVSeries.value || selectedSong.value || selectedPodcast.value || selectedImage.value || selectedPlace.value || selectedArticle.value || selectedApp.value || selectedDesignSystemItem.value || longFormArticle.value || pdfUrl.value || mapPlaces.value.length > 0)
!!(selectedFilm.value || selectedBook.value || selectedTVSeries.value || selectedSong.value || selectedPodcast.value || selectedImage.value || selectedPlace.value || selectedRecipe.value || selectedArticle.value || selectedApp.value || selectedDesignSystemItem.value || longFormArticle.value || pdfUrl.value || mapPlaces.value.length > 0)
)
const windowWidth = ref(window.innerWidth)
@@ -343,6 +360,7 @@ const TAB_LABELS: Record<ContentTab, string> = {
tvshow: 'TV',
image: 'Images',
place: 'Places',
recipe: 'Recipes',
song: 'Music',
podcast: 'Podcasts',
news: 'News',