Reconcile artifact renderers and fixtures with the backend shapes
Workers A/B are merged, so match what blueprints/ynab/tools.py and conversation/agents.py actually emit: - ynab.spending_report rows and ynab.summary.spent are positive for spending; bars use the positive amount and a net refund (negative row) sorts last with no bar. - ynab.transactions start_date may be null (open-ended search); the header reads "Through Oct 12, 2026". Amounts stay signed with outflows as −$, and total is the net. - Recipe image_url is never sent, search.documents has no source_ref, memo/age_of_money/goal_target are optional; fixtures now match (backend titles, ordering, float quantities, no empty fields). - Mock server: drop the placeholder recipe image, positive month spending, and a "philz" open-ended transactions scenario. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,8 @@ export const MealieShoppingLists = ({ data }: RendererProps<MealieShoppingListsD
|
||||
<span className={cn("flex-1", item.checked ? "text-warm-gray line-through" : "text-charcoal")}>
|
||||
{item.name}
|
||||
</span>
|
||||
{item.quantity != null && item.quantity !== "" && item.quantity !== 0 && (
|
||||
{/* Like the tool's text, quantities of 0/1 aren't worth showing. */}
|
||||
{item.quantity != null && item.quantity !== "" && Number(item.quantity) !== 0 && Number(item.quantity) !== 1 && (
|
||||
<span className="text-xs tabular-nums text-warm-gray">{item.quantity}</span>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@@ -14,7 +14,7 @@ const MonthBars = ({ rows }: { rows: Row[] }) => {
|
||||
const ordered = [...rows].sort((a, b) => a.key.localeCompare(b.key));
|
||||
const withYear = new Set(ordered.map((r) => r.key.slice(0, 4))).size > 1;
|
||||
const label = (key: string) => monthLabel(key, withYear);
|
||||
const max = Math.max(...ordered.map((r) => Math.abs(r.amount)), 1);
|
||||
const max = Math.max(...ordered.map((r) => r.amount), 1);
|
||||
return (
|
||||
<div className="rounded-xl border border-sand-light bg-warm-white px-3 pt-4 pb-2">
|
||||
<div className="flex h-44 items-end gap-1.5">
|
||||
@@ -25,11 +25,11 @@ const MonthBars = ({ rows }: { rows: Row[] }) => {
|
||||
title={`${monthLabel(r.key, true)}: ${formatMoney(r.amount)}`}
|
||||
>
|
||||
<span className="text-[10px] font-semibold tabular-nums text-warm-gray opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
|
||||
{formatMoney(Math.abs(r.amount)).replace(/\.\d\d$/, "")}
|
||||
{formatMoney(r.amount).replace(/\.\d\d$/, "")}
|
||||
</span>
|
||||
<div
|
||||
className="w-full max-w-10 rounded-t-md bg-amber-glow/80 group-hover:bg-amber-dark transition-colors"
|
||||
style={{ height: `${Math.max(2, (Math.abs(r.amount) / max) * 100)}%` }}
|
||||
style={{ height: `${Math.max(2, (Math.max(0, r.amount) / max) * 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@@ -46,15 +46,16 @@ const MonthBars = ({ rows }: { rows: Row[] }) => {
|
||||
};
|
||||
|
||||
const HorizontalBars = ({ rows }: { rows: Row[] }) => {
|
||||
const ordered = [...rows].sort((a, b) => Math.abs(b.amount) - Math.abs(a.amount));
|
||||
const max = Math.max(...ordered.map((r) => Math.abs(r.amount)), 1);
|
||||
// Amounts are positive spending; a net refund (negative) sorts last with no bar.
|
||||
const ordered = [...rows].sort((a, b) => b.amount - a.amount);
|
||||
const max = Math.max(...ordered.map((r) => r.amount), 1);
|
||||
return (
|
||||
<ul className="flex flex-col gap-2.5">
|
||||
{ordered.map((r) => (
|
||||
<li key={r.key}>
|
||||
<div className="flex items-baseline justify-between gap-3 text-sm">
|
||||
<span className="truncate text-charcoal">{r.key}</span>
|
||||
<span className="shrink-0 font-semibold tabular-nums text-charcoal">
|
||||
<span className={`shrink-0 font-semibold tabular-nums ${r.amount < 0 ? "text-leaf-dark" : "text-charcoal"}`}>
|
||||
{formatMoney(r.amount)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -62,7 +63,7 @@ const HorizontalBars = ({ rows }: { rows: Row[] }) => {
|
||||
<div className="h-2 flex-1 rounded-full bg-sand-light/70 overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full bg-leaf"
|
||||
style={{ width: `${(Math.abs(r.amount) / max) * 100}%` }}
|
||||
style={{ width: `${(Math.max(0, r.amount) / max) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
{r.count != null && (
|
||||
|
||||
@@ -15,6 +15,7 @@ export const YnabSummary = ({ data }: RendererProps<YnabSummaryData>) => {
|
||||
/>
|
||||
<StatTile label="Income" value={formatMoney(data.income)} />
|
||||
<StatTile label="Assigned" value={formatMoney(data.assigned)} />
|
||||
{/* Positive for spending, like the spending report. */}
|
||||
<StatTile label="Spent" value={formatMoney(data.spent)} />
|
||||
<StatTile
|
||||
label="Net worth"
|
||||
|
||||
@@ -44,7 +44,8 @@ export function formatMonth(value: string | null | undefined): string {
|
||||
return formatDate(value, { month: "long", year: "numeric" });
|
||||
}
|
||||
|
||||
export function formatRange(start?: string, end?: string): string {
|
||||
export function formatRange(start?: string | null, end?: string | null): string {
|
||||
if (start && end) return `${formatDate(start)} – ${formatDate(end)}`;
|
||||
return formatDate(start ?? end);
|
||||
if (end) return `Through ${formatDate(end)}`;
|
||||
return start ? `From ${formatDate(start)}` : "";
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export type SourceRef = {
|
||||
export type MealieRecipeData = {
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
description: string | null;
|
||||
servings: string | number | null;
|
||||
prep_time: string | null;
|
||||
cook_time: string | null;
|
||||
@@ -19,7 +19,7 @@ export type MealieRecipeData = {
|
||||
ingredients: string[];
|
||||
instructions: { title?: string; text: string }[];
|
||||
tags: string[];
|
||||
image_url?: string;
|
||||
image_url?: string; // in the contract, not sent by the backend today
|
||||
};
|
||||
|
||||
export type MealieMealPlanData = {
|
||||
@@ -32,7 +32,7 @@ export type MealieMealPlanData = {
|
||||
};
|
||||
|
||||
export type MealieShoppingListsData = {
|
||||
lists: { name: string; items: { name: string; quantity: string | number | null; checked: boolean }[] }[];
|
||||
lists: { name: string; items: { name: string; quantity: number | string | null; checked: boolean }[] }[];
|
||||
};
|
||||
|
||||
export type ObsidianNoteData = {
|
||||
@@ -61,6 +61,7 @@ export type YnabSummaryData = {
|
||||
ready_to_assign: number;
|
||||
income: number;
|
||||
assigned: number;
|
||||
/** Positive for spending (-SUM of outflows). */
|
||||
spent: number;
|
||||
age_of_money?: number | null;
|
||||
net_worth: number;
|
||||
@@ -68,9 +69,11 @@ export type YnabSummaryData = {
|
||||
};
|
||||
|
||||
export type YnabTransactionsData = {
|
||||
start_date: string;
|
||||
/** Null for open-ended searches ("all time through end_date"). */
|
||||
start_date: string | null;
|
||||
end_date: string;
|
||||
count: number;
|
||||
/** Net of outflows and inflows. */
|
||||
total: number;
|
||||
transactions: {
|
||||
date: string;
|
||||
@@ -78,6 +81,7 @@ export type YnabTransactionsData = {
|
||||
category: string;
|
||||
account: string;
|
||||
memo?: string | null;
|
||||
/** Signed: negative = outflow. */
|
||||
amount: number;
|
||||
}[];
|
||||
};
|
||||
@@ -85,7 +89,8 @@ export type YnabTransactionsData = {
|
||||
export type YnabSpendingReportData = {
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
group_by: string;
|
||||
group_by: "category" | "category_group" | "payee" | "account" | "month" | string;
|
||||
/** amount is positive for spending (-SUM); a net refund comes out negative. */
|
||||
rows: { key: string; amount: number; count?: number }[];
|
||||
total: number;
|
||||
};
|
||||
@@ -97,6 +102,7 @@ export type YnabCategoryBudgetData = {
|
||||
categories: {
|
||||
name: string;
|
||||
assigned: number;
|
||||
/** Negative for spending. */
|
||||
activity: number;
|
||||
available: number;
|
||||
goal_target?: number | null;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"kind": "mealie.meal_plan",
|
||||
"title": "Meal plan, Oct 12–18",
|
||||
"source_ref": {
|
||||
"system": "mealie"
|
||||
},
|
||||
"title": "Meal plan · Oct 12–18",
|
||||
"data": {
|
||||
"start_date": "2026-10-12",
|
||||
"end_date": "2026-10-18",
|
||||
@@ -48,10 +45,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date": "2026-10-15",
|
||||
"meals": []
|
||||
},
|
||||
{
|
||||
"date": "2026-10-16",
|
||||
"meals": [
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
"Indian",
|
||||
"Weeknight",
|
||||
"Freezer friendly"
|
||||
],
|
||||
"image_url": "/mock-assets/tikka.svg"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"kind": "mealie.shopping_lists",
|
||||
"title": "Shopping lists",
|
||||
"source_ref": {
|
||||
"system": "mealie"
|
||||
},
|
||||
"data": {
|
||||
"lists": [
|
||||
{
|
||||
@@ -11,33 +8,33 @@
|
||||
"items": [
|
||||
{
|
||||
"name": "Chicken thighs",
|
||||
"quantity": "1.5 lb",
|
||||
"quantity": 1.5,
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"name": "Whole-milk yogurt",
|
||||
"quantity": 1,
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"name": "Crushed tomatoes (28 oz)",
|
||||
"quantity": 1,
|
||||
"quantity": 1.0,
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"name": "Heavy cream",
|
||||
"quantity": 1,
|
||||
"quantity": 1.0,
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"name": "Cilantro",
|
||||
"quantity": null,
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"name": "Tortillas",
|
||||
"quantity": "2 packs",
|
||||
"quantity": 2.0,
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"name": "Whole-milk yogurt",
|
||||
"quantity": 1.0,
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"name": "Cilantro",
|
||||
"quantity": 1.0,
|
||||
"checked": true
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -46,18 +43,18 @@
|
||||
"items": [
|
||||
{
|
||||
"name": "Cat litter",
|
||||
"quantity": 2,
|
||||
"quantity": 2.0,
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"name": "Olive oil",
|
||||
"quantity": 1.0,
|
||||
"checked": false
|
||||
},
|
||||
{
|
||||
"name": "Paper towels",
|
||||
"quantity": 1,
|
||||
"quantity": 1.0,
|
||||
"checked": true
|
||||
},
|
||||
{
|
||||
"name": "Olive oil",
|
||||
"quantity": 1,
|
||||
"checked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"kind": "obsidian.note",
|
||||
"title": "Simba vet visit 2026-09",
|
||||
"title": "2026-09 annual checkup",
|
||||
"source_ref": {
|
||||
"system": "obsidian",
|
||||
"path": "Simba/Vet/2026-09 annual checkup.md",
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"data": {
|
||||
"path": "Simba/Vet/2026-09 annual checkup.md",
|
||||
"title": "Simba vet visit 2026-09",
|
||||
"title": "2026-09 annual checkup",
|
||||
"tags": [
|
||||
"simba",
|
||||
"vet",
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
{
|
||||
"kind": "search.documents",
|
||||
"title": "Search: Simba vaccination records",
|
||||
"source_ref": {
|
||||
"system": "paperless"
|
||||
},
|
||||
"data": {
|
||||
"query": "Simba vaccination records",
|
||||
"source": "paperless",
|
||||
"results": [
|
||||
{
|
||||
"title": "Mission Pet Hospital invoice — Sep 2026",
|
||||
"title": "2026-09-18_mission-pet-hospital",
|
||||
"snippet": "Patient: Simba (feline, DSH, M/N). Services: annual wellness exam, FVRCP vaccine (3-year), fecal test. Next due: rabies 03/2027. Total $110.00.",
|
||||
"filename": "2026-09-18_mission-pet-hospital.pdf",
|
||||
"document_type": "Invoice",
|
||||
"date": "2026-09-18"
|
||||
},
|
||||
{
|
||||
"title": "Rabies vaccination certificate",
|
||||
"title": "rabies-certificate-2024",
|
||||
"snippet": "This certifies that Simba has been vaccinated against rabies. Vaccine: Purevax Feline Rabies, lot 3K91A. Date given 03/14/2024. Expires 03/14/2027.",
|
||||
"filename": "rabies-certificate-2024.pdf",
|
||||
"document_type": "Certificate",
|
||||
"date": "2024-03-14"
|
||||
},
|
||||
{
|
||||
"title": "Adoption packet — SF SPCA",
|
||||
"title": "2022-05-09_sf-spca-adoption",
|
||||
"snippet": "Simba, approx. 10 weeks. Vaccines given: FVRCP #1 (2022-05-02), deworming. Microchip 985112004567213 registered to adopter.",
|
||||
"path": "Simba/Adoption/SPCA packet.md",
|
||||
"document_type": "Note",
|
||||
"filename": "2022-05-09_sf-spca-adoption.pdf",
|
||||
"document_type": "Pet records",
|
||||
"date": "2022-05-09"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"kind": "ynab.category_budget",
|
||||
"title": "October 2026 category budget",
|
||||
"source_ref": {
|
||||
"system": "ynab"
|
||||
},
|
||||
"title": "Category budget · October 2026",
|
||||
"data": {
|
||||
"month": "2026-10",
|
||||
"groups": [
|
||||
|
||||
@@ -1,53 +1,55 @@
|
||||
{
|
||||
"kind": "ynab.spending_report",
|
||||
"title": "Spending by category, Sep 2026",
|
||||
"source_ref": {
|
||||
"system": "ynab"
|
||||
},
|
||||
"title": "Spending by category · Sep 1–30",
|
||||
"data": {
|
||||
"start_date": "2026-09-01",
|
||||
"end_date": "2026-09-30",
|
||||
"group_by": "category",
|
||||
"total": -3684.21,
|
||||
"total": 3642.21,
|
||||
"rows": [
|
||||
{
|
||||
"key": "Rent",
|
||||
"amount": -2400.0,
|
||||
"amount": 2400.0,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"key": "Groceries",
|
||||
"amount": -588.14,
|
||||
"amount": 588.14,
|
||||
"count": 14
|
||||
},
|
||||
{
|
||||
"key": "Dining Out",
|
||||
"amount": -243.7,
|
||||
"amount": 243.7,
|
||||
"count": 9
|
||||
},
|
||||
{
|
||||
"key": "Electric",
|
||||
"amount": -97.55,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"key": "Pet Care",
|
||||
"amount": -148.0,
|
||||
"amount": 148.0,
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"key": "Electric",
|
||||
"amount": 97.55,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"key": "Transportation",
|
||||
"amount": -74.32,
|
||||
"amount": 74.32,
|
||||
"count": 5
|
||||
},
|
||||
{
|
||||
"key": "Internet",
|
||||
"amount": -70.0,
|
||||
"amount": 70.0,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"key": "Pet Food",
|
||||
"amount": -62.5,
|
||||
"amount": 62.5,
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"key": "Clothing",
|
||||
"amount": -42.0,
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
{
|
||||
"kind": "ynab.summary",
|
||||
"title": "October 2026 budget summary",
|
||||
"source_ref": {
|
||||
"system": "ynab"
|
||||
},
|
||||
"title": "Budget · October 2026",
|
||||
"data": {
|
||||
"month": "2026-10",
|
||||
"ready_to_assign": 412.37,
|
||||
"income": 7850.0,
|
||||
"assigned": 7437.63,
|
||||
"spent": -3921.48,
|
||||
"spent": 3921.48,
|
||||
"age_of_money": 41,
|
||||
"net_worth": 84213.92,
|
||||
"overspent": [
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"kind": "ynab.transactions",
|
||||
"title": "Dining out, last 30 days",
|
||||
"source_ref": {
|
||||
"system": "ynab"
|
||||
},
|
||||
"title": "Transactions · Sep 12–Oct 12",
|
||||
"data": {
|
||||
"start_date": "2026-09-12",
|
||||
"end_date": "2026-10-12",
|
||||
@@ -15,7 +12,6 @@
|
||||
"payee": "Tartine Bakery",
|
||||
"category": "Dining Out",
|
||||
"account": "Chase Sapphire",
|
||||
"memo": null,
|
||||
"amount": -18.75
|
||||
},
|
||||
{
|
||||
@@ -31,7 +27,6 @@
|
||||
"payee": "Philz Coffee",
|
||||
"category": "Dining Out",
|
||||
"account": "Checking",
|
||||
"memo": null,
|
||||
"amount": -6.25
|
||||
},
|
||||
{
|
||||
@@ -39,7 +34,6 @@
|
||||
"payee": "Souvla",
|
||||
"category": "Dining Out",
|
||||
"account": "Chase Sapphire",
|
||||
"memo": null,
|
||||
"amount": -34.1
|
||||
},
|
||||
{
|
||||
@@ -63,7 +57,6 @@
|
||||
"payee": "Philz Coffee",
|
||||
"category": "Dining Out",
|
||||
"account": "Checking",
|
||||
"memo": null,
|
||||
"amount": -6.25
|
||||
},
|
||||
{
|
||||
@@ -71,7 +64,6 @@
|
||||
"payee": "Golden Boy Pizza",
|
||||
"category": "Dining Out",
|
||||
"account": "Chase Sapphire",
|
||||
"memo": null,
|
||||
"amount": -29.6
|
||||
},
|
||||
{
|
||||
@@ -79,7 +71,6 @@
|
||||
"payee": "Tartine Bakery",
|
||||
"category": "Dining Out",
|
||||
"account": "Chase Sapphire",
|
||||
"memo": null,
|
||||
"amount": -12.75
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user