pantrybase 0.1.0 → 0.3.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.
- package/.claude-plugin/plugin.json +8 -2
- package/README.md +4 -0
- package/dist/{chunk-2EMC6TJB.js → chunk-IUQQ3I2E.js} +605 -8
- package/dist/cli.js +1 -1
- package/dist/server.js +239 -61
- package/dist/web/assets/{graph-Mpnwdw0-.js → graph-70CI6xc-.js} +1 -1
- package/dist/web/assets/{index-PLKhwbDz.css → index--7z6Fb_H.css} +1 -1
- package/dist/web/assets/index-PBzjTL7i.js +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/providers/generic/profile.yaml +1 -0
- package/providers/ucook/guide.md +1 -1
- package/providers/ucook/profile.yaml +2 -0
- package/skills/cook/SKILL.md +9 -1
- package/skills/ingest/SKILL.md +6 -5
- package/dist/web/assets/index-BQVf10Ok.js +0 -1
package/dist/web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Pantrybase</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='8' fill='%23c2410c'/><path d='M9 22V10h7a4 4 0 0 1 0 8h-7' stroke='white' stroke-width='3' fill='none' stroke-linecap='round'/></svg>" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-PBzjTL7i.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index--7z6Fb_H.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantrybase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Local-first recipe knowledge base for Claude Code: capture meal-box recipe cards from photos, search by ingredient, tag, allergen or mood, match recipes to your pantry, explore them as a graph, and synthesize new ones.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/providers/ucook/guide.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# UCOOK card extraction guide
|
|
2
2
|
|
|
3
|
-
UCOOK (South Africa) cards are landscape, two-sided. Read every photo of a card before saving. Save with `provider: {id: "ucook", …}` and `source: {kind: "card"}`.
|
|
3
|
+
UCOOK (South Africa) cards are landscape, two-sided. Read every photo of a card before saving. A card photo that is taller than it is wide, or whose text runs sideways or upside down, needs `pb_inbox_rotate` (clockwise degrees) before you read it. Save with `provider: {id: "ucook", …}` and `source: {kind: "card"}`.
|
|
4
4
|
|
|
5
5
|
## Front (photo left, text panel right)
|
|
6
6
|
|
|
@@ -9,6 +9,8 @@ sides:
|
|
|
9
9
|
back: [Ingredients & Prep, From Your Kitchen, Nutritional Information, Allergens, Eat Within]
|
|
10
10
|
# Photos are usually shot front then back; the back carries no title, so pair by order and content.
|
|
11
11
|
pairing: front-then-back
|
|
12
|
+
# Upright cards are wider than tall; a portrait photo of one is almost certainly sideways.
|
|
13
|
+
orientation: landscape
|
|
12
14
|
nutrition_basis: per_100g
|
|
13
15
|
default_servings: 2
|
|
14
16
|
# Heading of the card section listing what you supply yourself (ingredients get source: kitchen).
|
package/skills/cook/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cook
|
|
3
|
-
description: Answer "what can I cook?" from the pantrybase recipe collection and the user's pantry, find recipes by ingredient, tag, cuisine, allergen or mood, keep the pantry up to date, and invent new recipes grounded in the collection. Use when the user asks for meal ideas, what to make with what they have, recipes matching criteria, a new recipe in the style of their collection, or to update their pantry.
|
|
3
|
+
description: Answer "what can I cook?" from the pantrybase recipe collection and the user's pantry, find recipes by ingredient, tag, cuisine, allergen or mood, plan several meals around shared ingredients with one grocery list, keep the pantry up to date, and invent new recipes grounded in the collection. Use when the user asks for meal ideas, what to make with what they have, recipes matching criteria, a meal plan or shopping list, a new recipe in the style of their collection, or to update their pantry.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Cook from the collection
|
|
@@ -19,6 +19,14 @@ description: Answer "what can I cook?" from the pantrybase recipe collection and
|
|
|
19
19
|
- Criteria → `pb_search` with `tags` (assigned: cuisine, diet, dish, protein, starch, flavor, texture, method, meal, time, season; derived: allergen, equipment, technique, chef, provider, plan). Parents expand: `cuisine: [asian]` includes thai and korean.
|
|
20
20
|
- `pb_list_tags` shows which values exist.
|
|
21
21
|
|
|
22
|
+
## Plan meals around shared ingredients
|
|
23
|
+
Goal: a few dinners that share their shopping, then one grocery list. Keep the pantry honest along the way.
|
|
24
|
+
1. **Start.** The user names a recipe ("plan around the harissa chicken") → `pb_plan_suggest {seed}`; "plan me 4 dinners" with no favourite → `pb_pantry_match` or `pb_search` to find a starting point they like, then seed with it. Pass constraints as filters (`tags`, `exclude_allergens`, `max_total_min`).
|
|
25
|
+
2. **Suggest.** Each suggestion shows `shares` (already on the list for a planned recipe), `from_pantry`, `new` (what it adds) and `reuse` (share already covered). Lead with the top 3–4, say in a phrase *why* each fits ("shares the peppers and coriander, adds only feta"), and flag `repeats` (same dish again). For a full week at once, `pb_plan_suggest {seed, auto: n}` picks the set that minimises the whole list — show it and let the user swap items.
|
|
26
|
+
3. **Save.** When the user picks, `pb_plan {action: "add" | "set", slugs}`; `servings` scales quantities. The plan is shared with the web UI (`/plan`), so they can carry on there.
|
|
27
|
+
4. **Confirm the pantry before the list.** `pb_plan_shopping_list` returns `pantry_check`: the pantry items the plan counts on, with the amount needed and what the pantry says it has. Ask the user to confirm those are really there — one short question listing them — then call again with `confirmed` for the ones they have and `out_of` for the ones they don't (`out_of` leaves the pantry and moves onto the list). Skip the question only when `needs_confirmation` is already empty.
|
|
28
|
+
5. **Hand over the list.** Present `buy` grouped by `aisle` with amounts and which recipes use each item, mention the staples to check, and offer `format: "text"` for a paste-ready version. Don't add staples to the list.
|
|
29
|
+
|
|
22
30
|
## Invent a new recipe
|
|
23
31
|
1. `pb_synthesis_context` with a `query` for what the user wants, `use_pantry: true` if it should use what they have, and any filters. Read the source recipes, shared ingredients, pairings and techniques.
|
|
24
32
|
2. Write a genuinely new recipe: combine techniques and flavour patterns from the sources, prefer pantry ingredients, keep quantities realistic for the servings, and respect any diet or allergen constraints.
|
package/skills/ingest/SKILL.md
CHANGED
|
@@ -21,17 +21,18 @@ You turn photos in `pantrybase/inbox/` into recipes. The queue is on disk, so in
|
|
|
21
21
|
## 3. Loop, one card at a time
|
|
22
22
|
|
|
23
23
|
1. `pb_inbox_next {window: 4}` gives the next photos in capture order.
|
|
24
|
-
2.
|
|
24
|
+
2. If a photo has a `rotation_hint`, or shows up sideways or upside down, turn it upright first with `pb_inbox_rotate {id, degrees}` (clockwise: text reading bottom-to-top → 90, upside down → 180, top-to-bottom → 270) and view it again. Never transcribe a sideways photo.
|
|
25
|
+
3. View them in order with `pb_inbox_image`. Group the photos that belong to one card: usually a front (title, photo) followed by its back (ingredients, steps, nutrition — no title). Match a back to its front by order and by ingredients that echo the front's subtitle and description.
|
|
25
26
|
- `retake_of` means a near-identical photo of the same side: use the clearer one as the main image and attach the other with the same side, or skip it.
|
|
26
27
|
- If small print is unreadable, request `pb_inbox_image {id, max_px: 2400}` before giving up on a field.
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
4. Extract everything the guide asks for and call `pb_save_recipe` with `provider`, `source: {kind: "card"}`, the full structured record, your own `blurb`, and `ingest: [{id, side}]` listing **every** photo of the card.
|
|
29
|
+
5. Handle the response:
|
|
29
30
|
- `saved: true` — note the slug and move on. Glance at `vocabulary.rejected` and `unmatched_uses`; fix only clear mistakes (re-save with `slug`).
|
|
30
31
|
- `duplicate_of` — compare with that recipe (`pb_get_recipe`). Same card → attach the photos to it (`pb_save_recipe` with its `slug`, the same data and `ingest`) or `pb_inbox_resolve {status: "skipped", reason: "duplicate of <slug>"}`. Genuinely different → re-save with `allow_duplicate: true`.
|
|
31
32
|
- `already_ingested` — the photos were saved before; continue.
|
|
32
33
|
- `error` — read it; usually a photo id is wrong or already attached elsewhere.
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
6. Photos that aren't recipe cards → `skipped` with a reason. Photos you cannot read at all → `failed` with what's wrong ("back too blurry to read steps") so the user can re-shoot them.
|
|
35
|
+
7. A front whose back is missing: save what the front gives with `extraction.needs_review: true` and list the missing parts in `extraction.uncertain`. Don't hold up the batch waiting for it.
|
|
35
36
|
|
|
36
37
|
## Rules
|
|
37
38
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e,t,n,r,i,a,o,s,c,l,u,d={},f=[],p=/^m(i|n|o|s|text|space)$/,m=Array.isArray,h=f.slice,g=Object.assign;function _(e){e&&e.parentNode&&e.remove()}function v(e,t,n){var r,i,a,o={},s=arguments.length;for(a in t)a==`key`?r=t[a]:a==`ref`&&typeof e!=`function`?i=t[a]:o[a]=t[a];return s>2&&(o.children=s>3?h.call(arguments,2):n),b(e,o,r,i,null)}function y(e,t,n){var r,i,a,o=g({},e.props),s=arguments.length;for(a in t)a==`key`?r=t[a]:a==`ref`&&typeof e.type!=`function`?i=t[a]:o[a]=t[a];return s>2&&(o.children=s>3?h.call(arguments,2):n),b(e.type,o,r===void 0?e.key:r,i===void 0?e.ref:i,null)}function b(n,r,i,a,o){var s={type:n,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o||++t,__i:-1,__u:0};return!o&&e.vnode&&e.vnode(s),s}function x(e){return e.children}function S(e,t){this.props=e,this.context=t,this.__g=0}function C(e,t){if(t==null)return e.__?C(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])&&n.__e)return n.__e;return typeof e.type!=`function`||e.props.__P?null:C(e)}function w(e){if((e=e.__)&&e.__c&&!e.props.__P)return e.__e=null,e.__k.some(function(t){return t&&(e.__e=t.__e)}),w(e)}function T(t){(8&t.__g||!(t.__g|=8)||!n.push(t)||i++)&&r==e.debounceRendering||((r=e.debounceRendering)||queueMicrotask)(E)}function E(){var t,r,o,s,c,l,u,d,f;try{for(r=1;n.length;)n.length>r&&n.sort(a),t=n.shift(),r=n.length,8&t.__g&&(s=void 0,c=void 0,l=(c=(o=t).__v).__e,u=[],d=[],(f=o.__P)&&((s=g({constructor:void 0},c)).__v=c.__v+1,e.vnode&&e.vnode(s),N(f,s,c,o.__n,f.namespaceURI,32&c.__u?[l]:null,u,l||C(c),32&c.__u,d),s.__v=c.__v,s.__.__k[s.__i]=s,P(u,s,d),c.__=c.__e=null,s.__e!=l&&w(s)))}finally{n.length=i=0}}function ee(e,t,n,r,i,a,o,s,c,l,u){var p,m,h,g,_,v,y=r.__k||f,b=t.length;for(c=D(n,t,y,c,b),p=0;p<b;p++)(h=n.__k[p])!=null&&(m=~h.__i&&y[h.__i]||d,h.__i=p,v=N(e,h,m,i,a,o,s,c,l,u),g=h.__e,(m.ref!=h.ref||8&m.__u)&&(m.ref&&F(m.ref,null,h,m),h.ref&&u.push(h.ref,h.__c||g,h)),_||=g,4&h.__u?(c=O(h,c,e,!m.__v),m.__e&&(m.__e=null)):typeof h.type==`function`&&v!==void 0?c=v:g&&(c=g.nextSibling),h.__u&=-7);return n.__e=_,c}function D(e,t,n,r,i){var a,o,s,c,l,u,d,f,p,h,g=n.length,_=g,v=0,y=!1,S=e.__k=Array(i);for(a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o!=`object`||o.constructor==String?o=S[a]=b(null,o):m(o)?o=S[a]=b(x,{children:o}):o.constructor===void 0&&o.__b?o=S[a]=b(o.type,o.props,o.key,o.ref,o.__v):S[a]=o,c=a+v,o.__=e,o.__b=e.__b+1,s=null,~(l=o.__i=A(o,n,c,_))&&(_--,(s=n[l])&&(s.__u|=2)),s&&s.__v?(o.__u|=2,l==c-1?v--:l==c+1?v++:l!=c&&(l>c?v--:v++,y=!0)):(~l||(i>g?v--:i<g&&v++),typeof o.type!=`function`&&(o.__u|=4))):S[a]=null;if(y){for(u=[],d=[],a=0;a<i;a++)if((o=S[a])&&2&o.__u){for(f=0,p=u.length;f<p;)u[h=f+p>>1]<o.__i?f=h+1:p=h;u[f]=o.__i,d[a]=f+1}for(v=u.length;a--;)d[a]&&(d[a]==v?v--:S[a].__u|=4)}if(_)for(a=0;a<g;a++)!(s=n[a])||2&s.__u||(s.__e==r&&(r=C(s)),ie(s,s));return r}function O(e,t,n,r){var i,a,o;if(typeof e.type==`function`){if(e.props.__P)return t;if(i=e.__k)for(a=0;a<i.length;a++)i[a]&&(i[a].__=e,t=O(i[a],t,n,!1));return t}for(t&&!t.parentNode&&(t=C(e))&&!t.parentNode&&(t=null),o=t;o&&o.nodeType==8;)o=o.nextSibling;for(e.__e!=o&&(!r&&n.moveBefore&&e.__e.parentNode?n.moveBefore(e.__e,t):n.insertBefore(e.__e,t||null)),t=e.__e;(t&&=t.nextSibling)&&t.nodeType==8;);return t}function k(e,t){return t||=[],e!=null&&typeof e!=`boolean`&&(m(e)?e.some(function(e){k(e,t)}):t.push(e)),t}function A(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l&&!(2&l.__u);if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function j(e,t,n){n??=``,t[0]==`-`?e.setProperty(t,n):e[t]=n}function M(e,t,n,r,i){var a,o;n:if(t==`style`){if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||j(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||j(e.style,t,n[t])}}else if(t[0]==`o`&&t[1]==`n`)(e.__e||={})[t]=n,n&&r||(o=l[t]||(l[t]=te(t)),(e.__a||={})[t]=c,a=t!=(t=t.replace(s,`$1`)),(t=t.slice(2))[0]<`a`&&(t=t.toLowerCase()),n?e.addEventListener(t,o,a):e.removeEventListener(t,o,a));else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function te(t){return function(n){if(this.__e){var r=this.__e[t];if(n[o]==null)n[o]=c++;else if(n[o]<this.__a[t])return;return r(e.event?e.event(n):n)}}}function N(t,n,r,i,a,o,s,c,l,u){var d,p,h,v,y,b,w,T,E,D,O,k,A,j,M,te,N,P,F,I,L=n.type;if(n.constructor!==void 0)return null;if(128&r.__u&&(l=32&r.__u,d=r.__c.__z)){if(n.__u|=l,p=o=[],d.nodeType==8)for(h=1,v=d.nextSibling;v;v=v.nextSibling){if(v.nodeType==8){if(v.data.startsWith(`$s`))h++;else if(v.data.startsWith(`/$s`)&&!--h)break}o.push(v)}else o.push(d);c=o[0]}(d=e.__b)&&d(n);n:if(typeof L==`function`){y=s.length;try{if(D=n.props,O=(d=L.prototype)&&d.render,k=(d=L.contextType)&&i[d.__c],A=d?k?k.props.value:d.__:i,r.__c?2&(b=n.__c=r.__c).__g&&(b.__g|=1):(O?n.__c=b=new L(D,A):(n.__c=b=new S(D,A),b.constructor=L,b.render=ae),k&&k.sub(b),b.state||(b.state={}),b.__n=i,b.__g|=8,b.__h=[],b.__k=[]),O&&(b.__s||(b.__s=b.state),L.getDerivedStateFromProps&&(b.__s==b.state&&(b.__s=g({},b.__s)),g(b.__s,L.getDerivedStateFromProps(D,b.__s)))),w=b.props,T=b.state,b.__v=n,r.__c){if(O&&!L.getDerivedStateFromProps&&D!==w&&b.componentWillReceiveProps&&b.componentWillReceiveProps(D,A),n.__v==r.__v&&!(8&b.__g)||!(4&b.__g)&&b.shouldComponentUpdate&&!1===b.shouldComponentUpdate(D,b.__s,A)){n.__v!=r.__v&&(b.props=D,b.state=b.__s,b.__g&=-9),n.__e=r.__e,n.__k=r.__k,n.__k.some(function(e){e&&(e.__=n)}),f.push.apply(b.__h,b.__k),b.__k=[],b.__h.length&&s.push(b),c=C(r);break n}b.componentWillUpdate&&b.componentWillUpdate(D,b.__s,A),O&&b.componentDidUpdate&&b.__h.push(function(){b.componentDidUpdate(w,T,E)})}else O&&!L.getDerivedStateFromProps&&b.componentWillMount&&b.componentWillMount(),O&&b.componentDidMount&&b.__h.push(b.componentDidMount);if(b.context=A,b.props=D,b.__P=t,b.__g&=-5,j=e.__r,M=0,O)b.state=b.__s,b.__g&=-9,j&&j(n),d=b.render(b.props,b.state,b.context),f.push.apply(b.__h,b.__k),b.__k=[];else do b.__g&=-9,j&&j(n),d=b.render(b.props,b.state,b.context),b.state=b.__s;while(8&b.__g&&++M<25);b.state=b.__s,b.getChildContext&&(i=g({},i,b.getChildContext())),O&&r.__c&&b.getSnapshotBeforeUpdate&&(E=b.getSnapshotBeforeUpdate(w,T)),te=d&&d.type===x&&d.key==null?d.props.children:d,D.__P&&(d=c,a=(t=D.__P).namespaceURI,l=o=null,r.props&&r.props.__P!=t&&(r.__k.some(function(e){e&&ie(e,e)}),r.__k=null),c=r.__k?C(r,0):null),c=ee(t,m(te)?te:[te],n,r,i,a,o,s,c,l,u),D.__P&&(n.__e=null,c=d),n.__u&=-161,128&r.__u&&(b.__z=null),p&&p.some(_),b.__h.length&&s.push(b),1&b.__g&&(b.__g&=-4)}catch(t){if(s.length=y,n.__v=null,l||o){if(t.then){if(N=0,n.__u|=l?160:128,o){for(~(F=o.indexOf(c||void 0))||(F=o.length);(I=o[F-1])&&I.nodeType==8;)F--;for(;F<o.length;F++)if(I=o[F]){if(o[F]=null,I.nodeType==8){if(I.data.startsWith(`$s`))N++||(P=I);else if(N&&I.data.startsWith(`/$s`)&&!--N){c=I;break}}else if(!N)break}}if(!P){for(;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;P=c}n.__c.__z||(n.__c.__z=P),n.__e=c}else o&&o.some(_)}else n.__e=r.__e;n.__k||=r.__k||[],t.then||ne(n),e.__e(t,n,r)}}else c=n.__e=re(r.__e,n,r,i,a,o,s,l,u,t);return(d=e.diffed)&&d(n),128&n.__u?void 0:c}function ne(e){e&&(e.__c&&(e.__c.__g|=4),e.__k&&e.__k.some(ne))}function P(t,n,r){for(var i=0;i<r.length;)F(r[i++],r[i++],r[i++]);e.__c&&e.__c(n,t),t.some(function(n){try{t=n.__h,n.__h=[],t.some(function(e){e.call(n)})}catch(t){e.__e(t,n.__v)}})}function re(t,n,r,i,a,o,s,c,l,u){var f,g,v,y,b,x,S,w,T,E=r.props||d,D=n.props,O=n.type;if(O==`svg`?a=`http://www.w3.org/2000/svg`:O==`math`?a=`http://www.w3.org/1998/Math/MathML`:a||=`http://www.w3.org/1999/xhtml`,o){for(f=0;f<o.length;f++)if((b=o[f])&&(O?b.localName==O:b.nodeType==3)){t=b,o[f]=null;break}}if(!t){if(w=u.ownerDocument||document,!O)return w.createTextNode(D);t=w.createElementNS(a,O,D.is&&D),c&&=(e.__m&&e.__m(n,o),!1),o=null}if(O){if(u=O==`template`?t.content:t,o=O==`textarea`&&D.defaultValue!=null?null:o&&h.call(u.childNodes),!c&&o)for(E={},f=0;f<t.attributes.length;f++)E[(b=t.attributes[f]).name]=b.value;for(f in E)b=E[f],f==`dangerouslySetInnerHTML`?v=b:f==`children`||f in D||f==`value`&&`defaultValue`in D||f==`checked`&&`defaultChecked`in D||M(t,f,null,b,a);for(f in T=1&r.__u,D)b=D[f],f==`children`?y=b:f==`dangerouslySetInnerHTML`?g=b:f==`value`?x=b:f==`checked`?S=b:c&&typeof b!=`function`||!(E[f]!==b||T&&b!=null)||M(t,f,b,E[f],a);g?(c||v&&(g.__html==v.__html||g.__html==t.innerHTML)||(t.innerHTML=g.__html),n.__k=[]):(v&&(t.textContent=``),(O==`foreignObject`||a==`http://www.w3.org/1998/Math/MathML`&&p.test(O))&&(a=`http://www.w3.org/1999/xhtml`),ee(u,m(y)?y:[y],n,r,i,a,o,s,o?o[0]:r.__k&&C(r,0),c,l),o&&o.some(_)),c&&O!=`textarea`||(f=`value`,O==`progress`&&x==null?t.removeAttribute(f):x==null||x===t[f]&&(O!=`progress`||x)||M(t,f,x,E[f],a),f=`checked`,S!=null&&S!=t[f]&&M(t,f,S,E[f],a))}else E===D||c&&t.data==D||(t.data=D);return t}function F(t,n,r,i){try{typeof t==`function`?n?n.__x=t(n)||1:(i&&=i.__c||i.__e)&&(n=i.__x)&&(i.__x=null,typeof n==`function`?n():t(null)):t.current=n}catch(t){e.__e(t,r)}}function ie(t,n,r){var i,a;if(e.unmount&&e.unmount(t),!(i=t.ref)||i.current&&i.current!=t.__e||F(i,null,n,t),i=t.__c){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(t){e.__e(t,n)}i.__P=i.__n=null}if(i=t.__k)for(a=0;a<i.length;a++)i[a]&&ie(i[a],n,typeof t.type!=`function`||r&&!t.props.__P);(i=t.__e)&&(r||_(i),i.__e&&(i.__e=null)),t.__e=t.__c=t.__=null}function ae(e,t,n){return this.constructor(e,n)}function I(t,n){var r,i,a,o;e.__&&e.__(t,n),n.nodeType==9&&(n=n.documentElement),i=(r=t&&32&t.__u)?null:n.__k,n.__k=b(x,{children:[t]}),a=[],o=[],N(n,n.__k,i||d,d,n.namespaceURI,i?null:n.firstChild?h.call(n.childNodes):null,a,i?i.__e:n.firstChild,r,o),P(a,n.__k,o),n.__k.props.children=null}function L(e){function t(e){var n,r;return this.getChildContext||(n=new Set,(r={})[t.__c]=this,this.getChildContext=function(){return r},this.shouldComponentUpdate=function(e){this.props.value!=e.value&&n.forEach(function(e){e.__g|=4,T(e)})},this.sub=function(e){n.add(e);var t=e.componentWillUnmount;e.componentWillUnmount=function(){n.delete(e),t&&t.call(e)}}),e.children}return t.__c=`__cC`+u++,t.__=e,t.Provider=(t.Consumer=function(e,t){return e.children(t)}).contextType=t,t}e={__e:function(e,t,n,r){for(var a,o,s;t=t.__;)if((a=t.__c)&&!(1&a.__g)){a.__g|=4;try{if((o=a.constructor)&&o.getDerivedStateFromError&&(a.setState(o.getDerivedStateFromError(e)),s=8&a.__g),a.componentDidCatch&&(a.componentDidCatch(e,r||{}),s=8&a.__g),s)return void(a.__g|=2)}catch(t){e=t,s=0}}throw i=0,e}},t=0,S.prototype.setState=function(e,t){var n=this.__s;n&&n!=this.state||(n=this.__s=g({},this.state)),typeof e==`function`&&(e=e(g({},n),this.props)),e&&(g(n,e),this.__v&&(t&&this.__k.push(t),T(this)))},S.prototype.forceUpdate=function(e){this.__v&&(this.__g|=4,e&&this.__h.push(e),T(this))},S.prototype.render=x,n=[],i=0,a=function(e,t){return e.__v.__b-t.__v.__b},o=Symbol(),s=/(PointerCapture)$|Capture$/i,c=0,l={},u=0;var R,z,oe,se,ce=Object.is,le=0,ue=[],de=[],B=e,fe=B.__b,pe=B.__r,me=B.diffed,he=B.__c,ge=B.unmount,_e=B.__;function ve(e,t){B.__h&&B.__h(z,e,le||t),le=0;var n=z.__H||(z.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function V(e){return le=1,ye(je,e)}function ye(e,t,n){var r=ve(R++,2);if(r.t=e,!r.__c&&(r.__=[n?n(t):je(void 0,t),function(e){var t=r.__N?r.__N[0]:r.__[0],n=r.t(t,e);ce(t,n)||(r.__N=[n,r.__[1]],r.__c.setState({}))}],r.__c=z,!z.__f)){z.__f=!0;var i=z.shouldComponentUpdate;z.shouldComponentUpdate=function(e,t,n){var r=this.__H;if(!r)return!0;var a=!1,o=this.props!=e;if(r.__.some(function(e){e.__N&&(a=!0,ce(e.__[0],e.__N[0])||(o=!0))}),i){var s=i.call(this,e,t,n);return a?s||o:s}return!a||o}}return r.__}function be(e,t){var n=ve(R++,3);!B.__s&&Ae(n.__H,t)&&(n.__P=!0,n.__=e,n.u=t,z.__H.__h.push(n))}function xe(e,t){var n=ve(R++,4);!B.__s&&Ae(n.__H,t)&&(n.__P=!1,n.__=e,n.u=t,z.__h.push(n))}function H(e){return le=5,Se(function(){return{current:e}},[])}function Se(e,t){var n=ve(R++,7);return Ae(n.__H,t)&&(n.__=e(),n.__H=t),n.__}function Ce(e,t){return le=8,Se(function(){return e},t)}function we(e){var t=z.context[e.__c],n=ve(R++,9);return n.c=e,t?(n.__??(n.__=!0,t.sub(z)),t.props.value):e.__}function Te(){var e;do{for(;e=de.shift();)try{U(e)}catch(t){B.__e(t,{__:(e=e.__P)&&e.__v})}for(;e=ue.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(U),t.__h.some(ke),t.__h=[]}catch(n){t.__h=[],B.__e(n,e.__v)}}}while(de.length)}B.__b=function(e){z=null,fe&&fe(e)},B.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),_e&&_e(e,t)},B.__r=function(e){pe&&pe(e),R=0;var t=(z=e.__c).__H;t&&(oe==z?z.__h=[]:(t.__h.some(U),t.__h.some(ke),R=0),t.__h=[],t.__.some(function(e){e.__N&&(e.__=e.__N),e.u=e.__N=void 0})),oe=z},B.diffed=function(e){me&&me(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&Oe(ue.push(t)),t.__H.__.some(function(e){e.u&&(e.__H=e.u)})),oe=z=null},B.__c=function(e,t){t.some(function(e){try{e.__h.some(U),e.__h=e.__h.filter(function(e){return!e.__||ke(e)})}catch(n){t.some(function(e){e.__h&&=[]}),t=[],B.__e(n,e.__v)}}),he&&he(e,t)},B.unmount=function(e){ge&&ge(e);var t,n,r=e.__c;r&&r.__H&&(r.__H.__.some(function(r){try{if(r.__P&&r.__c){if(n===void 0){for(n=e.__;n&&(!n.__c||!n.__c.__P);)n=n.__;n&&=n.__c}r.__P=n,Oe(de.push(r))}else U(r)}catch(e){t=e}}),r.__H=void 0,t&&B.__e(t,r.__v))};var Ee=typeof requestAnimationFrame==`function`;function De(e){var t,n=function(){clearTimeout(r),Ee&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,35);Ee&&(t=requestAnimationFrame(n))}function Oe(e){e!=1&&se==B.requestAnimationFrame||((se=B.requestAnimationFrame)||De)(Te)}function U(e){var t=z,n=e.__c;typeof n==`function`&&(e.__c=void 0,n()),z=t}function ke(e){var t=z;e.__c=e.__(),z=t}function Ae(e,t){return!e||e.length!=t.length||t.some(function(t,n){return!ce(t,e[n])})}function je(e,t){return typeof t==`function`?t(e):t}var W,Me;function Ne(e){return!Me||(typeof Me==`string`?e.startsWith(Me):Me.test(e))}function Pe(e,t){let n=``;if(W=void 0,t&&t.type===`click`){if(t.ctrlKey||t.metaKey||t.altKey||t.shiftKey||t.button!==0)return e;let r=t.composedPath().find(e=>e.nodeName==`A`&&e.href),i=r&&r.getAttribute(`href`);if(!r||r.origin!=location.origin||/^#/.test(i)||!/^(_?self)?$/i.test(r.target)||!Ne(i)||r.download)return e;W=!0,t.preventDefault(),n=r.href.replace(location.origin,``)}else t&&t.url?(W=!t.replace,n=t.url):n=location.pathname+location.search;return W===!0?history.pushState(null,``,n):W===!1&&history.replaceState(null,``,n),n}var Fe=(e,t,n={})=>{e=e.split(`/`).filter(Boolean),t=(t||``).split(`/`).filter(Boolean),n.params||={};for(let r=0,i,a;r<Math.max(e.length,t.length);r++){let[,o,s,c]=(t[r]||``).match(/^(:?)(.*?)([+*?]?)$/);if(i=e[r],o||s!=i){if(!o&&i&&c==`*`){n.rest=`/`+e.slice(r).join(`/`);break}if(!o||!i&&c!=`?`&&c!=`*`)return;if(a=c==`+`||c==`*`,a?i=e.slice(r).map(decodeURIComponent).join(`/`)||void 0:i&&=decodeURIComponent(i),n.params[s]=i,s in n||(n[s]=i),a)break}}return n};function Ie(e){let[t,n]=ye(Pe,e.url||location.pathname+location.search);e.scope&&(Me=e.scope);let r=W===!0,i=Se(()=>{let e=new URL(t,location.origin),i=e.pathname.replace(/\/+$/g,``)||`/`;return{url:t,path:i,query:Object.fromEntries(e.searchParams),route:(e,t)=>n({url:e,replace:t}),back:()=>{history.back()},forward:()=>{history.forward()},wasPush:r}},[t]);return xe(()=>(addEventListener(`click`,n),addEventListener(`popstate`,n),()=>{removeEventListener(`click`,n),removeEventListener(`popstate`,n)}),[]),v(Ie.ctx.Provider,{value:i},e.children)}var Le=Promise.resolve();function Re(e){let[t,n]=ye(e=>e+1,0),{url:r,query:i,wasPush:a,path:o}=Ue();if(!r)throw Error(`preact-iso's <Router> must be used within a <LocationProvider>, see: https://github.com/preactjs/preact-iso#locationprovider`);let{rest:s=o,params:c={}}=we(He),l=H(!1),u=H(o),d=H(1),f=H(),p=H(),m=H(),h=H(!1),g=H();g.current=!1;let _,b,x;k(e.children).some(e=>{if(Fe(s,e.props.path,x={...e.props,path:s,query:i,params:Object.assign({},c),rest:``}))return _=y(e,x);e.props.default&&(b=y(e,x))});let S=_||b,C=f.current&&f.current.__u&ze&&f.current.__u&Be,w=f.current&&f.current.__h,T=Se(()=>{p.current=f.current,f.current=v(He.Provider,{value:x},S);let e=p.current&&p.current.props.children;return!e||!S||S.type!==e.type||S.props.component!==e.props.component?(d.current++,!0):!1},[r,JSON.stringify(x)]);C?(f.current.__u|=ze,f.current.__u|=Be):w&&(f.current.__h=!0);let E=p.current;return p.current=null,this.__c=(t,i)=>{g.current=!0,p.current=E,e.onLoadStart&&e.onLoadStart(r),l.current=!0;let a=d.current;t.then(()=>{a===d.current&&(p.current=null,f.current&&(i.__h&&(f.current.__h=i.__h),i.__u&Be&&(f.current.__u|=Be),i.__u&ze&&(f.current.__u|=ze)),Le.then(n))})},xe(()=>{let t=this.__v&&this.__v.__e;g.current?!h.current&&!m.current&&(m.current=t):(!h.current&&m.current&&(m.current!==t&&m.current.remove(),m.current=null),h.current=!0,u.current!==o&&(a&&scrollTo(0,0),e.onRouteChange&&e.onRouteChange(r),u.current=o),e.onLoadEnd&&l.current&&e.onLoadEnd(r),l.current=!1)},[o,a,t]),T?[v(Ve,{r:f,key:d.current}),v(Ve,{r:p,key:d.current-1})]:v(Ve,{r:f,key:d.current})}var ze=32,Be=128,Ve=({r:e})=>e.current;Re.Provider=Ie,Ie.ctx=L({});var He=L({}),G=e=>v(e.component,e),Ue=()=>we(Ie.ctx),We=()=>we(He),Ge=e.__b;e.__b=e=>{e.type&&e.type._forwarded&&e.ref&&(e.props.ref=e.ref,e.ref=null),Ge&&Ge(e)};function Ke(e){let t,n,r=()=>e().then(e=>n=e&&e.default||e),i=e=>{let[,i]=V(0),a=H(n);if(t||=r(),n!==void 0)return v(n,e);throw a.current||=t.then(()=>i(1)),t};return i.preload=()=>(t||=r(),t),i._forwarded=!0,i}var qe=e.__e;e.__e=(e,t,n)=>{if(e&&e.then){let r=t;for(;r=r.__;)if(r.__c&&r.__c.__c)return t.__e??(t.__c.__z=[n.__e],t.__e=n.__e,t.__k=n.__k),t.__k||=[],r.__c.__c(e,t)}qe&&qe(e,t,n)};var Je=`modulepreload`,Ye=function(e){return`/`+e},Xe={},Ze=function(e){return e.pathname.endsWith(`.css`)},Qe=function(e,t,n){if(t in e)return e[t];let r=n();if(!r){e[t]=void 0;return}let i=r.then(()=>{e[t]=void 0},n=>{throw e[t]=void 0,n});return e[t]=i,i},$e=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e,i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?new URL(import.meta.resolve(e)):new URL(e,import.meta.url)}r=o(t.map(t=>{t=Ye(t,n);let r=s(t),i=Ze(r);return Qe(Xe,r.href,()=>{if(e===void 0){e={all:new Set,styles:new Set};let t=document.getElementsByTagName(`link`);for(let n=t.length-1;n>=0;n--){let r=t[n];e.all.add(r.href),r.rel===`stylesheet`&&e.styles.add(r.href)}}if((i?e.styles:e.all).has(r.href))return;let t=document.createElement(`link`);if(t.rel=i?`stylesheet`:Je,i||(t.as=`script`),t.crossOrigin=``,t.href=r.href,a&&t.setAttribute(`nonce`,a),document.head.appendChild(t),i)return new Promise((e,n)=>{t.addEventListener(`load`,e),t.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${r}`)))})})}).filter(e=>e!==void 0))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};async function K(e,t,n,r={}){let i={method:e,headers:{...r}};e!==`GET`&&(i.headers[`X-Pantrybase`]=`1`),n instanceof Blob?i.body=n:n!==void 0&&(i.body=JSON.stringify(n),i.headers[`Content-Type`]=`application/json`);let a=await fetch(t,i),o=await a.json().catch(()=>({}));if(!a.ok)throw Error(o.error??`${a.status} ${a.statusText}`);return o}var et=e=>{let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!=null&&r!==``&&t.set(n,String(r));let n=t.toString();return n?`?${n}`:``},tt=e=>({tags:e.tags&&Object.keys(e.tags).length?JSON.stringify(e.tags):void 0,exclude_allergens:e.exclude_allergens?.length?e.exclude_allergens.join(`,`):void 0,max_total_min:e.max_total_min,needs_review:e.needs_review?`true`:void 0}),q=(e,t)=>`/api/files/${e.split(`/`).map(encodeURIComponent).join(`/`)}${t?`?w=${t}`:``}`,J={stats:()=>K(`GET`,`/api/stats`),facets:()=>K(`GET`,`/api/facets`),search:(e,t,n=48)=>K(`GET`,`/api/search${et({q:e,limit:n,...tt(t)})}`),recipe:e=>K(`GET`,`/api/recipes/${encodeURIComponent(e)}`),pantry:()=>K(`GET`,`/api/pantry`),pantryAdd:e=>K(`POST`,`/api/pantry`,{items:e.map(e=>({name:e}))}),pantryRemove:e=>K(`DELETE`,`/api/pantry`,{names:e}),pantryClear:()=>K(`DELETE`,`/api/pantry`,{all:!0}),match:e=>K(`GET`,`/api/pantry/match${et({...tt(e),min_coverage:e.min_coverage,ingredients:e.ingredients?.join(`,`)})}`),ingredients:e=>K(`GET`,`/api/ingredients${et({q:e,limit:12})}`),graph:e=>K(`GET`,`/api/graph${et({categories:e.categories.join(`,`),ingredients:e.ingredients?`1`:`0`,min_degree:e.min_degree,cuisine:e.cuisine,similar:e.similar})}`),inbox:e=>K(`GET`,`/api/inbox${et({status:e,limit:300})}`),upload:e=>K(`POST`,`/api/inbox/upload`,e,{"X-Filename":encodeURIComponent(e.name)}),resolve:(e,t,n)=>K(`POST`,`/api/inbox/resolve`,{ids:e,status:t,reason:n})},nt=0;Array.isArray;function Y(t,n,r,i,a,o){n||={};var s,c,l=n;if(`ref`in l&&typeof t!=`function`)for(c in l={},n)c==`ref`?s=n[c]:l[c]=n[c];var u={type:t,props:l,key:r,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--nt,__i:-1,__u:0};return(a||o)&&(u.__source=a,u.__self=o),e.vnode&&e.vnode(u),u}function X(e,t){let[n,r]=V({loading:!0}),i=H(0),a=Ce(()=>{let t=++i.current;r(e=>({...e,loading:!0})),e().then(e=>t===i.current&&r({data:e,loading:!1}),e=>t===i.current&&r(t=>({...t,error:String(e instanceof Error?e.message:e),loading:!1})))},t);return be(()=>a(),[a]),{...n,reload:a}}function rt(e,t=250){let[n,r]=V(e);return be(()=>{let n=setTimeout(()=>r(e),t);return()=>clearTimeout(n)},[e,t]),n}function it(e){return e==null?``:e>=60?`${Math.floor(e/60)}h${e%60?` ${e%60}m`:``}`:`${e} min`}function Z(e){return e.replace(/-/g,` `)}function Q(e){let t=`chip${e.tone?` ${e.tone}`:``}`;return e.onClick?Y(`button`,{type:`button`,class:t,onClick:e.onClick,title:e.title,children:e.children}):Y(`span`,{class:t,title:e.title,children:e.children})}function at({r:e}){let t=e.images[0];return Y(`a`,{class:`card`,href:`/recipe/${e.slug}`,children:[t?Y(`div`,{class:`img`,style:{backgroundImage:`url("${q(t,480)}")`}}):Y(`div`,{class:`img none`,"aria-hidden":`true`,children:e.source_kind===`synthesized`?`✦`:`🍽`}),Y(`div`,{class:`body`,children:[Y(`div`,{class:`title`,children:e.title}),e.subtitle&&Y(`div`,{class:`sub`,children:e.subtitle}),Y(`div`,{class:`meta`,children:[e.total_min!=null&&Y(Q,{children:[`⏱ `,it(e.total_min)]}),e.tags.cuisine?.slice(0,1).map(e=>Y(Q,{children:Z(e)},e)),e.tags.diet?.slice(0,2).map(e=>Y(Q,{tone:`ok`,children:Z(e)},e)),e.needs_review&&Y(Q,{tone:`warn`,children:`needs review`})]})]})]})}function $({children:e}){return Y(`div`,{class:`empty`,children:e})}var ot=[`pending`,`done`,`skipped`,`failed`];function st(){let[e,t]=V(`pending`),n=X(()=>J.inbox(e),[e]),[r,i]=V(!1),[a,o]=V(null),[s,c]=V({}),l=async e=>{let r=[...e].filter(e=>/\.(jpe?g|png|webp|heic|heif|tiff?)$/i.test(e.name));if(!r.length)return;let i=[];o({done:0,total:r.length,errors:i});for(let[e,t]of r.entries()){try{await J.upload(t)}catch(e){i.push(`${t.name}: ${String(e)}`)}o({done:e+1,total:r.length,errors:i})}t(`pending`),n.reload()},u=async(e,t)=>{await J.resolve([e.id],t,s[e.id]),n.reload()},d=n.data?.counts;return Y(`div`,{class:`stack`,children:[Y(`label`,{class:`drop${r?` over`:``}`,onDragOver:e=>{e.preventDefault(),i(!0)},onDragLeave:()=>i(!1),onDrop:e=>{e.preventDefault(),i(!1),e.dataTransfer?.files&&l(e.dataTransfer.files)},children:[Y(`input`,{type:`file`,multiple:!0,accept:`image/*,.heic,.heif`,style:{display:`none`},onChange:e=>{let t=e.target.files;t&&l(t)}}),Y(`div`,{style:{fontSize:`1.1rem`,fontWeight:600},children:`Drop recipe card photos here, or click to choose`}),Y(`div`,{class:`small`,children:`JPEG, PNG, WebP or iPhone HEIC — fronts and backs. Exact duplicates are ignored automatically.`}),a&&Y(`div`,{class:`small`,style:{marginTop:`0.5rem`},children:[`Uploaded `,a.done,`/`,a.total,a.errors.map(e=>Y(`div`,{class:`error`,children:e},e))]})]}),d&&d.pending>0&&Y(`div`,{class:`notice`,children:[d.pending,` photo`,d.pending===1?``:`s`,` waiting. In Claude Code, run`,` `,Y(`b`,{children:`/pantrybase:ingest`}),` and Claude will read each card, pair fronts with backs, and save the recipes.`]}),Y(`div`,{class:`tabs`,children:ot.map(n=>Y(`button`,{type:`button`,class:n===e?`on`:``,onClick:()=>t(n),children:[n,` `,d?Y(`span`,{class:`muted`,children:d[n]}):null]},n))}),n.data&&n.data.items.length===0?Y($,{children:[`Nothing `,e,`.`]}):Y(`div`,{class:`thumbgrid`,children:n.data?.items.map(e=>Y(`div`,{class:`thumb`,children:[Y(`a`,{href:q(e.path,1600),target:`_blank`,rel:`noreferrer`,children:Y(`img`,{src:q(e.path,360),alt:e.name,loading:`lazy`})}),Y(`div`,{class:`tb`,children:[Y(`div`,{class:`row`,style:{justifyContent:`space-between`},children:[Y(`span`,{title:e.name,children:[`#`,e.id]}),Y(`span`,{class:`muted`,children:e.captured_at.slice(5,16).replace(`T`,` `)})]}),e.retake_of&&Y(Q,{tone:`warn`,title:`Looks like photo #${e.retake_of.id} (distance ${e.retake_of.distance})`,children:[`retake of #`,e.retake_of.id]}),e.recipe_slug&&Y(`a`,{href:`/recipe/${e.recipe_slug}`,children:[`→ `,e.recipe_slug]}),e.reason&&Y(`span`,{class:`muted`,children:e.reason}),e.status!==`done`&&Y(x,{children:[e.status===`pending`&&Y(`input`,{type:`text`,placeholder:`reason (optional)`,value:s[e.id]??``,onInput:t=>c(n=>({...n,[e.id]:t.target.value}))}),Y(`div`,{class:`row`,children:e.status===`pending`?Y(x,{children:[Y(`button`,{type:`button`,class:`small`,onClick:()=>u(e,`skipped`),children:`Skip`}),Y(`button`,{type:`button`,class:`small`,onClick:()=>u(e,`failed`),children:`Unreadable`})]}):Y(`button`,{type:`button`,class:`small`,onClick:()=>u(e,`pending`),children:`Back to queue`})})]})]})]},e.id))})]})}function ct(){let e=X(()=>J.pantry(),[]),[t,n]=V(``),[r,i]=V(.5),[a,o]=V(null),s=rt(t,150),c=X(()=>s.trim().length>=2?J.ingredients(s.trim()):Promise.resolve([]),[s]),l=X(()=>J.match({min_coverage:r}),[r,e.data?.length]),u=async t=>{let r=t.split(`,`).map(e=>e.trim()).filter(Boolean);if(r.length){o(null);try{await J.pantryAdd(r),n(``),e.reload()}catch(e){o(String(e))}}},d=async t=>{await J.pantryRemove([t]),e.reload()};return Y(`div`,{class:`layout`,style:{gridTemplateColumns:`minmax(280px, 380px) 1fr`},children:[Y(`section`,{class:`panel stack`,children:[Y(`h2`,{children:`Your pantry`}),Y(`form`,{onSubmit:e=>{e.preventDefault(),u(t)},class:`row`,style:{flexWrap:`nowrap`},children:[Y(`input`,{type:`text`,list:`ingredient-suggestions`,placeholder:`Add items, e.g. eggs, baby marrow, rice`,value:t,onInput:e=>n(e.target.value)}),Y(`datalist`,{id:`ingredient-suggestions`,children:c.data?.map(e=>Y(`option`,{value:e.canonical},e.canonical))}),Y(`button`,{type:`submit`,class:`primary`,children:`Add`})]}),a&&Y(`p`,{class:`error small`,children:a}),e.data?.length?Y(`div`,{class:`row`,children:e.data.map(e=>Y(Q,{onClick:()=>d(e.canonical),title:`Remove`,children:[e.canonical,e.qty==null?``:` ×${e.qty}${e.unit?` ${e.unit}`:``}`,` `,Y(`span`,{class:`x`,children:`✕`})]},e.canonical))}):Y(`p`,{class:`muted small`,children:`Nothing yet. Salt, oil, water and other kitchen staples never count against a recipe, so skip those.`}),e.data&&e.data.length>0&&Y(`button`,{type:`button`,class:`ghost small`,onClick:async()=>{await J.pantryClear(),e.reload()},children:`Clear pantry`})]}),Y(`section`,{class:`panel`,children:[Y(`div`,{class:`row`,style:{justifyContent:`space-between`},children:[Y(`h2`,{children:`What can I cook?`}),Y(`label`,{class:`row small`,children:[`at least `,Math.round(r*100),`% covered`,Y(`input`,{type:`range`,min:0,max:1,step:.1,value:r,onInput:e=>i(Number(e.target.value))})]})]}),l.data&&l.data.results.length===0?Y($,{children:`No recipes reach that coverage yet — add more to your pantry or lower the bar.`}):l.data?.results.map(e=>Y(`div`,{class:`list-row`,children:[Y(`div`,{children:[Y(`a`,{href:`/recipe/${e.slug}`,children:Y(`b`,{children:e.title})}),e.near_miss&&Y(Q,{tone:`warn`,title:`1–2 items short`,children:`near miss`}),Y(`div`,{class:`row small`,style:{marginTop:`0.25rem`},children:e.missing.length?Y(x,{children:[Y(`span`,{class:`muted`,children:`missing`}),e.missing.map(e=>Y(Q,{onClick:()=>u(e.canonical),title:`Add to pantry`,children:[`+ `,Z(e.canonical)]},e.canonical))]}):Y(`span`,{class:`muted`,children:`you have everything`})})]}),Y(`div`,{children:[Y(`div`,{class:`bar`,children:Y(`span`,{style:{width:`${Math.round(e.coverage*100)}%`}})}),Y(`div`,{class:`muted small`,style:{textAlign:`right`},children:[e.have,`/`,e.need,` · `,Math.round(e.coverage*100),`%`]})]})]},e.slug))]})]})}var lt=[[`energy_kj`,`Energy`,`kJ`],[`energy_kcal`,`Energy`,`kcal`],[`protein_g`,`Protein`,`g`],[`carbs_g`,`Carbs`,`g`],[`sugars_g`,`of which sugars`,`g`],[`fibre_g`,`Fibre`,`g`],[`fat_g`,`Fat`,`g`],[`saturated_g`,`of which saturated`,`g`],[`sodium_mg`,`Sodium`,`mg`]];function ut(e){return[e.qty,e.unit].filter(e=>e!=null&&e!==``).join(` `)}function dt(){let{params:e}=We(),t=e.slug??``,n=X(()=>J.recipe(t),[t]),[r,i]=V(0),[a,o]=V(!1);if(n.error)return Y($,{children:n.error});let s=n.data;if(!s)return Y($,{children:`Loading…`});let c=s.recipe,l=new Set(s.pantry_have),u=c.ingredients.filter(e=>e.source!==`kitchen`),d=c.ingredients.filter(e=>e.source===`kitchen`),f=u.filter(e=>!e.staple&&!e.optional&&!l.has(e.canonical)),p=async e=>{o(!0);try{await J.pantryAdd(e),n.reload()}finally{o(!1)}},m=e=>{let t=l.has(e.canonical);return Y(`li`,{children:[Y(`span`,{class:`tick`,title:t?`in your pantry`:e.staple?`staple`:`not in pantry`,children:t?`✓`:e.staple?`·`:`○`}),Y(`span`,{children:[Y(`span`,{title:ut(e)?`${ut(e)} ${e.canonical}`:e.canonical,children:e.raw}),e.prep&&Y(`span`,{class:`prep`,children:[` — `,e.prep]}),e.optional&&!/optional/i.test(e.raw)&&Y(`span`,{class:`muted small`,children:` (optional)`}),e.alternatives?.length?Y(`span`,{class:`muted small`,children:[` · or `,e.alternatives.join(`, `)]}):null,e.components?.length?Y(`div`,{class:`muted small`,children:e.components.map(e=>e.raw).join(` + `)}):null]})]},`${e.raw}-${e.canonical}`)},h=c.nutrition,g=c.provider?.wine;return Y(`article`,{children:[Y(`div`,{class:`hero`,children:[Y(`div`,{class:`gallery`,children:c.images.length?Y(x,{children:[Y(`img`,{class:`main`,src:q(c.images[r]??c.images[0]??``,1400),alt:c.title}),c.images.length>1&&Y(`div`,{class:`thumbs`,children:c.images.map((e,t)=>Y(`button`,{type:`button`,class:`ghost`,style:{padding:0},onClick:()=>i(t),"aria-label":`Photo ${t+1}`,children:Y(`img`,{src:q(e,200),class:t===r?`sel`:``,alt:``})},e))})]}):Y(`div`,{class:`panel empty`,children:`No photo`})}),Y(`div`,{class:`stack`,children:[Y(`div`,{children:[Y(`h1`,{children:c.title}),c.subtitle&&Y(`div`,{class:`muted`,children:c.subtitle})]}),Y(`div`,{class:`row`,children:[`cuisine`,`dish`,`diet`,`protein`,`flavor`,`texture`,`season`].flatMap(e=>(c.tags[e]??[]).map(t=>Y(`a`,{href:`/?tags=${encodeURIComponent(JSON.stringify({[e]:[t]}))}`,children:Y(Q,{tone:e===`diet`?`ok`:void 0,children:Z(t)})},`${e}${t}`)))}),c.extraction?.needs_review&&Y(`div`,{class:`notice`,children:[`Flagged for review`,c.extraction.uncertain?.length?`: couldn't read ${c.extraction.uncertain.join(`, `)}`:``,`.`,c.extraction.notes?` ${c.extraction.notes}`:``]}),c.blurb&&Y(`p`,{children:c.blurb}),Y(`div`,{class:`facts`,children:[c.time.active!=null&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`Hands-on`}),it(c.time.active)]}),c.time.total!=null&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`Total`}),it(c.time.total)]}),c.servings!=null&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`Serves`}),c.servings]}),c.provider?.chef&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`Chef`}),c.provider.chef]}),c.provider&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`From`}),c.provider.name??c.provider.id,c.provider.plan?` · ${c.provider.plan}`:``]}),c.provider?.eat_within_days!=null&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`Eat within`}),c.provider.eat_within_days,` days`]}),g&&(g.name||g.producer)&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`Wine`}),[g.producer,g.name,g.vintage].filter(Boolean).join(` · `)]}),c.source.kind===`synthesized`&&Y(`div`,{class:`fact`,children:[Y(`b`,{children:`Source`}),`Synthesized`]})]}),c.description&&Y(`p`,{class:`muted small`,children:c.description})]})]}),Y(`div`,{class:`cols`,children:[Y(`section`,{class:`panel`,children:[Y(`div`,{class:`row`,style:{justifyContent:`space-between`},children:[Y(`h2`,{children:`Ingredients`}),f.length>0&&Y(`button`,{type:`button`,class:`small`,disabled:a,onClick:()=>p(f.map(e=>e.canonical)),children:`I have these`})]}),Y(`ul`,{class:`ings`,children:u.map(m)}),d.length>0&&Y(x,{children:[Y(`h3`,{style:{marginTop:`1rem`},children:`From your kitchen`}),Y(`ul`,{class:`ings`,children:d.map(m)})]}),Y(`p`,{class:`muted small`,children:`✓ in your pantry · ○ missing · · staple`})]}),Y(`section`,{class:`panel`,children:[Y(`h2`,{children:`Method`}),Y(`ol`,{class:`steps`,children:c.steps.map((e,t)=>Y(`li`,{children:[e.title&&Y(`div`,{class:`stitle`,children:e.title}),Y(`div`,{children:e.text}),Y(`div`,{class:`row`,style:{marginTop:`0.3rem`},children:[e.techniques?.map(e=>Y(Q,{children:Z(e)},e)),e.temp_c!=null&&Y(Q,{children:[`🌡 `,e.temp_c,`°C`]}),e.minutes?.length?Y(Q,{children:[`⏱ `,e.minutes.join(`–`),` min`]}):null]})]},t))}),c.tips.length>0&&Y(x,{children:[Y(`h3`,{children:`Tips`}),c.tips.map(e=>Y(`p`,{class:`small`,children:e},e))]}),c.notes&&Y(x,{children:[Y(`h3`,{children:`Notes`}),Y(`p`,{class:`small`,children:c.notes})]})]}),Y(`aside`,{class:`stack`,children:[h&&Y(`section`,{class:`panel`,children:[Y(`h2`,{children:`Nutrition`}),Y(`div`,{class:`muted small`,children:h.basis===`per_100g`?`per 100 g`:`per serving`}),Y(`table`,{class:`nut`,children:Y(`tbody`,{children:lt.filter(([e])=>h[e]!=null).map(([e,t,n])=>Y(`tr`,{children:[Y(`td`,{children:t}),Y(`td`,{children:[h[e],` `,n]})]},e))})})]}),(c.allergens.length>0||c.equipment.length>0)&&Y(`section`,{class:`panel stack`,children:[c.allergens.length>0&&Y(`div`,{children:[Y(`h3`,{children:`Allergens`}),Y(`div`,{class:`row`,children:c.allergens.map(e=>Y(Q,{tone:`bad`,children:Z(e)},e))})]}),c.equipment.length>0&&Y(`div`,{children:[Y(`h3`,{children:`Equipment`}),Y(`div`,{class:`row`,children:c.equipment.map(e=>Y(Q,{children:Z(e)},e))})]})]}),s.sources.length>0&&Y(`section`,{class:`panel`,children:[Y(`h3`,{children:`Inspired by`}),s.sources.map(e=>Y(`div`,{children:Y(`a`,{href:`/recipe/${e.slug}`,children:e.title})},e.slug))]}),s.derived.length>0&&Y(`section`,{class:`panel`,children:[Y(`h3`,{children:`Recipes built on this`}),s.derived.map(e=>Y(`div`,{children:Y(`a`,{href:`/recipe/${e.slug}`,children:e.title})},e.slug))]})]})]}),s.similar.length>0&&Y(`section`,{style:{marginTop:`2rem`},children:[Y(`h2`,{children:`Similar recipes`}),Y(`div`,{class:`grid`,children:s.similar.map(e=>Y(at,{r:e},e.slug))})]})]})}var ft=[`diet`,`cuisine`,`dish`,`protein`,`starch`,`flavor`,`texture`,`method`,`meal`,`time`,`season`,`technique`,`equipment`,`chef`,`plan`,`provider`],pt=[0,20,30,45,60];function mt(e){let t={};try{t=e.tags?JSON.parse(e.tags):{}}catch{t={}}return{q:e.q??``,tags:t,exclude:e.exclude?e.exclude.split(`,`):[],max:e.max?Number(e.max):0,review:e.review===`1`}}function ht(){let e=Ue(),t=mt(e.query),[n,r]=V(t.q),[i,a]=V(t.tags),[o,s]=V(t.exclude),[c,l]=V(t.max),[u,d]=V(t.review),[f,p]=V({}),m=rt(n);be(()=>{let t=new URLSearchParams;m&&t.set(`q`,m),Object.keys(i).length&&t.set(`tags`,JSON.stringify(i)),o.length&&t.set(`exclude`,o.join(`,`)),c&&t.set(`max`,String(c)),u&&t.set(`review`,`1`);let n=`/${t.toString()?`?${t}`:``}`;n!==`${e.path}${e.url.includes(`?`)?e.url.slice(e.url.indexOf(`?`)):``}`&&e.route(n,!0)},[m,i,o,c,u]);let h={tags:i,exclude_allergens:o,max_total_min:c||void 0,needs_review:u||void 0},g=X(()=>J.search(m,h),[m,JSON.stringify(h)]),_=X(()=>J.facets(),[]),v=(e,t)=>a(n=>{let r=new Set(n[e]??[]);r.has(t)?r.delete(t):r.add(t);let i={...n,[e]:[...r]};return i[e]?.length||delete i[e],i}),y=e=>s(t=>t.includes(e)?t.filter(t=>t!==e):[...t,e]),b=Object.entries(i).flatMap(([e,t])=>t.map(t=>[e,t])),x=_.data?.used??{};return Y(`div`,{class:`layout`,children:[Y(`aside`,{class:`panel`,children:[Y(`div`,{class:`facet`,children:[Y(`h3`,{children:`Total time`}),Y(`select`,{value:c,onChange:e=>l(Number(e.target.value)),children:pt.map(e=>Y(`option`,{value:e,children:e?`≤ ${e} min`:`Any`},e))})]}),x.allergen?.length?Y(`div`,{class:`facet`,children:[Y(`h3`,{children:`Leave out allergens`}),Y(`div`,{class:`chips`,children:x.allergen.map(e=>Y(Q,{tone:o.includes(e.name)?`bad`:void 0,onClick:()=>y(e.name),children:[o.includes(e.name)?`✕ `:``,Z(e.name)]},e.name))})]}):null,ft.filter(e=>x[e]?.length).map(e=>{let t=x[e]??[],n=f[e]?t:t.slice(0,8);return Y(`div`,{class:`facet`,children:[Y(`h3`,{children:e}),Y(`div`,{class:`chips`,children:n.map(t=>Y(Q,{tone:i[e]?.includes(t.name)?`on`:void 0,onClick:()=>v(e,t.name),children:[Z(t.name),` `,Y(`span`,{class:`muted`,children:t.count})]},t.name))}),t.length>8&&Y(`button`,{type:`button`,class:`ghost more`,onClick:()=>p(t=>({...t,[e]:!t[e]})),children:f[e]?`fewer`:`+${t.length-8} more`})]},e)}),Y(`label`,{class:`row small`,children:[Y(`input`,{type:`checkbox`,checked:u,onChange:e=>d(e.target.checked)}),`Only recipes needing review`]})]}),Y(`section`,{class:`stack`,children:[Y(`input`,{type:`search`,placeholder:`Search recipes — a dish, an ingredient, a mood (“cosy weeknight curry”)…`,value:n,onInput:e=>r(e.target.value)}),(b.length>0||o.length>0||c>0)&&Y(`div`,{class:`row`,children:[b.map(([e,t])=>Y(Q,{tone:`on`,onClick:()=>v(e,t),children:[e,`: `,Z(t),` `,Y(`span`,{class:`x`,children:`✕`})]},`${e}:${t}`)),o.map(e=>Y(Q,{tone:`bad`,onClick:()=>y(e),children:[`no `,Z(e),` `,Y(`span`,{class:`x`,children:`✕`})]},e)),Y(`button`,{type:`button`,class:`ghost small`,onClick:()=>{a({}),s([]),l(0),d(!1)},children:`Clear all`})]}),g.error&&Y(`p`,{class:`error`,children:g.error}),Y(`div`,{class:`muted small`,children:[g.data?`${g.data.results.length} recipes`:`Loading…`,g.data&&m?g.data.vector_used?` · semantic + keyword`:` · keyword (semantic index warming up)`:``]}),g.data&&g.data.results.length===0?Y($,{children:[`No recipes match.`,` `,m||b.length?`Try fewer filters.`:`Add some — drop card photos on the Inbox page, then ask Claude to /pantrybase:ingest them.`]}):Y(`div`,{class:`grid`,children:g.data?.results.map(e=>Y(at,{r:e},e.slug))})]})]})}var gt=[`cuisine`,`dish`,`diet`,`protein`,`starch`,`flavor`,`texture`,`method`,`meal`,`time`,`season`,`allergen`,`technique`,`equipment`,`chef`,`plan`,`provider`,`other`];function _t(){let e=X(()=>J.facets(),[]),t=e.data?.used??{},n=gt.filter(e=>t[e]?.length);return e.data&&!n.length?Y($,{children:`No tags yet — they appear as recipes are saved.`}):Y(`div`,{class:`grid`,style:{gridTemplateColumns:`repeat(auto-fill, minmax(300px, 1fr))`},children:n.map(n=>Y(`section`,{class:`panel`,children:[Y(`h3`,{children:[n,` `,Y(`span`,{class:`muted`,children:[`· `,t[n]?.length]})]}),Y(`div`,{class:`row`,children:t[n]?.map(e=>Y(`a`,{href:n===`allergen`?`/?exclude=${encodeURIComponent(e.name)}`:`/?tags=${encodeURIComponent(JSON.stringify({[n]:[e.name]}))}`,children:Y(Q,{children:[Z(e.name),` `,Y(`span`,{class:`muted`,children:e.count})]})},e.name))}),e.data?.vocabulary[n]?.mode===`closed`&&Y(`div`,{class:`muted small`,style:{marginTop:`0.5rem`},children:`Controlled vocabulary`})]},n))})}var vt=Ke(()=>$e(()=>import(`./graph-Mpnwdw0-.js`).then(e=>e.GraphPage),[]));function yt(){let{path:e}=Ue(),t=X(()=>J.stats(),[e]),n=(t,n,r)=>Y(`a`,{class:`link${(t===`/`?e===`/`:e.startsWith(t))?` active`:``}`,href:t,children:[n,r?Y(`span`,{class:`badge`,children:r}):null]}),r=t.data;return Y(`nav`,{class:`nav`,children:[Y(`a`,{class:`brand`,href:`/`,children:[Y(`i`,{}),` pantrybase`]}),n(`/`,`Recipes`),n(`/pantry`,`Pantry`),n(`/graph`,`Graph`),n(`/inbox`,`Inbox`,r?.inbox.pending),n(`/tags`,`Tags`),Y(`span`,{class:`spacer`}),r&&Y(`span`,{class:`stat`,children:[r.recipes,` recipes · `,r.ingredients,` ingredients`,r.needs_review?` · ${r.needs_review} to review`:``]})]})}function bt(){return Y(Ie,{children:[Y(yt,{}),Y(`main`,{children:Y(Re,{children:[Y(G,{path:`/`,component:ht}),Y(G,{path:`/recipe/:slug`,component:dt}),Y(G,{path:`/pantry`,component:ct}),Y(G,{path:`/graph`,component:vt}),Y(G,{path:`/inbox`,component:st}),Y(G,{path:`/tags`,component:_t}),Y(G,{default:!0,component:ht})]})})]})}var xt=document.getElementById(`app`);xt&&I(Y(bt,{}),xt);export{J as a,be as c,V as d,Y as i,H as l,Z as n,q as o,X as r,Ue as s,Q as t,Se as u};
|