docs(plans): purchasing UI tweaks — 4 small tasks with TDD-friendly bite-sized steps

This commit is contained in:
DEls 2026-08-26 23:58:30 +02:00
parent 7020449853
commit a8e67faf3d

View file

@ -0,0 +1,713 @@
# Purchasing UI tweaks Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make four small UI/data fixes to the Purchasing & Deliveries dashboard: shorter upload button labels, pending-DN visual styling in two places, a "View PO PDF" button on PO detail, and a fulfillment-rate data fix.
**Architecture:** All four changes are presentational or one-line data-fetching tweaks. No new components (reuse `SlipPreview`), no new state machines, no new routes. Server mappers thread two extra fields (`processingStatus`, `purchaseOrderFile`); the existing view components grow minor branches.
**Tech Stack:** Next.js 16 App Router, React 19, Radix Dialog, Tailwind CSS, PocketBase admin SDK.
---
## File Structure
**Modify:**
- `src/components/upload-purchase-order-button.tsx` — button label only.
- `src/components/upload-delivery-note-button.tsx` — button label only.
- `src/app/dashboard/purchasing/page.tsx` — `received_items` query: `getList(1, 500, ...)` → `getFullList(...)`.
- `src/app/dashboard/purchasing/purchasing-view.tsx` — `PoListItem` shows amber treatment + inline "Pending DNs" badge when `pendingDnCount > 0`.
- `src/app/dashboard/purchasing/[poId]/page.tsx` — server mapper: thread `processingStatus` and `purchaseOrderFile` to `PoDetail`.
- `src/app/dashboard/purchasing/[poId]/po-detail-view.tsx` — extend `PoDetailDn` (add `processingStatus`), add status pill on `DeliveryNoteCard`, extend `PoDetail` (add `purchaseOrderFile`), add `View PO PDF` button + dialog in the PO header.
**No new files.** All changes reuse `SlipPreview` and existing palette tokens.
---
## Task 1: Shorter upload button labels
**Files:**
- Modify: `src/components/upload-purchase-order-button.tsx` (1 line)
- Modify: `src/components/upload-delivery-note-button.tsx` (1 line)
- Modify: `src/app/dashboard/purchasing/page.tsx` (no change needed — `flex-wrap` already there; just verify)
The third file needs no edit: `page.tsx:174` already uses `flex flex-wrap`. Shorter labels push the wrap point down — on common phone widths (375–414px) the two buttons will fit on one row, only stacking on truly tiny screens. No code change required there.
- [ ] **Step 1: Edit `src/components/upload-purchase-order-button.tsx` button label**
Find the `<Button>` at the start of the component return (around line 431–434):
```tsx
<Button onClick={() => setOpen(true)} className="gap-2">
<Upload className="h-4 w-4" />
Upload purchase order
</Button>
```
Replace the inner text only:
```tsx
<Button onClick={() => setOpen(true)} className="gap-2">
<Upload className="h-4 w-4" />
Upload PO
</Button>
```
- [ ] **Step 2: Edit `src/components/upload-delivery-note-button.tsx` button label**
Find the `<Button>` at the start of the component return (around line 136–139):
```tsx
<Button onClick={() => setOpen(true)} className="gap-2">
<Upload className="h-4 w-4" />
Upload delivery note
</Button>
```
Replace the inner text only:
```tsx
<Button onClick={() => setOpen(true)} className="gap-2">
<Upload className="h-4 w-4" />
Upload DN
</Button>
```
- [ ] **Step 3: Verify typecheck**
Run:
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE && npx tsc --noEmit
```
Expected: exit 0. Pure label change, no type impact.
- [ ] **Step 4: Commit**
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE
git add src/components/upload-purchase-order-button.tsx src/components/upload-delivery-note-button.tsx
git -c user.email=DEls@local -c user.name=DEls commit -m "feat(purchasing): shorten upload button labels (Upload PO / Upload DN)"
```
---
## Task 2: Pending-DN styling (list card + detail rows)
Two files modified in this task; one of them (`[poId]/page.tsx`) is touched again in Task 3 to add `purchaseOrderFile`, but `processingStatus` is independent and committed here.
**Files:**
- Modify: `src/app/dashboard/purchasing/[poId]/page.tsx` (extend `dnRows` mapper)
- Modify: `src/app/dashboard/purchasing/purchasing-view.tsx` (`PoListItem` amber treatment)
- Modify: `src/app/dashboard/purchasing/[poId]/po-detail-view.tsx` (`PoDetailDn` type + `DeliveryNoteCard` pill)
- [ ] **Step 1: Extend the server mapper in `[poId]/page.tsx`**
In `src/app/dashboard/purchasing/[poId]/page.tsx`, find the `dnRows` mapper (around line 83–98):
```tsx
const dnRows = dns.map((d) => ({
id: d.id,
deliveryNoteNumber: d.delivery_note_number,
projectCode: d.project_code,
dateReceived: d.date_received,
receivedBy: d.received_by,
isVerified: !!d.is_verified,
slipImage: d.slip_image,
rawOcrData: d.raw_ocr_data ?? null,
receivedItems: (receivedByDn.get(d.id) ?? []).map((r) => ({
id: r.id,
purchaseOrderItemId: r.purchase_order_item,
qtyReceived: r.qty_received,
conditionNotes: r.condition_notes,
})),
}));
```
Add `processingStatus` after `rawOcrData`:
```tsx
const dnRows = dns.map((d) => ({
id: d.id,
deliveryNoteNumber: d.delivery_note_number,
projectCode: d.project_code,
dateReceived: d.date_received,
receivedBy: d.received_by,
isVerified: !!d.is_verified,
slipImage: d.slip_image,
rawOcrData: d.raw_ocr_data ?? null,
processingStatus: d.processing_status ?? "",
receivedItems: (receivedByDn.get(d.id) ?? []).map((r) => ({
id: r.id,
purchaseOrderItemId: r.purchase_order_item,
qtyReceived: r.qty_received,
conditionNotes: r.condition_notes,
})),
}));
```
- [ ] **Step 2: Extend `PoDetailDn` type in `po-detail-view.tsx`**
In `src/app/dashboard/purchasing/[poId]/po-detail-view.tsx`, find the `PoDetailDn` interface (around line 40–55):
```tsx
export interface PoDetailDn {
id: string;
deliveryNoteNumber: string;
projectCode: string;
dateReceived: string;
receivedBy: string;
isVerified: boolean;
slipImage: string;
rawOcrData: unknown;
receivedItems: {
```
Add `processingStatus: string;` after `rawOcrData`:
```tsx
export interface PoDetailDn {
id: string;
deliveryNoteNumber: string;
projectCode: string;
dateReceived: string;
receivedBy: string;
isVerified: boolean;
slipImage: string;
rawOcrData: unknown;
processingStatus: string;
receivedItems: {
```
- [ ] **Step 3: Render the status pill in `DeliveryNoteCard`**
In `src/app/dashboard/purchasing/[poId]/po-detail-view.tsx`, find the `DeliveryNoteCard` component's `<p>` showing the DN number (around line 339–342):
```tsx
<div>
<p className="font-mono text-sm font-semibold">
{dn.deliveryNoteNumber || "(no DN number)"}
</p>
```
Replace with:
```tsx
<div>
<div className="flex flex-wrap items-center gap-2">
<p className="font-mono text-sm font-semibold">
{dn.deliveryNoteNumber || "(no DN number)"}
</p>
<DnStatusPill status={dn.processingStatus} />
</div>
```
Add a small `DnStatusPill` helper above `DeliveryNoteCard` (just above the `DeliveryNoteCard` function — it lives near the end of the file before `VerifyButton`). Insert this helper:
```tsx
function DnStatusPill({ status }: { status: string }) {
if (status === "needs-review" || status === "pending") {
return (
<Badge
variant="outline"
className="gap-1 border-amber-500/40 bg-amber-500/15 text-amber-700 dark:text-amber-300"
>
<AlertTriangle className="h-3 w-3" />
{status === "needs-review" ? "Needs review" : "Pending"}
</Badge>
);
}
if (status === "processed") {
return (
<Badge
variant="outline"
className="border-border bg-muted/40 text-muted-foreground"
>
Processed
</Badge>
);
}
return null;
}
```
`Badge` and `AlertTriangle` are already imported at the top of the file — no import changes needed.
- [ ] **Step 4: Add amber treatment to `PoListItem` in `purchasing-view.tsx`**
In `src/app/dashboard/purchasing/purchasing-view.tsx`, find `PoListItem` (around line 303–357):
```tsx
function PoListItem({ po }: { po: PoRow }) {
const href = `${PO_BASE}/${po.id}`;
return (
<li>
<Link
href={href}
className="group flex items-center gap-3 px-4 py-3 transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#F2B165]/40"
>
<span
aria-hidden
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-[#F2B165]/15"
>
<Package className="h-3.5 w-3.5 text-[#F2B165]" />
</span>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<span className="font-mono text-sm font-semibold">{po.poNumber}</span>
<span className="truncate text-sm text-foreground">
{po.supplier || "—"}
</span>
<Badge variant="outline" className="font-mono text-xs">
{po.projectCode || "—"}
</Badge>
{po.requiredByDate && (
<span className="text-xs text-muted-foreground">
Required {format(parseISO(po.requiredByDate), "yyyy-MM-dd")}
</span>
)}
</div>
<p className="mt-0.5 text-xs text-muted-foreground">
{po.itemCount} line item{po.itemCount === 1 ? "" : "s"} ·{" "}
{po.deliveryNoteCount} delivery note
{po.deliveryNoteCount === 1 ? "" : "s"}
{po.deliveryNoteCount > 0 &&
` (${po.deliveryNoteVerified} verified)`}
</p>
</div>
```
Replace the whole `PoListItem` body (everything from the `function PoListItem` declaration through `</Link>` and `</li>`) with:
```tsx
function PoListItem({ po }: { po: PoRow }) {
const href = `${PO_BASE}/${po.id}`;
const hasPending = po.pendingDnCount > 0;
return (
<li>
<Link
href={href}
className={cn(
"group relative flex items-center gap-3 px-4 py-3 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#F2B165]/40",
hasPending
? "border-l-2 border-amber-500/60 bg-amber-500/5 hover:bg-amber-500/10"
: "hover:bg-muted/40",
)}
>
<span
aria-hidden
className={cn(
"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md",
hasPending ? "bg-amber-500/20" : "bg-[#F2B165]/15",
)}
>
<Package
className={cn(
"h-3.5 w-3.5",
hasPending ? "text-amber-700 dark:text-amber-300" : "text-[#F2B165]",
)}
/>
</span>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<span className="font-mono text-sm font-semibold">{po.poNumber}</span>
<span className="truncate text-sm text-foreground">
{po.supplier || "—"}
</span>
<Badge variant="outline" className="font-mono text-xs">
{po.projectCode || "—"}
</Badge>
{po.requiredByDate && (
<span className="text-xs text-muted-foreground">
Required {format(parseISO(po.requiredByDate), "yyyy-MM-dd")}
</span>
)}
{hasPending && (
<Badge
variant="outline"
className="gap-1 border-amber-500/40 bg-amber-500/15 text-amber-700 dark:text-amber-300"
>
<AlertTriangle className="h-3 w-3" />
Pending DNs: {po.pendingDnCount}
</Badge>
)}
</div>
<p className="mt-0.5 text-xs text-muted-foreground">
{po.itemCount} line item{po.itemCount === 1 ? "" : "s"} ·{" "}
{po.deliveryNoteCount} delivery note
{po.deliveryNoteCount === 1 ? "" : "s"}
{po.deliveryNoteCount > 0 &&
` (${po.deliveryNoteVerified} verified)`}
</p>
</div>
```
(The rest of the component — FulfilmentBar / totalValue / VerifyBadge / arrow — stays unchanged.)
`AlertTriangle` is already imported at the top of the file (used by `VerifyBadge`). `cn` is already imported. No import changes needed.
- [ ] **Step 5: Verify typecheck**
Run:
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE && npx tsc --noEmit
```
Expected: exit 0.
- [ ] **Step 6: Commit**
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE
git add src/app/dashboard/purchasing/\[poId\]/page.tsx src/app/dashboard/purchasing/\[poId\]/po-detail-view.tsx src/app/dashboard/purchasing/purchasing-view.tsx
git -c user.email=DEls@local -c user.name=DEls commit -m "feat(purchasing): amber treatment for POs/DNs with pending status"
```
---
## Task 3: View PO PDF button on PO detail page
**Files:**
- Modify: `src/app/dashboard/purchasing/[poId]/page.tsx` (extend `PoDetail` mapper)
- Modify: `src/app/dashboard/purchasing/[poId]/po-detail-view.tsx` (extend `PoDetail` type, add button + dialog)
- [ ] **Step 1: Extend `PoDetail` type and server mapper**
In `src/app/dashboard/purchasing/[poId]/page.tsx`, find the `detail` mapper (around line 103–118):
```tsx
const detail: PoDetail = {
id: po.id,
poNumber: po.purchase_order_number,
projectCode: po.project_code,
projectName: po.expand?.site?.name ?? "",
supplier: po.supplier,
requiredByDate: po.required_by_date,
orderedBy: po.ordered_by,
totalValue: po.total_value,
isVerified: !!po.is_verified,
items: itemRows,
deliveryNotes: dnRows,
fulfillmentPct,
totalOrdered,
totalReceived,
};
```
Add `purchaseOrderFile` after `totalValue`:
```tsx
const detail: PoDetail = {
id: po.id,
poNumber: po.purchase_order_number,
projectCode: po.project_code,
projectName: po.expand?.site?.name ?? "",
supplier: po.supplier,
requiredByDate: po.required_by_date,
orderedBy: po.ordered_by,
totalValue: po.total_value,
purchaseOrderFile: po.purchase_order_file ?? "",
isVerified: !!po.is_verified,
items: itemRows,
deliveryNotes: dnRows,
fulfillmentPct,
totalOrdered,
totalReceived,
};
```
In `src/app/dashboard/purchasing/[poId]/po-detail-view.tsx`, find the `PoDetail` interface (around line 57–72):
```tsx
export interface PoDetail {
id: string;
poNumber: string;
projectCode: string;
projectName: string;
supplier: string;
requiredByDate: string;
orderedBy: string;
totalValue: number;
isVerified: boolean;
items: PoDetailItem[];
deliveryNotes: PoDetailDn[];
fulfillmentPct: number;
totalOrdered: number;
totalReceived: number;
}
```
Add `purchaseOrderFile: string;` after `totalValue`:
```tsx
export interface PoDetail {
id: string;
poNumber: string;
projectCode: string;
projectName: string;
supplier: string;
requiredByDate: string;
orderedBy: string;
totalValue: number;
purchaseOrderFile: string;
isVerified: boolean;
items: PoDetailItem[];
deliveryNotes: PoDetailDn[];
fulfillmentPct: number;
totalOrdered: number;
totalReceived: number;
}
```
- [ ] **Step 2: Add a `ViewPoPdfButton` to the PO header**
In `src/app/dashboard/purchasing/[poId]/po-detail-view.tsx`, find the PO header right column (around line 168–182):
```tsx
<div className="flex flex-col items-end gap-2">
<VerifyButton
collection="purchase_orders"
id={detail.id}
verified={detail.isVerified}
onDone={refresh}
/>
<div className="text-right">
<p className="font-semibold tabular-nums">
{formatCurrency(detail.totalValue)}
</p>
<p className="text-xs text-muted-foreground">PO value</p>
</div>
</div>
```
Replace with:
```tsx
<div className="flex flex-col items-end gap-2">
<div className="flex items-center gap-2">
{detail.purchaseOrderFile && (
<ViewPoPdfButton
filename={detail.purchaseOrderFile}
recordId={detail.id}
poNumber={detail.poNumber}
/>
)}
<VerifyButton
collection="purchase_orders"
id={detail.id}
verified={detail.isVerified}
onDone={refresh}
/>
</div>
<div className="text-right">
<p className="font-semibold tabular-nums">
{formatCurrency(detail.totalValue)}
</p>
<p className="text-xs text-muted-foreground">PO value</p>
</div>
</div>
```
- [ ] **Step 3: Add the `ViewPoPdfButton` component**
Add the imports first. Find the existing import block at the top of `po-detail-view.tsx`:
```tsx
import {
AlertTriangle,
Banknote,
CheckCircle2,
ClipboardCheck,
ImageIcon,
Package,
PackageCheck,
Truck,
} from "lucide-react";
```
Add `FileText` to the list:
```tsx
import {
AlertTriangle,
Banknote,
CheckCircle2,
ClipboardCheck,
FileText,
ImageIcon,
Package,
PackageCheck,
Truck,
} from "lucide-react";
```
Now add the `ViewPoPdfButton` component definition. Place it just above the `VerifyButton` component at the bottom of the file. Insert this:
```tsx
function ViewPoPdfButton({
filename,
recordId,
poNumber,
}: {
filename: string;
recordId: string;
poNumber: string;
}) {
const [open, setOpen] = React.useState(false);
return (
<>
<Button
variant="outline"
size="sm"
className="gap-1.5"
onClick={() => setOpen(true)}
>
<FileText className="h-3.5 w-3.5" />
View PO PDF
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>{poNumber}</DialogTitle>
</DialogHeader>
<SlipPreview filename={filename} recordId={recordId} />
</DialogContent>
</Dialog>
</>
);
}
```
- [ ] **Step 4: Verify typecheck**
Run:
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE && npx tsc --noEmit
```
Expected: exit 0.
- [ ] **Step 5: Commit**
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE
git add src/app/dashboard/purchasing/\[poId\]/page.tsx src/app/dashboard/purchasing/\[poId\]/po-detail-view.tsx
git -c user.email=DEls@local -c user.name=DEls commit -m "feat(po-detail): add View PO PDF button using existing SlipPreview"
```
---
## Task 4: Fulfillment rate data fix
**Files:**
- Modify: `src/app/dashboard/purchasing/page.tsx` (replace `getList(1, 500, ...)` with `getFullList(...)`)
Before the edit, do a quick data investigation to confirm the orphan-row hypothesis. The other two causes (cap and listRule) are fixed by the same code change; the orphans are separate.
- [ ] **Step 1: Investigate orphan `received_items` rows**
This step needs PocketBase admin token access. Run:
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE && python3 <<'EOF'
import urllib.request, json, re
token = re.search(r'POCKETBASE_ADMIN_TOKEN=([^\s]+)', open('.env.local').read()).group(1)
def fetch(p):
return json.loads(urllib.request.urlopen(urllib.request.Request(
f'https://pb.mega.infra.apphub.co.za{p}',
headers={'Authorization': f'Bearer {token}'})).read())
ri = fetch('/api/collections/received_items/records?fields=id,purchase_order_item,qty_received&perPage=500')['items']
total = len(ri)
orphans = [r for r in ri if not r.get('purchase_order_item')]
print(f'received_items rows: {total}')
print(f'orphans (empty purchase_order_item): {len(orphans)} ({len(orphans)/total*100:.1f}%)')
EOF
```
Note the orphan percentage. If it's small (<5%), the orphan rows aren't the user's reported bug — the cause is the cap or listRule (which the code change below addresses). If it's large (>5%), note it for the follow-up banner — but still apply the code change below to eliminate the cap.
- [ ] **Step 2: Replace `getList` with `getFullList`**
In `src/app/dashboard/purchasing/page.tsx`, find this block (around line 26–36):
```tsx
// Authoritative source for received quantities. The list page
// used to read `purchase_order_items.qty_received` (a denormalized
// column set at ingest and never updated), which is why every PO
// showed 0% completion. The detail page sums from received_items
// and is correct; the list now does the same.
// NOTE: getList caps at 500 rows. If received_items grows past
// that, switch to getFullList or push the rollup server-side.
pb.collection("received_items").getList(1, 500, {
fields: "id,purchase_order_item,qty_received",
}),
```
Replace with:
```tsx
// Authoritative source for received quantities. The list page
// used to read `purchase_order_items.qty_received` (a denormalized
// column set at ingest and never updated), which is why every PO
// showed 0% completion. The detail page sums from received_items
// and is correct; the list does the same.
//
// `getFullList` (not `getList`) — the old `getList(1, 500, ...)`
// silently truncated once received_items exceeded 500 rows, which
// hid fulfillment for later POs and is exactly the bug the user
// reported. `getFullList` walks PB's pagination internally.
pb.collection("received_items").getFullList({
fields: "id,purchase_order_item,qty_received",
}),
```
- [ ] **Step 3: Verify typecheck + tests**
Run:
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE && npx tsc --noEmit && npm run test:run 2>&1 | tail -15
```
Expected: `tsc` exit 0; tests show `1 failed | 188 passed (189)` baseline (pre-existing `process-batch/route.test.ts` failure is unrelated; no new failures).
- [ ] **Step 4: Live verification — fulfillment reflects real data**
The dev server should be running. Verify it:
```bash
curl -sS -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:6004/dashboard/purchasing
```
If it's not running, start it:
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE
set -a && source .env.local && set +a
N8N_INGEST_SECRET=test-secret N8N_INGEST_TOKEN=test-token npm run dev
```
Open `http://127.0.0.1:6004/dashboard/purchasing` in Playwright. Log in (PIN 2-4-6-8). On the PO list, check the "Fulfillment rate" KPI tile and the per-PO FulfilmentBars — they should show non-zero values for any PO with `received_items` rows. Cross-check against the PO detail page's per-item table (the detail page sums from the same `received_items` data so both should agree).
- [ ] **Step 5: Commit**
```bash
cd /Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE
git add src/app/dashboard/purchasing/page.tsx
git -c user.email=DEls@local -c user.name=DEls commit -m "fix(purchasing): use getFullList for received_items to fix 0% fulfillment truncation"
```
---
## Self-review
- **Spec coverage:**
- "Shorter upload labels" → Task 1.
- "Pending-DN styling (list card + detail rows)" → Task 2.
- "View PO PDF button on detail page" → Task 3.
- "Fulfillment rate bug" → Task 4.
- "Reuses SlipPreview" → Task 3 step 3 (ViewPoPdfButton body).
- "No new files" → confirmed; all four tasks are edits.
- **Placeholder scan:** none — every code block is complete and runnable.
- **Type consistency:**
- `PoDetail` interface adds `purchaseOrderFile: string;`; server mapper sets it from `po.purchase_order_file ?? ""` — types match.
- `PoDetailDn` interface adds `processingStatus: string;`; server mapper sets it from `d.processing_status ?? ""` — types match.
- `ViewPoPdfButton` props (`{ filename, recordId, poNumber }`) match the JSX usage and the `<SlipPreview filename={filename} recordId={recordId} />` call (lowercase `filename`, matching `SlipPreview`'s prop name).
- `SlipPreview`'s prop is `filename` — confirmed by reading the existing `DeliveryNoteCard` callsite which uses `filename={dn.slipImage} recordId={dn.id}`.
- **Drag-along:** The original `DeliveryNoteCard` `<SlipPreview>` callsite already uses `filename={dn.slipImage} recordId={dn.id}` — confirmed by re-reading the existing file. So `filename` is the correct prop name everywhere.