@ssejal8/recipe-ui-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.npmr +1 -0
  3. package/README.md +354 -0
  4. package/apps/recipe-web/package.json +31 -0
  5. package/apps/recipe-web/src/app.css +418 -0
  6. package/apps/recipe-web/src/app.d.ts +32 -0
  7. package/apps/recipe-web/src/app.html +25 -0
  8. package/apps/recipe-web/src/lib/api/mealdb.ts +240 -0
  9. package/apps/recipe-web/src/lib/components/AppHeader.svelte +290 -0
  10. package/apps/recipe-web/src/lib/components/ConfirmDialog.svelte +46 -0
  11. package/apps/recipe-web/src/lib/components/Modal.svelte +33 -0
  12. package/apps/recipe-web/src/lib/components/RecipeCard.svelte +100 -0
  13. package/apps/recipe-web/src/lib/components/RecipeForm.svelte +560 -0
  14. package/apps/recipe-web/src/lib/components/RecipeGrid.svelte +77 -0
  15. package/apps/recipe-web/src/lib/components/RecipePickerDialog.svelte +185 -0
  16. package/apps/recipe-web/src/lib/components/Toaster.svelte +111 -0
  17. package/apps/recipe-web/src/lib/domain/recipe.ts +130 -0
  18. package/apps/recipe-web/src/lib/domain/validation.ts +189 -0
  19. package/apps/recipe-web/src/lib/stencil/WebComponent.svelte +145 -0
  20. package/apps/recipe-web/src/lib/stencil/register.ts +28 -0
  21. package/apps/recipe-web/src/lib/stores/favorites.svelte.ts +81 -0
  22. package/apps/recipe-web/src/lib/stores/myRecipes.svelte.ts +98 -0
  23. package/apps/recipe-web/src/lib/stores/persisted.svelte.ts +73 -0
  24. package/apps/recipe-web/src/lib/stores/planner.svelte.ts +275 -0
  25. package/apps/recipe-web/src/lib/stores/theme.svelte.ts +67 -0
  26. package/apps/recipe-web/src/lib/stores/toast.svelte.ts +66 -0
  27. package/apps/recipe-web/src/routes/+error.svelte +36 -0
  28. package/apps/recipe-web/src/routes/+layout.svelte +62 -0
  29. package/apps/recipe-web/src/routes/+page.svelte +352 -0
  30. package/apps/recipe-web/src/routes/+page.ts +107 -0
  31. package/apps/recipe-web/src/routes/favorites/+page.svelte +170 -0
  32. package/apps/recipe-web/src/routes/my-recipes/+layout.ts +55 -0
  33. package/apps/recipe-web/src/routes/my-recipes/+page.svelte +180 -0
  34. package/apps/recipe-web/src/routes/my-recipes/[id]/edit/+page.svelte +104 -0
  35. package/apps/recipe-web/src/routes/my-recipes/new/+page.svelte +67 -0
  36. package/apps/recipe-web/src/routes/planner/+page.svelte +422 -0
  37. package/apps/recipe-web/src/routes/recipes/[id]/+page.svelte +538 -0
  38. package/apps/recipe-web/src/routes/recipes/[id]/+page.ts +24 -0
  39. package/apps/recipe-web/static/favicon.svg +8 -0
  40. package/apps/recipe-web/static/robots.txt +2 -0
  41. package/apps/recipe-web/svelte.config.js +35 -0
  42. package/apps/recipe-web/tsconfig.json +14 -0
  43. package/apps/recipe-web/vite.config.ts +23 -0
  44. package/package.json +29 -0
  45. package/packages/recipe-kit-stencil/LICENSE +21 -0
  46. package/packages/recipe-kit-stencil/README.md +185 -0
  47. package/packages/recipe-kit-stencil/package-lock.json +4772 -0
  48. package/packages/recipe-kit-stencil/package.json +91 -0
  49. package/packages/recipe-kit-stencil/src/components/rk-badge/readme.md +38 -0
  50. package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.css +102 -0
  51. package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.tsx +31 -0
  52. package/packages/recipe-kit-stencil/src/components/rk-chip/readme.md +52 -0
  53. package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.css +155 -0
  54. package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.tsx +105 -0
  55. package/packages/recipe-kit-stencil/src/components/rk-empty-state/readme.md +44 -0
  56. package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.css +64 -0
  57. package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.tsx +50 -0
  58. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/readme.md +58 -0
  59. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.css +225 -0
  60. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.tsx +182 -0
  61. package/packages/recipe-kit-stencil/src/components/rk-modal/readme.md +57 -0
  62. package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.css +163 -0
  63. package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.tsx +176 -0
  64. package/packages/recipe-kit-stencil/src/components/rk-rating/readme.md +42 -0
  65. package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.css +71 -0
  66. package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.tsx +114 -0
  67. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/readme.md +66 -0
  68. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.css +280 -0
  69. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.tsx +179 -0
  70. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/test/rk-recipe-card.spec.tsx +70 -0
  71. package/packages/recipe-kit-stencil/src/components/rk-search-bar/readme.md +67 -0
  72. package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.css +141 -0
  73. package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.tsx +152 -0
  74. package/packages/recipe-kit-stencil/src/components/rk-search-bar/test/rk-search-bar.spec.tsx +93 -0
  75. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/readme.md +41 -0
  76. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.css +91 -0
  77. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.tsx +115 -0
  78. package/packages/recipe-kit-stencil/src/components/rk-tag-input/readme.md +47 -0
  79. package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.css +146 -0
  80. package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.tsx +181 -0
  81. package/packages/recipe-kit-stencil/src/components/rk-tag-input/test/rk-tag-input.spec.tsx +76 -0
  82. package/packages/recipe-kit-stencil/src/components.d.ts +1192 -0
  83. package/packages/recipe-kit-stencil/src/global/rk-global.css +104 -0
  84. package/packages/recipe-kit-stencil/src/index.html +259 -0
  85. package/packages/recipe-kit-stencil/src/index.ts +6 -0
  86. package/packages/recipe-kit-stencil/stencil.config.ts +41 -0
  87. package/packages/recipe-kit-stencil/tsconfig.json +24 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm -v)",
