Use this skill when the user wants to add a receipt, split a bill among friends, or divide restaurant costs. Applies when the user provides a receipt image and describes who ordered what, mentions shared dishes, asks to calculate each person's share of tax and tip, or wants to record a group meal. Also use when the user describes a dinner, lunch, or meal outing with others -- even if they don't explicitly say "receipt" or "split."
Split a restaurant bill among multiple people with itemized breakdowns, proportional tax/tip, and insert the result into the app's Supabase database.
Copy this checklist and track progress:
Progress:
- [ ] Step 1: Read receipt and gather item assignments
- [ ] Step 2: Verify all items assigned (plan-validate)
- [ ] Step 3: Calculate splits (tax, tip, rounding)
- [ ] Step 4: Insert receipt + bill items into Supabase
- [ ] Step 5: Validate totals match receipt
- [ ] Step 6: Present summary to user
Extract:
Ask for:
If the user says "Me" for themselves, use "Me" as the person_name.
Before any calculation, build a verification table mapping every receipt line item to its assigned person(s). Present it to the user:
| # | Receipt Item | Price | Assigned To |
|---|------------------------|--------|----------------------|
| 1 | Juicy Pork Bao (6Pcs) | $15.95 | Jay + Dylan (shared) |
| 2 | Pot Stickers (5Pcs) | $14.95 | Jay |
| ... |
Every item must be assigned. If any item is unaccounted for, ask the user before proceeding. Do not guess.
Each person's solo items sum to their food subtotal.
For an item with price P shared among N people: each person's share = P / N.
Store the full price in the breakdown's shared_items[].amount field (not the split portion). The split_with array lists the other people sharing (not the current person).
person_tax = (person_food_subtotal / receipt_subtotal) * receipt_tax
person_tip = (person_food_subtotal / receipt_subtotal) * tip_amount
Tip is usually calculated on the pre-tax subtotal. Confirm with the user if unclear.
parseFloat(value.toFixed(2))Generate and run a Node.js script that uses @supabase/supabase-js (already installed in the project). Read credentials from .env.local:
NEXT_PUBLIC_SUPABASE_URLSUPABASE_SECRET_KEYThe script should:
receipts:const imageBuffer = fs.readFileSync(imagePath);
const fileName = 'restaurant-name-' + Date.now() + '.png';
await supabase.storage.from('receipts').upload(fileName, imageBuffer, {
contentType: 'image/png', upsert: false
});
const { data: urlData } = supabase.storage.from('receipts').getPublicUrl(fileName);
const { data: receipt } = await supabase
.from('receipts')
.insert({
name: 'Restaurant Name',
date: '2026-03-10', // YYYY-MM-DD from receipt
image_url: urlData.publicUrl,
notes: 'Optional context' // e.g. who was there, occasion
})
.select().single();
const billItems = [
{
receipt_id: receipt.id,
person_name: 'PersonName',
amount: 55.57, // final total (food + tax + tip)
breakdown: {
items: [
{ description: 'Pot Stickers (5Pcs)', amount: 14.95 }
],
shared_items: [
{ description: 'Juicy Pork Bao (6Pcs)', amount: 15.95,
split_with: ['OtherPerson'] }
],
subtotal: 43.88,
tax_share: 3.79,
tip_share: 7.90
}
},
// ... one entry per person
];
await supabase.from('bill_items').insert(billItems);
For full schema details and field types, see references/schema.md.
After insertion, query back all bill_items and verify:
const { data: items } = await supabase
.from('bill_items')
.select('person_name, amount')
.eq('receipt_id', receipt.id);
let sum = 0;
items.forEach(i => { sum += parseFloat(i.amount); });
console.log('Total: $' + sum.toFixed(2));
Compare the sum to the expected grand total (subtotal + tax + tip). If off by more than $0.01, update the person with the largest share to reconcile. Only proceed once validated.
Show the user a table with the final split:
| Person | Food | Tax | Tip | Total |
|---------|---------|-------|-------|-----------|
| Jay | $43.88 | $3.79 | $7.90 | **$55.57**|
| Dylan | $28.93 | $2.50 | $5.21 | **$36.63**|
| Kyle | $15.95 | $1.38 | $2.87 | **$20.20**|
| Me | $48.85 | $4.22 | $8.79 | **$61.86**|
| | | | Total | **$174.26**|
Include a breakdown summary noting who shared what with whom.
43.875 + 3.79 in JavaScript may produce 47.664999... not 47.665. Always wrap final amounts with parseFloat((...).toFixed(2)).SUPABASE_SECRET_KEY, not SUPABASE_SERVICE_ROLE_KEY.receipts (lowercase, no prefix).shared_items semantics: The amount field stores the full item price, not the per-person split. The split_with array lists the other people (not the current person)."Me" as their person_name in bill_items.YYYY-MM-DD for the date column.fs.readFileSync() with the full path to read it for upload.