Dev updates

Changelog

What changed in Zedi and when, written from the commit log by the one person who writes the commits. Each entry lists work that is merged on the dev branch as of that date. Production trails dev by a bit, so promotions are called out on their own line.

Found a bug or want something moved up? Email zedi@sneeks.ai.

  1. Bank sync grows up, use-case pages, and a partners page

    • Bank sync (SimpleFIN, still development only): a connection can sync itself once a day after 10:00 UTC, switched on per connection from Settings > Connections & keys. The card shows the last manual and automatic sync, pending charges the bank has not posted yet, a history of recent syncs, and a one-click Categorize button for synced rows the rules missed. Reconnecting or remapping a bank account no longer re-reads up to 89 days, and a statement uploaded for a period already synced is caught as a duplicate even though the descriptions differ.
    • New users who can use bank sync get a Connect your bank step during onboarding, the import page points them to Connections & keys, and the Accounts page marks synced accounts with their last sync time.
    • Use-case pages under /for, one each for splitting receipts with friends, Venmo and Zelle reconciliation, statement import without a bank login, itemized grocery tracking and PDF statement import. A /partners page explains how ?ref= credit works and what Zedi will never do with your data.
    • Landing page: Venmo CSV import is listed as shipped instead of coming soon, and the Pro card no longer promises priority support. Nothing about pricing changed.
    • Admins can see signups, first imports and Pro conversions per referral tag and per source for the last 30 or 90 days.
  2. Duplicate detection across formats and merchant renames everywhere

    • A transaction imported once from a PDF statement and again from a CSV or QFX export is now caught as a duplicate. Whole-dollar amounts from PDFs used to be compared as 400 against 400.00, so the second import slipped through; stored rows were rebuilt so earlier imports match too.
    • Merchant renames you told Zedi to remember now apply to every import, including PDF statements that commit automatically, not only files reviewed in the staging station. A rename you make while staging still wins, and resetting a merchant keeps the name from the statement.
    • Removing the sample data now removes the demo accounts as well, and only ever touches your own rows.
    • People: adding a person saves their nickname, emoji, notes and handles in one step, so a failure partway never leaves a half-made person. The Owed to you card on the dashboard shows each person's nickname and emoji avatar.
    • Signup links shared with a ?ref= tag are credited even when they point at the landing page or the changelog instead of the signup page.
  3. Import staging, transfer links, and a People ledger

    • Import is a staging station. Every file you drop (CSV, QFX/OFX or PDF, one or many) is staged for review: edit the date, merchant, description, category and amount row by row, hide rows, or include a flagged duplicate on purpose, then send one file or all ready files. Staged files survive navigating away; a refresh clears them.
    • Renaming a merchant while staging can apply to every matching row in the batch and be remembered for future imports.
    • Transfers: Venmo cash-outs and top-ups are linked to the matching bank row after every import and moved to the Transfer category, so they stop counting as spending or income. A review card confirms or rejects each link, and rejecting restores the original categories. The import page has a Scan for transfers button for rows imported earlier.
    • Rule learning: the first time you change a transaction's category, Zedi learns a rule for that merchant and shows a toast with Undo and an option to apply it to past transactions. Learned rules are listed in Settings.
    • People: give each person a colour, an emoji avatar and a nickname, and add, archive or restore people from the People tab. Balances split into Owed and Settled, and money a person sends you pays off their oldest open receipt share first.
    • Receipts: items are categorized automatically when a scan finishes. HEIC and AVIF photos, the iPhone default, are converted in the browser instead of failing.
    • PDF statements: when an extracted amount comes back with the wrong sign, the statement's printed daily balances are used to correct it.
    • Real Venmo statement exports import cleanly: the header is found, and balance rows and bank transfers stay out of the preview and the review tray.
    • Account security: an account is locked after 10 failed sign-ins within 15 minutes, sessions end after 7 days idle or 30 days at most, new passwords have a minimum length, and the Apple button stays hidden until Apple sign-in is configured. Database checks now make sure every linked record, such as a category, person or statement, belongs to the same account as the row that points at it.
  4. Production update: the import overhaul and a week of fixes

    • Production now runs everything in the 2026-09-06 and 2026-09-07 entries except investments, which stays in development: background PDF extraction, People and the Venmo review tray, sign-in connections, and email verification for new signups.
    • A failed PDF extraction can be retried from the archived PDF, and failed statements keep their PDF for 14 days.
    • Password-protected PDFs are rejected before they are sent for extraction, and a PDF batch that needs more quota than you have left fails right away instead of partway through.
    • Categorization rules normalize their patterns when saved so they match, and characters like % and _ in a pattern no longer act as wildcards.
    • The receipt upload dialog shows your remaining scans, and there is a feedback button inside the app.
    • Signup asks how you heard about Zedi.
    • The landing page says which Claude model reads your uploads and what it is sent. The terms cap liability at the fees paid in the prior 12 months.
    • On phones the dashboard summary cards stack as rows so values never truncate, and the analytics donut shows readable merchant names.
    • Forward-by-email ingest checks sender authentication (SPF, DKIM, DMARC) and is rate limited. It stays hidden until the inbound worker is deployed.
  5. Mobile menu, this page, and small fixes

    • The marketing site has a mobile menu. Below the desktop breakpoint the section links moved into a slide-out sheet behind a hamburger, so phone visitors can reach Product, Compare, Pricing and FAQ.
    • This changelog. Entries live in the repo next to the code, and this page is where dev updates get posted from now on.
    • Import: when a PDF extraction succeeds but the automatic commit fails, the extracted preview is kept so you can review and commit it by hand instead of losing the work.
    • People: the person detail page links back to the People list.
    • Landing page copy corrected: forward-by-email gives you one inbound address for your whole account, not one per bank.
    • CI now runs on every push and pull request to the dev branch, and the Docker build contexts got a .dockerignore.
  6. Import overhaul: PDF extraction runs in the background

    • PDF statement extraction is a background job. Upload returns right away and the import page polls the statement until the preview is ready, so a long statement no longer times out behind the web proxy. The copy no longer promises a 30 second extraction.
    • Stuck and abandoned statements are swept automatically: an extraction that never finished is marked failed, and a preview nobody committed expires.
    • QFX rows now carry the same fingerprint as CSV rows, so a transaction imported once as CSV and again as QFX is deduplicated across formats.
    • Reconciliation counts interest lines, and the extraction request keeps its schema key order, which cut extraction latency by a wide margin.
    • The import page was split into smaller components: the PDF panel with polling, the statements table, and the import history table.
  7. Every feature branch merged onto dev

    • Investments, in development and not yet in production: Schwab brokerage CSV exports (activity history and positions snapshots) import into a dedicated investments domain instead of the transactions table, with a new Investments page showing holdings, activity, allocation and value over time. The net position card buckets investment balances separately from cash.
    • People: Venmo and Zelle counterparties are tagged during CSV import, each person shows a net balance, unmatched names land in a review tray on the import page, and receipt splits get a Request via Venmo button. Venmo exports are detected by header and there is a Venmo account type.
    • Forward-by-email ingest, prototype: a private inbound address per account, an approved-sender list, and an activity log under Settings, Connections. The inbound worker is not deployed yet, and the card hides itself until an ingest domain is configured.
    • Settings, Connections: connect and disconnect Google and Apple sign-in on an existing account.
    • New signups must verify their email address before reaching the dashboard, with a dedicated page explaining the wait.
    • Theme: an info status color joins success, warning and danger across all 14 themes, and the import page stopped using raw palette classes.
    • A Yahoo Finance client and an internal-secret middleware are in place for the upcoming daily price refresh; the refresh route itself is still being rebuilt on the investments tables. Nothing prices holdings yet.
  8. Hotfix: intermittent 500s on ordinary requests

    • Fixed bursts of 500 errors that could hit any signed-in request, including PDF imports that failed with "internal server" and no log line. The backend fetched the signing keys for session tokens once per concurrent request when its cache expired and tripped the auth rate limit. Refreshes are now single-flight, a failed refresh serves the last good key set for 15 seconds, and the keys endpoint gets the same rate-limit rule as session checks.
    • Deleting an account that had PDF statements no longer fails: the migration that cascades import runs on statement delete existed on disk but was never registered with the migration runner. It is now.
    • The receipt library is paginated, 20 receipts per page, with the total counted under the same filters as the page.
    • Receipts: the store name input no longer stretches the card while editing.
    • Shipped to production the same evening.
  9. Filter-aware overview and analytics filters

    • Transactions: the Overview tiles follow whatever filter is active (category, search, type, amount, hidden, review flags) and are labeled Filtered when they do.
    • Analytics: a category filter including Uncategorized, plus include and exclude modes on both the category and account dimensions, applied to the KPIs, spending mix, trends, merchants and the pivot card. Choices persist locally and mirror into the mobile filter sheet.
    • Analytics: the spending donut labels its slices and wraps its legend instead of clipping it.
    • Import: the PDF statements table shows which account each statement targets.
  10. Rendering sweep

    • Dashboard KPI sparkline tooltips are no longer clipped by their card.
    • The Uncategorized filter on transactions works again.
    • Assorted rendering fixes across the signed-in app.
  11. Email verification, durable Stripe webhooks, rate limits

    • Email verification is enforced for new accounts.
    • Stripe webhooks are recorded before they are processed, so a failure during processing can be retried instead of losing a subscription change.
    • Auth rate limiting works as configured, and the API has its own rate limits.
    • Error boundaries around the signed-in app, and database pool sizing tuned for the hosted environment.