Frontend: B1 — Recipe library #22
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Browse all recipes in a card grid. Hub for recipe discovery and creation. Entry point for J1 (Add a recipe).
Journey: J1 — Add a recipe
Role: Planner only
Screen: B1
Layout
Mobile (< 768px)
Desktop (> 1024px)
Recipe Cards
Mobile
Desktop
Filter Chips
--green-tintbg +--green-darktext--color-borderborderBehavior
Acceptance Criteria
Spec file:
specs/frontend/j1-add-recipe.html— screen B1 with mobile (2-col grid) + desktop (4-col grid + filter chips) previews, agent table, and LLM implementation guide.👨💻 Kai — Frontend Engineer
B1 is a data-heavy grid with filtering and search — good scope for breaking it into focused components. Here's how I'd think about the implementation:
Component split I'd propose:
RecipeLibraryPage.svelte— top-level, owns$statefor active filter + search queryRecipeTopbar.svelte— title + search icon (mobile) / search input (desktop) + add buttonFilterChipRow.svelte— chip list, emits selected filter back upRecipeGrid.svelte— receivesrecipesprop, renders cards in the correct column countRecipeCard.svelte— single card, two display variants via acompactprop (mobile 64px vs desktop 100px)Questions before I start:
Risks I see:
grid-cols-2 lg:grid-cols-4is straightforward, but I want to confirm the breakpoints match Atlas's spec (768px / 1024px). SvelteKit SSR won't know the viewport, so the grid must be CSS-only — no JS-based column detection.+page.server.tsshould fetch with pagination or a reasonable limit. An unbounded "give me all recipes" call will bite us later.RecipeCardneeds an explicitalttext strategy for images — what do we show as alt text when there's no recipe image and we're rendering a placeholder?🔧 Backend Engineer — Recipe Library (B1)
Solid screen definition. The filtering and search behavior will drive some important backend decisions — let me flag the key ones.
API shape questions:
GET /recipes?tag=Vegetarian&effort=Easy&search=pasta. We should agree on the exact query parameter names before the frontend builds against it.GET /recipes/tagsendpoint to populate the chips. If they're static, document that contract clearly.limitandoffsetparameter to the endpoint even if the UI doesn't expose paging yet.Search implementation:
searchby recipe name — is this a SQLILIKE '%term%'onname? That's fine for v1 but won't use an index well. Aginindex onto_tsvector(name)would scale better. Worth deciding now rather than retrofitting later.Data concerns:
RecipeListItemDTOis defined separately from theRecipeDetailDTOthat B2 uses.Questions:
🧪 QA Engineer — Recipe Library (B1)
B1 has a deceptively large test surface. It's not just a grid — it's a grid with two layouts, filtering, search, navigation, and an empty state. Let me map out the paths I'll want covered.
Component test coverage (Vitest + Testing Library):
RecipeCard: renders name, time, effort; truncates long names; shows placeholder when no image; clicking fires navigationFilterChipRow: renders all chip options; clicking a chip marks it active (correct styling class or aria-pressed); clicking active chip deactivates (if toggle behavior is intended)RecipeGrid: renders correct number of cards from props; renders empty state whenrecipesis an empty arrayIntegration / E2E paths I want covered:
Questions / gaps I see:
🔐 Sable — Security Engineer
B1 is a read-heavy screen, but there are a few threat vectors worth calling out before implementation starts.
Access control:
SecurityFilterChainor the service layer, not just by hiding the nav link from members. A member who knows the URL should get a 403, not a 200 with data.household_idfrom the authenticated session — never from a request parameter that could be spoofed (IDOR risk).Search input:
@Size(max = 100)or similar). Unbounded search strings can be a DoS vector against the DB.Xis text-interpolated in Svelte, not injected via{@html}— otherwise we have a reflected XSS vector.Image handling:
srcvalues are validated before storage (nojavascript:URIs, no cross-household image URL leakage). This is a decision point that should be captured in the image upload spec.Information leakage:
household_id, internal IDs beyond what the frontend needs for navigation, or any admin/audit fields. Confirm theRecipeListItemDTOis purpose-built and reviewed before the endpoint ships.Questions:
🎨 Atlas — UI/UX Designer
B1 is the entry point for one of the most used journeys (J1 — Add a recipe) and the primary discovery surface. A few design clarifications needed before implementation starts.
Filter chips — spec alignment:
11px weight 500, 5px 14px padding, 12px radius— this diverges from the default--radius-md (6px). I want to explicitly confirm12pxis intentional here (it would map to--radius-lg), not a copy error from another spec. Kai will need the exact token.--green-tintbg +--green-darktext, matching the nav active pattern — good.--color-borderborder — confirm this means a transparent/white background with a border stroke, not a filled background.Mobile collapsible chips:
Card image areas:
--color-borderbackground with a centered icon, not a broken image or blank space.Empty states:
Typography check:
text-overflow: ellipsison a single line, not a two-line clamp. Single line is safer at 64px card height.