mirror of
https://github.com/runyanjake/cooking.git
synced 2026-11-10 15:14:50 -08:00
Every top-level :::name block is now a tab, shown in file order, so authors can add custom tabs and reorder them in the markdown. Titles come from the name or a :::name[Title] label. The recipe card is an ordinary :::card tab. Its body lists what it prints (::ingredients, or a block written just for the card), replacing ::card, the fallback to full sections, and its fixed tab position. All recipes are migrated, with pages and card images unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
2.8 KiB
2.8 KiB
Component Architecture
State and Data Flow
- RecipeLayout owns sidebar open/close state; passes
handleFilterChange(memoised withuseCallback) down to RecipesSidebar - Sidebar is the generic shell: a modal drawer below
lg, a sticky column atlg+. While open it locks page scroll, closes on Escape/overlay/resize tolg, and moves focus in (restoring it on close). Its body hasoverscroll-containso drawer scrolling never chains to the page.onClosemust be stable (useCallback) since its effects depend on it - RecipesSidebar owns local UI state (debounced search input, tag filter text, show-more toggles) and reports filter changes via
onFilterChange - RecipesClient owns filter state (synced to the URL), the filtered recipe list, and facet counts (all memoised with
useMemo). Each facet's counts apply every filter except its own; options with zero results are hidden - ActiveFilters renders removable chips for the active filters above the results grid
- Recipe page gets the parsed
RecipeContentfromgetRecipeContent(cached), renders each tab in file order with RecipeMarkdown or RecipeCardPanel (server), and passes them to RecipeTabs (client). All panels are in the HTML; inactive ones arehidden - Recipe card:
getRecipeCardImagescomputes the layout once per recipe; the page and thecard/[image]route both use it, so the tab always matches the generated files. Layout measures real block heights with satori'sonNodeDetected, so fitting is exact, not estimated
Known Constraints
- Recipe image URLs are
/recipes/[category]/[slug]/assets/...(from frontmatter, not the folder on disk). UseresolveRecipeAssetUrlfromlib/recipe-urls.ts— never build them fromfolderPath, which can differ from category/slug and uses backslashes on Windows - Recipe detail and asset routes set
dynamicParams = false; everything is prerendered and the runtime image doesn't shiprecipes/ - Paragraphs containing only images are converted to
<figure>elements during parsing (caption from the image title), so there's no<p><figure>nesting - Card fonts are read from
node_modules/@fontsource/*at build time; satori needs woff/ttf (not woff2) and doesn't support variable fonts lib/recipes.tsuses Node.jsfs— server-side only; never import in client components- Build warnings about
<img>vs<Image />in RecipeMarkdown and RecipeCardImage are intentional - The About page is hand-written JSX, not markdown; it shares body typography with recipe tabs through
MARKDOWN_BODY_CLASSES. Change spacing there rather than per page. Class strings must live incomponents/orapp/— Tailwind doesn't scanlib/ - Never add redundant ARIA roles on semantic elements (
<main>,<aside>,<footer>already carry implicit roles)