summaryrefslogtreecommitdiff
path: root/static/app.js
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-12 03:44:40 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-12 03:44:40 -0700
commit9c8332d4442ddd569aacc37f2000fb0afe36111e (patch)
tree1d1f03340249bab2729ce123e302e91e88e7c249 /static/app.js
parent360eadf78fb001e947f3850603152adc413bb3a8 (diff)
Pantry-first AI prompts, recipe editing, and grocery copyHEADmaster
- Rewrite menu generation to build recipes around existing pantry ingredients (max 4-5 extras per recipe); add in_pantry flag per ingredient for downstream accuracy - Flip grocery list logic to ONLY include items not in the pantry, with explicit cross-check rule instead of include-everything default - Strengthen no-menu grocery prompt to handle empty vs. stocked pantry - Rewrite Commis chat system prompt to treat kitchen context as source of truth with explicit response rules per query type - Align replacement/single-recipe prompts to prefer pantry coverage - Update system_prompt default to reflect pantry-first identity - Add PUT /api/recipes/:id endpoint for recipe editing - Add inline edit mode to recipe detail page (name, type, ingredients, instructions, time, servings) - Add Copy button to grocery list (exports markdown checklist) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'static/app.js')
-rw-r--r--static/app.js47
1 files changed, 47 insertions, 0 deletions
diff --git a/static/app.js b/static/app.js
index 3135aa9..796b9bf 100644
--- a/static/app.js
+++ b/static/app.js
@@ -592,6 +592,7 @@ async function loadGrocery() {
document.getElementById('grocery-empty').classList.remove('hidden');
document.getElementById('btn-mark-purchased').classList.add('hidden');
document.getElementById('btn-clear-grocery').classList.add('hidden');
+ document.getElementById('btn-copy-grocery').classList.add('hidden');
return;
}
@@ -599,6 +600,7 @@ async function loadGrocery() {
state.currentGroceryId = res.id;
document.getElementById('grocery-empty').classList.add('hidden');
document.getElementById('btn-clear-grocery').classList.remove('hidden');
+ document.getElementById('btn-copy-grocery').classList.remove('hidden');
if (res.notes) {
document.getElementById('grocery-notes').textContent = res.notes;
@@ -691,6 +693,48 @@ function renderGroceryList() {
}
}
+function copyGroceryList() {
+ if (!state.currentGrocery) return;
+
+ let items = state.currentGrocery.items;
+ if (typeof items === 'string') {
+ try { items = JSON.parse(items); } catch { items = []; }
+ }
+ if (!Array.isArray(items)) items = [];
+
+ const total = items.filter(i => !i.checked).reduce((sum, item) => sum + (item.estimated_cost || 0), 0);
+ const grouped = {};
+ items.forEach(item => {
+ const section = item.store_section || 'other';
+ if (!grouped[section]) grouped[section] = [];
+ grouped[section].push(item);
+ });
+
+ let md = '## Grocery List\n';
+ if (total > 0) md += `**Estimated total: $${total.toFixed(2)}**\n`;
+
+ Object.keys(grouped).sort().forEach(section => {
+ const unchecked = grouped[section].filter(item => !item.checked);
+ if (!unchecked.length) return;
+ const title = section.charAt(0).toUpperCase() + section.slice(1);
+ md += `\n### ${title}\n`;
+ unchecked.forEach(item => {
+ const qty = [item.quantity, item.unit].filter(Boolean).join(' ');
+ md += `- [ ] ${qty ? qty + ' ' : ''}${item.name}\n`;
+ });
+ });
+
+ const notes = state.currentGrocery.notes;
+ if (notes) md += `\n> ${notes}\n`;
+
+ navigator.clipboard.writeText(md).then(() => {
+ const btn = document.getElementById('btn-copy-grocery');
+ const orig = btn.textContent;
+ btn.textContent = 'Copied!';
+ setTimeout(() => { btn.textContent = orig; }, 2000);
+ }).catch(() => showToast('Could not copy to clipboard', 'error'));
+}
+
async function checkGroceryItem(checkbox) {
const checked = checkbox.checked;
const row = checkbox.closest('.grocery-item');
@@ -739,6 +783,7 @@ async function generateGrocery() {
document.getElementById('grocery-empty').classList.add('hidden');
document.getElementById('btn-mark-purchased').classList.remove('hidden');
document.getElementById('btn-clear-grocery').classList.remove('hidden');
+ document.getElementById('btn-copy-grocery').classList.remove('hidden');
if (res.shopping_notes) {
document.getElementById('grocery-notes').textContent = res.shopping_notes;
@@ -771,6 +816,7 @@ async function clearGrocery() {
document.getElementById('grocery-notes').classList.add('hidden');
document.getElementById('btn-mark-purchased').classList.add('hidden');
document.getElementById('btn-clear-grocery').classList.add('hidden');
+ document.getElementById('btn-copy-grocery').classList.add('hidden');
showToast('Grocery list cleared');
} catch (err) {
showToast(err.message, 'error');
@@ -999,6 +1045,7 @@ async function init() {
// Set up grocery form
document.getElementById('btn-generate-grocery').addEventListener('click', generateGrocery);
document.getElementById('btn-clear-grocery').addEventListener('click', clearGrocery);
+ document.getElementById('btn-copy-grocery').addEventListener('click', copyGroceryList);
document.getElementById('btn-mark-purchased').addEventListener('click', markPurchased);
// Set up chat