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>
23 lines
2.8 KiB
Markdown
23 lines
2.8 KiB
Markdown
# Component Architecture
|
|
|
|
## State and Data Flow
|
|
|
|
- **RecipeLayout** owns sidebar open/close state; passes `handleFilterChange` (memoised with `useCallback`) down to RecipesSidebar
|
|
- **Sidebar** is the generic shell: a modal drawer below `lg`, a sticky column at `lg`+. While open it locks page scroll, closes on Escape/overlay/resize to `lg`, and moves focus in (restoring it on close). Its body has `overscroll-contain` so drawer scrolling never chains to the page. `onClose` must 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 `RecipeContent` from `getRecipeContent` (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 are `hidden`
|
|
- **Recipe card**: `getRecipeCardImages` computes the layout once per recipe; the page and the `card/[image]` route both use it, so the tab always matches the generated files. Layout measures real block heights with satori's `onNodeDetected`, so fitting is exact, not estimated
|
|
|
|
## Known Constraints
|
|
|
|
- Recipe image URLs are `/recipes/[category]/[slug]/assets/...` (from frontmatter, not the folder on disk). Use `resolveRecipeAssetUrl` from `lib/recipe-urls.ts` — never build them from `folderPath`, 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 ship `recipes/`
|
|
- 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.ts` uses Node.js `fs` — 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 in `components/` or `app/` — Tailwind doesn't scan `lib/`
|
|
- Never add redundant ARIA roles on semantic elements (`<main>`, `<aside>`, `<footer>` already carry implicit roles)
|