diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c27c8..1e19098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together. +## 0.62.0 — 2026-07-20 23:45 + +### Added +- Settings → Features: new Chatbots toggle — hides both the recipe cooking-chat panel and the recipe-list cooking assistant when off. + +### Fixed +- Feature toggles (Settings → Features) were only respected by the mobile hamburger menu — the desktop horizontal nav still showed every item regardless of your saved preferences. Both now read the same visibility list. + ## 0.61.0 — 2026-07-20 23:15 ### Added diff --git a/apps/web/app/(app)/recipes/[id]/page.tsx b/apps/web/app/(app)/recipes/[id]/page.tsx index a8d59e5..3c1b875 100644 --- a/apps/web/app/(app)/recipes/[id]/page.tsx +++ b/apps/web/app/(app)/recipes/[id]/page.tsx @@ -35,6 +35,7 @@ import { CommentsSection } from "@/components/social/comments-section"; import { getPublicUrl } from "@/lib/storage"; import { cn, stripMarkdown } from "@/lib/utils"; import { RecipeChatPanel } from "@/components/recipe/recipe-chat-panel"; +import { getFeaturePrefs } from "@/lib/feature-prefs"; import { BatchCookSteps } from "@/components/recipe/batch-cook-steps"; import { BatchCookDishes } from "@/components/recipe/batch-cook-dishes"; import { KeepScreenAwake } from "@/components/recipe/keep-screen-awake"; @@ -71,7 +72,7 @@ export default async function RecipePage({ params }: Params) { const DIETARY_LABELS = m.recipe.dietary; const unitPref = (session.user as { unitPref?: string }).unitPref === "imperial" ? "imperial" : "metric"; - const [recipe, ratingData, favoriteData, myRating, forkedFrom, myNote, dishCookLog, featureFlags] = await Promise.all([ + const [recipe, ratingData, favoriteData, myRating, forkedFrom, myNote, dishCookLog, featureFlags, featurePrefs] = await Promise.all([ db.query.recipes.findFirst({ where: and( eq(recipes.id, id), @@ -102,6 +103,7 @@ export default async function RecipePage({ params }: Params) { columns: { batchDishId: true, cookedAt: true }, }), getFeatureFlagMatrix(), + getFeaturePrefs(session.user.id), ]); if (!recipe) notFound(); @@ -507,7 +509,7 @@ export default async function RecipePage({ params }: Params) { - + {featurePrefs.chatbots && } ); } diff --git a/apps/web/app/(app)/recipes/page.tsx b/apps/web/app/(app)/recipes/page.tsx index 186aa2b..ad8c549 100644 --- a/apps/web/app/(app)/recipes/page.tsx +++ b/apps/web/app/(app)/recipes/page.tsx @@ -9,6 +9,7 @@ import { RecipesEmptyState } from "@/components/recipe/recipes-empty-state"; import { RecipesGrid } from "@/components/recipe/recipes-grid"; import { CookingAssistantPanel } from "@/components/recipe/cooking-assistant-panel"; import { getMessages } from "@/lib/i18n/server"; +import { getFeaturePrefs } from "@/lib/feature-prefs"; export const metadata: Metadata = {}; @@ -40,6 +41,7 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear const session = await auth.api.getSession({ headers: await headers() }); if (!session) return null; const m = getMessages((session.user as { locale?: string }).locale); + const featurePrefs = await getFeaturePrefs(session.user.id); const { q, sort, visibility, difficulty, tag, page: pageParam, batchCook, recipeType } = await searchParams; const query = (q ?? "").trim().slice(0, 200); @@ -160,7 +162,7 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear )} - + {featurePrefs.chatbots && } ); } diff --git a/apps/web/app/api/v1/users/me/feature-prefs/route.ts b/apps/web/app/api/v1/users/me/feature-prefs/route.ts index 2e0e123..1df3ab0 100644 --- a/apps/web/app/api/v1/users/me/feature-prefs/route.ts +++ b/apps/web/app/api/v1/users/me/feature-prefs/route.ts @@ -11,6 +11,7 @@ const PutSchema = z.object({ shoppingLists: z.boolean().optional(), collections: z.boolean().optional(), messages: z.boolean().optional(), + chatbots: z.boolean().optional(), }); export async function GET() { diff --git a/apps/web/components/layout/nav.tsx b/apps/web/components/layout/nav.tsx index d14b2ea..75ba73d 100644 --- a/apps/web/components/layout/nav.tsx +++ b/apps/web/components/layout/nav.tsx @@ -52,7 +52,7 @@ export function Nav() { // backend default — avoids a flash of items disappearing on load for the // (much more common) case where a user hasn't hidden anything. const [featurePrefs, setFeaturePrefs] = useState({ - nutrition: true, pantry: true, mealPlan: true, shoppingLists: true, collections: true, messages: true, + nutrition: true, pantry: true, mealPlan: true, shoppingLists: true, collections: true, messages: true, chatbots: true, }); useEffect(() => { @@ -121,7 +121,7 @@ export function Nav() { Epicure