5
+ "Bash(git --version)",
6
+ "Bash(npm whoami *)",
7
+ "Bash(npx stencil *)"
8
+ ]
9
+ }
10
+ }
package/.npmr ADDED
@@ -0,0 +1 @@
1
+ registry=https://registry.npmjs.org/
package/README.md ADDED
@@ -0,0 +1,354 @@
1
+ # Recipe Finder & Meal Planner
2
+
3
+ A recipe discovery and weekly meal-planning application built with **Svelte 5 + SvelteKit**, using a **StencilJS** web component library that is published to npm and consumed as a package dependency.
4
+
5
+ | | |
6
+ | --- | --- |
7
+ | **Live application** | _fill in after deploying — see [Deployment](#deployment)_ |
8
+ | **npm package** | https://www.npmjs.com/package/recipe-kit-stencil |
9
+ | **GitHub repository** | _fill in after pushing — see [Repository](#repository)_ |
10
+ | **Recipe data** | [TheMealDB](https://www.themealdb.com/api.php) (public test key, no signup) |
11
+
12
+ ---
13
+
14
+ ## Contents
15
+
16
+ - [What it does](#what-it-does)
17
+ - [Repository layout](#repository-layout)
18
+ - [Setup](#setup)
19
+ - [Starting the development server](#starting-the-development-server)
20
+ - [The Stencil component library](#the-stencil-component-library)
21
+ - [Publishing to npm](#publishing-to-npm)
22
+ - [How SvelteKit consumes the package](#how-sveltekit-consumes-the-package)
23
+ - [Architecture](#architecture)
24
+ - [Deployment](#deployment)
25
+ - [Repository](#repository)
26
+ - [Assumptions and decisions](#assumptions-and-decisions)
27
+ - [Known limitations](#known-limitations)
28
+
29
+ ---
30
+
31
+ ## What it does
32
+
33
+ **Recipe discovery** — Search the catalogue by name, browse by category chip, and narrow results by category, cuisine and main ingredient. Every filter lives in the URL, so a search is shareable and survives a reload. Results can be sorted, and are paged client-side.
34
+
35
+ **Recipe details** — A dedicated page per recipe with the full ingredient list (as a tickable prep checklist with progress), numbered method steps, cuisine metadata, and links to the original source and video where the API provides them.
36
+
37
+ **Recipe management** — Create, edit and delete your own recipes through a validated form. Your recipes are searchable alongside the catalogue, appear first in results, and are marked with a badge. Deletes are undoable.
38
+
39
+ **Favorites** — Favorite from any card or from the detail page, filter your saved list by text and category, and clear the list (also undoable).
40
+
41
+ **Weekly meal planner** — A 7-day × 3-meal grid for any week, with previous/next week navigation, "repeat last week", drag-and-drop between slots, a recipe picker that searches your favorites / your own recipes / the whole catalogue, and a per-week recipe summary.
42
+
43
+ Favorites, your recipes and meal plans persist in `localStorage` and stay in sync across duplicate browser tabs.
44
+
45
+ ---
46
+
47
+ ## Repository layout
48
+
49
+ ```
50
+ recipe-ui/ npm workspaces root
51
+ ├── packages/
52
+ │ └── recipe-kit-stencil/ StencilJS component library → published to npm
53
+ │ ├── src/components/ 10 web components (.tsx + .css + specs)
54
+ │ ├── src/global/ design tokens (--rk-* custom properties)
55
+ │ ├── src/index.html component gallery / dev harness
56
+ │ └── stencil.config.ts
57
+ └── apps/
58
+ └── recipe-web/ SvelteKit 5 application
59
+ ├── src/lib/api/ typed TheMealDB client
60
+ ├── src/lib/domain/ recipe model + validation rules
61
+ ├── src/lib/stencil/ the SvelteKit ↔ Stencil integration layer
62
+ ├── src/lib/stores/ Svelte 5 rune stores, localStorage-backed
63
+ ├── src/lib/components/ app-level Svelte components
64
+ └── src/routes/ pages
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Setup
70
+
71
+ **Requirements:** Node.js ≥ 20 and npm ≥ 10.
72
+
73
+ ```bash
74
+ git clone <your-repo-url> recipe-ui
75
+ cd recipe-ui
76
+ npm install
77
+ ```
78
+
79
+ A single `npm install` at the root installs both workspaces. The component library's `prepare` script builds it automatically, so the app has something to import straight away.
80
+
81
+ No environment variables and no API key are needed — TheMealDB's public test key is used.
82
+
83
+ ---
84
+
85
+ ## Starting the development server
86
+
87
+ ```bash
88
+ npm run dev
89
+ ```
90
+
91
+ Builds the component library, then starts the app on **http://localhost:5173**.
92
+
93
+ Other useful commands, all from the repository root:
94
+
95
+ | Command | What it does |
96
+ | --- | --- |
97
+ | `npm run dev` | Build the library, then run the app dev server |
98
+ | `npm run dev:app` | App dev server only (library already built) |
99
+ | `npm run dev:kit` | Library in watch mode + component gallery |
100
+ | `npm run build` | Production build of the library, then the app |
101
+ | `npm run check` | `svelte-check` type and template checking |
102
+ | `npm test` | Component library spec tests |
103
+ | `npm run publish:kit` | Publish the library to npm |
104
+
105
+ Working on a component and the app at the same time? Run `npm run dev:kit` and `npm run dev:app` in two terminals — Stencil rebuilds `dist/` on save and Vite picks it up.
106
+
107
+ To verify a real production server locally:
108
+
109
+ ```bash
110
+ npm run build:kit
111
+ npm run preview:node --workspace recipe-web # builds with adapter-node and serves it
112
+ ```
113
+
114
+ ---
115
+
116
+ ## The Stencil component library
117
+
118
+ `recipe-kit-stencil` ships 10 framework-agnostic custom elements. Full prop / event / slot tables are in [`packages/recipe-kit-stencil/README.md`](./packages/recipe-kit-stencil/README.md) and in the generated docs beside each component.
119
+
120
+ | Tag | Role in the app | Events consumed by SvelteKit |
121
+ | --- | --- | --- |
122
+ | `rk-recipe-card` | Every recipe tile, in all grids | `rkCardSelect`, `rkFavoriteToggle` |
123
+ | `rk-search-bar` | Discover, favorites, my recipes, picker | `rkSearch`, `rkClear` |
124
+ | `rk-chip` | Category rail, active-filter pills, favorites facets | `rkChipToggle`, `rkChipDismiss` |
125
+ | `rk-segmented-control` | Sort controls, picker source switch | `rkSegmentChange` |
126
+ | `rk-meal-slot` | All 21 cells of the planner grid | `rkMealAdd`, `rkMealSwap`, `rkMealRemove`, `rkMealOpen`, `rkMealDrop` |
127
+ | `rk-modal` | Picker, add-to-plan, all confirmations | `rkModalClose` |
128
+ | `rk-tag-input` | Tag field in the recipe form | `rkTagsChange` |
129
+ | `rk-badge` | Metadata badges throughout | — |
130
+ | `rk-rating` | Star rating display | `rkRatingChange` |
131
+ | `rk-empty-state` | Every empty and error state | — |
132
+
133
+ Run the gallery to see and exercise all of them, with a live event log:
134
+
135
+ ```bash
136
+ npm run dev:kit # → http://localhost:3333
137
+ ```
138
+
139
+ ### Design principles
140
+
141
+ - **Primitive props over object props.** `rk-recipe-card` takes `name`, `image`, `category` … as separate primitives rather than one `recipe` object, so it works from plain HTML attributes in any framework.
142
+ - **Components are controlled.** They emit intent and never mutate their own state. `rkFavoriteToggle` reports the state to move *to*; the app persists it and passes `favorite` back down.
143
+ - **Shadow DOM everywhere.** Styling hooks are deliberate: `--rk-*` custom properties and `part="…"` attributes.
144
+ - **Complex props have a string fallback.** `options` and `values` are arrays, assigned as DOM properties on the normal path, but both also parse a JSON string attribute.
145
+
146
+ ---
147
+
148
+ ## Publishing to npm
149
+
150
+ The library is configured to publish to the public registry regardless of any local registry setting, via `publishConfig` in its `package.json`.
151
+
152
+ ```bash
153
+ # 1. Log in to the public npm registry (once per machine)
154
+ npm login --registry=https://registry.npmjs.org/
155
+
156
+ # 2. Publish from the repository root
157
+ npm run publish:kit
158
+ ```
159
+
160
+ `prepublishOnly` runs a clean production build first, so a published tarball can never contain stale output. `files` is restricted to `dist/`, `loader/`, `README.md` and `LICENSE` — no source, no tests, no config.
161
+
162
+ ### Versioning
163
+
164
+ [Semantic versioning](https://semver.org/), from the library directory:
165
+
166
+ | Change | Command |
167
+ | --- | --- |
168
+ | Bug fix, styling correction, no API change | `npm run release:patch` |
169
+ | New component, new optional prop, new event | `npm run release:minor` |
170
+ | Renamed/removed prop, event or tag; changed payload | `npm run release:major` |
171
+
172
+ Each script bumps the version, then publishes. After a bump, raise the range in `apps/recipe-web/package.json` to match.
173
+
174
+ ---
175
+
176
+ ## How SvelteKit consumes the package
177
+
178
+ The app depends on the library **by package name**, exactly as any external consumer would:
179
+
180
+ ```jsonc
181
+ // apps/recipe-web/package.json
182
+ "dependencies": {
183
+ "recipe-kit-stencil": "^0.1.0"
184
+ }
185
+ ```
186
+
187
+ Nothing is imported from `packages/recipe-kit-stencil/src`. All imports resolve through the package's `exports` map to its built `dist/` and `loader/` output:
188
+
189
+ ```ts
190
+ import 'recipe-kit-stencil/dist/recipe-kit/recipe-kit.css'; // design tokens
191
+ const { defineCustomElements } = await import('recipe-kit-stencil/loader');
192
+ ```
193
+
194
+ Because both directories are npm workspaces, `npm install` satisfies `^0.1.0` from the local package during development — one install, no `npm link`, no manual rebuild step. Outside this repository the identical range resolves from the registry.
195
+
196
+ To prove the registry path end-to-end after publishing:
197
+
198
+ ```bash
199
+ cd /tmp && mkdir kit-check && cd kit-check && npm init -y
200
+ npm install recipe-kit-stencil --registry=https://registry.npmjs.org/
201
+ ls node_modules/recipe-kit-stencil/loader
202
+ ```
203
+
204
+ ### Passing data down: properties, not attributes
205
+
206
+ Attributes can only carry strings, so array props such as `rk-segmented-control.options` must be assigned as DOM properties. `src/lib/stencil/WebComponent.svelte` is a small bridge that handles this for the whole app:
207
+
208
+ ```svelte
209
+ <WebComponent
210
+ tag="rk-recipe-card"
211
+ props={{ recipeId: recipe.id, name: recipe.name, favorite: isFavorite }}
212
+ events={{ rkFavoriteToggle: handleToggle }}
213
+ >
214
+ <rk-badge tone="accent" small>30 min</rk-badge>
215
+ {#snippet actions()}
216
+ <button slot="actions" class="btn btn-sm">Add to plan</button>
217
+ {/snippet}
218
+ </WebComponent>
219
+ ```
220
+
221
+ It does three things that matter:
222
+
223
+ 1. **Defers property writes until the element upgrades.** Assigning a property before `customElements.define` runs would shadow the generated accessor, so writes wait on `customElements.whenDefined(tag)`.
224
+ 2. **Registers listeners with `addEventListener`.** The library emits `rkFavoriteToggle`, not `rkfavoritetoggle`; `addEventListener` preserves case exactly, keeping the app's contract identical to the library's documented one.
225
+ 3. **Mirrors props as kebab-case attributes for SSR.** Property assignment cannot happen on the server, which would leave every card an empty shell until hydration. Stencil observes attributes, so elements render with correct content in the server response — before hydration, and with JavaScript disabled.
226
+
227
+ ### Handling events coming back up
228
+
229
+ Custom events are handled as ordinary app logic. The store is treated as the source of truth rather than the event payload, so a change made in another tab cannot be clobbered:
230
+
231
+ ```ts
232
+ function handleFavoriteToggle(event: CustomEvent<{ favorite: boolean }>) {
233
+ const nowFavorite = favorites.toggle(recipe);
234
+ if (nowFavorite !== event.detail.favorite) return;
235
+ toasts.push(nowFavorite ? `Saved “${recipe.name}”.` : `Removed “${recipe.name}”.`);
236
+ }
237
+ ```
238
+
239
+ ### Slots
240
+
241
+ - **Default slot** — badges under a card title (`rk-recipe-card`), call-to-action buttons (`rk-empty-state`), dialog body (`rk-modal`).
242
+ - **Named slots** — `actions` on `rk-recipe-card` for per-card buttons, `actions` on `rk-search-bar` for the result counter, `footer` on `rk-modal` for every confirmation's button row.
243
+
244
+ Slotted content is passed with Svelte 5 snippets. Any element needing a `slot="…"` attribute must sit inside a `{#snippet}` block, because Svelte 5 treats `slot` on a direct child of a *component* as the deprecated slot syntax.
245
+
246
+ ---
247
+
248
+ ## Architecture
249
+
250
+ ### Data flow
251
+
252
+ ```
253
+ TheMealDB ──► src/lib/api/mealdb.ts ──► +page.ts load ──► page component ──► rk-* elements
254
+ (typed, normalised) (SSR + CSR) │ │
255
+ │ props down │ events up
256
+ localStorage ──► rune stores ─────────────────────────────────┘◄───────────────────┘
257
+ ```
258
+
259
+ **One recipe model.** TheMealDB returns 20 flat `strIngredient1…20` / `strMeasure1…20` column pairs, inconsistent instruction formatting, and comma-joined tags. `src/lib/api/mealdb.ts` normalises all of it into a single `Recipe` type that user-authored recipes also satisfy, so no component branches on data origin — only on whether the user *owns* a recipe.
260
+
261
+ **Filters live in the URL.** Search state is read from `url.searchParams` inside `load`, which means SvelteKit re-runs the fetch on change and no fetch logic is duplicated in the component.
262
+
263
+ **Combined filters via set intersection.** The API only supports one filter dimension per request, so each active filter becomes one `filter.php` call and the resulting id sets are intersected. That is what makes "Seafood + Italian" work.
264
+
265
+ **Stores are classes with explicit persistence.** Stores are created at module scope, outside any component, where `$effect` has no owner — so each mutation calls `save()` rather than relying on an effect. A `storage` listener keeps duplicate tabs in sync.
266
+
267
+ **Referential integrity is owned by the store, not the pages.** Favorites and planner slots hold a `RecipeSummary` snapshot so those views render without a network round-trip per recipe. `myRecipes.update()` and `.remove()` therefore propagate renames and deletions into favorites and every planned slot, so no caller can forget to.
268
+
269
+ ### Validation
270
+
271
+ `src/lib/domain/validation.ts` is pure and Svelte-free, so the same rules could run on a server later. It enforces: required name (3–120 chars), required category and cuisine, valid `http(s)` image URL if given, ≥ 2 ingredients with no case-insensitive duplicates, ≥ 1 instruction step, optional whole-number time and servings within range, ≤ 6 tags, and a notes length cap. Duplicate recipe titles are checked separately, in the page, because that rule depends on store contents rather than input shape.
272
+
273
+ Errors surface only after the first submit attempt, then live on every keystroke, with a count banner and focus moved to the first invalid field.
274
+
275
+ ### Rendering
276
+
277
+ Discover, recipe details and the facet lists are server-rendered. Anything backed by `localStorage` — favorites, your recipes, the planner — is client-only by nature, and each page handles the pre-hydration window explicitly rather than flashing an incorrect empty state.
278
+
279
+ ---
280
+
281
+ ## Deployment
282
+
283
+ `adapter-auto` is the default: on Vercel, Netlify or Cloudflare Pages it installs the right adapter with no configuration.
284
+
285
+ ### Vercel or Netlify (recommended)
286
+
287
+ Import the repository, then set:
288
+
289
+ | Setting | Value |
290
+ | --- | --- |
291
+ | Root directory / base | `apps/recipe-web` |
292
+ | Install command | `npm install` |
293
+ | Build command | `npm run build` |
294
+
295
+ npm workspaces are detected from the repository root, so the install reaches both packages. Once the library is published, the app installs it from the registry and no library build step is needed at all.
296
+
297
+ ### Self-hosted Node
298
+
299
+ ```bash
300
+ npm run build:kit
301
+ ADAPTER=node npm run build --workspace recipe-web
302
+ node apps/recipe-web/build/index.js # PORT and HOST are respected
303
+ ```
304
+
305
+ Verified locally: the Node build boots and serves every route.
306
+
307
+ ---
308
+
309
+ ## Repository
310
+
311
+ ```bash
312
+ git remote add origin https://github.com/<your-username>/recipe-ui.git
313
+ git push -u origin main
314
+ ```
315
+
316
+ Then fill in the repository and live-application links in the table at the top of this file.
317
+
318
+ ---
319
+
320
+ ## Assumptions and decisions
321
+
322
+ **TheMealDB was chosen over Spoonacular or Edamam** because it needs no API key or signup, which keeps the project clone-and-run. Consequences: it has no ratings, no cook times and no nutrition data, and no write endpoints.
323
+
324
+ **No backend, and user data is per-browser.** The assignment scopes the deliverable to the frontend, so favorites, user recipes and meal plans are stored in `localStorage`. They are therefore per-browser and per-device: they do not sync, and clearing site data clears them. A URL to a user-authored recipe only resolves in the browser that created it, which the detail page states explicitly rather than showing a bare 404.
325
+
326
+ **Cook time and servings are only shown when they are real.** Rather than estimating a time for API recipes from instruction length, those fields are simply omitted for catalogue recipes and shown only for user-authored ones. Cards show ingredient count instead, which is genuine data.
327
+
328
+ **The default Discover view is a small featured set.** `search.php` with an empty query returns 25 recipes, not the full catalogue. Rather than firing 14 parallel category requests on every page load, the default view is labelled "Featured recipes" and a category chip rail gives one-tap access to each full category list.
329
+
330
+ **The planner is week-addressable.** Plans are keyed by the Monday of their week, so past and future weeks are all navigable and "repeat last week" is possible, rather than there being a single perpetual week.
331
+
332
+ **Ratings are display-only.** `rk-rating` is fully interactive, but the app never persists a user rating: TheMealDB has no rating data to seed from, and a rating that exists only in one browser would be misleading. The component is in the library for completeness and is exercised in the gallery.
333
+
334
+ **The prep checklist is intentionally not persisted.** Ticking ingredients on the detail page is a cooking aid for the current session, not part of the recipe.
335
+
336
+ **`recipe-kit-stencil` is unscoped.** The name was verified available on npm. Publishing under a scope instead only needs the `name` field changed in the library's `package.json` and the dependency in the app's.
337
+
338
+ **Node ≥ 20** is assumed, matching the SvelteKit 2 and Vite 8 baseline.
339
+
340
+ ---
341
+
342
+ ## Known limitations
343
+
344
+ - **Deploy and publish are not done.** Both need credentials this environment does not have. The npm package is built, packed and verified (`npm pack` produces a valid tarball); the production Node build is verified booting and serving. The two links at the top of this file are the remaining steps.
345
+ - **Browser interaction was not verified end-to-end.** Every route was exercised over HTTP and asserted on its server-rendered output (card counts, filter intersection, planner grid, 404 handling), `svelte-check` reports zero errors, and 16 component spec tests pass. Click-through of the drag-and-drop planner and the create/edit form was not machine-verified.
346
+ - **Stencil's bundled test runner is deprecated** as of Stencil 4 and will be removed in v5. The spec tests will need migrating to `@stencil/vitest`.
347
+ - **Ingredient filtering of your own recipes is a substring match**, while the API's ingredient filter is an exact match on its own ingredient vocabulary. Results from the two sources can differ in strictness for the same query.
348
+ - **No pagination against the API.** TheMealDB returns whole result sets, so paging is client-side over the full list.
349
+
350
+ ---
351
+
352
+ ## License
353
+
354
+ MIT
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "recipe-web",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "description": "Recipe Finder & Meal Planner — SvelteKit 5 application.",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "vite dev",
9
+ "build": "vite build",
10
+ "build:node": "ADAPTER=node vite build",
11
+ "preview": "vite preview",
12
+ "preview:node": "ADAPTER=node vite build && node build/index.js",
13
+ "start": "node build/index.js",
14
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
15
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
16
+ "prepare": "svelte-kit sync"
17
+ },
18
+ "dependencies": {
19
+ "recipe-kit-stencil": "^0.1.0"
20
+ },
21
+ "devDependencies": {
22
+ "@sveltejs/adapter-auto": "^7.0.1",
23
+ "@sveltejs/adapter-node": "^5.5.7",
24
+ "@sveltejs/kit": "^2.70.3",
25
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
26
+ "svelte": "^5.56.10",
27
+ "svelte-check": "^4.7.6",
28
+ "typescript": "^5.9.3",
29
+ "vite": "^8.2.2"
30
+ }
31
+ }