Roadmap
Planned
- The 2025 purchase documents from the tax folder are in the app: 221 under Whiz-Art Media (241 receipts with the 20 that were there), 12 private ones under "marlinjai". Two files were already present and were not added again. Missing dates were filled from the tax session's table, and assignment and share were set from its draft expense table. Done 2026-10-10.
- Shares the assignment column cannot hold are set: 50 percent business plus 30 percent study on the 12 Telekom home internet and the 12 Notion invoices (per receipt, in the finance area, study part on "Fortbildungskosten"), and 50 percent study on the 12 invoices of the older mobile contract (Marlin's answers of 2026-10-06 to the tax decision page). Done 2026-10-10.
- Sub rows in the receipts table, like sub-items in Notion: a group Marlin creates (a parent
row as a container, its receipts underneath, the parent showing their sum and never counting
as a receipt itself). Built with
@marlinjai/data-table-react0.6.0. Clicked through on production on 2026-10-11: a group made from two receipts, both drawn underneath it, the section count and the 241 items unchanged, folding, the state kept over a reload, one receipt taken out, the group dissolved with its confirmation and both receipts back at the top level. Plan: receipt sub rows. Done 2026-10-11. - A group's sum no longer looks complete when it is not: next to the number the group says
how many of its receipts the sum covers ("(1 von 2)"), decided by Marlin on 2026-10-11. With
@marlinjai/data-table-react0.7.0, which also brings folding all sections at once and a filter bar without its own frame. Done 2026-10-11. - Dragging a group from one section into another is not yet proven on production. (2026-10-11)
- A receipt with its parts as sub rows, the second use of sub rows (further values of the
hidden
Row Kindcolumn). Not built; the group design leaves room for it. (2026-10-11) - The text reader takes two line items for net and tax. Seen in the review list on 2026-10-11: a Cursor invoice of 20.06 US dollars with two usage lines (18.76 and 1.30) and no tax was offered a new reading of net 18.76 at 7 percent, because 1.30 is about 7 percent of 18.76. A tax needs a line that names it; two item amounts that happen to fit a rate are no tax. The same pass showed the stored-total flags of older readings never clear by themselves once the amount was corrected or tied to a payment: 68 such "Betrag prüfen" entries were confirmed by hand against the tax table and the documents. A corrected or payment-linked total should clear its flag. (2026-10-11)
- Read receipts with a model that sees the page, not only the recognized text. Live since 2026-10-11: the classification call gets the page itself and also reads date, total, currency, net and tax lines; the invoice's arithmetic and the recognized text check its answer. On 288 real documents no wrong date or total is stored without a flag. "Seite neu lesen" was used on production on one receipt (the unpaid hosting invoice: 288.59 where the old reader had 242.51; assignment, share and name untouched). Open: (1) a new reading replaces a vendor that is already set with the legal name on the page ("A2 Hosting" became "A2 Hosting LLC", set back by hand), which splits the vendor grouping: keep a vendor that is set. (2) The measurement was made on the documents the rules were written against; measure on the next import of unseen documents. (3) The vendor cell is not judged by the evaluation script. Plan: docs/plans/2026-10-11-vision-reading.md. (2026-10-11)
- The review list no longer offers the text reader's vendor name against a stored one on an invoice. Measured on production on 2026-10-11: of 102 offered new readings, 76 changed the vendor, and about 70 of those were worse than the stored name (a heading, a field label, the customer's own name). Marlin's call the same day: the name a customer would use stands ("YouTube", not the company that bills for it). A vendor is now only offered on a till receipt of a restaurant or bar, or where none is stored, and never for the legal form alone. The amount offers stay: there the new reading was the better one (the printed 19 percent on dollar invoices that were stored with 0). Done 2026-10-11.
- The dashboard table doubles its rows after cell edits. Seen on production on 2026-10-11 while renaming receipts through the table: after a few dozen edits in one page load the header read "917 items" for 267 receipts and every row was drawn several times (917 rows, 267 distinct ids). The stored data was intact and a reload showed 267 again. Probably the loader that fetches all pages appends them again after an edit. Until fixed, counts and sums on a page that was edited a lot cannot be trusted without a reload. (2026-10-11)
- What was bought is part of a receipt's name: a cell "Item" the reader fills, the name as
"vendor, item, total, date", and the name following its cells when vendor, item, total,
currency or date is corrected (a typed name stays). Decided by the owner on 2026-10-11 for
every receipt of every year. Deployed the same day; every stored receipt has its item through
POST /api/receipts/items: 780 under Whiz-Art Media (2021 to 2025, the 13 business meals of 2025 left as they are, 3 receipts where the model found nothing to name) and 24 private ones. The 2024 and 2025 figures were the same before and after. Plan: docs/plans/2026-10-11-receipt-item-in-name.md. Done 2026-10-11. - The item is worded per receipt, so the monthly invoices of one subscription can read differently ("PocketTube Abo", "PocketTube-Mitgliedschaft"). The reader should be given the items the same vendor already has and reuse one that fits. (2026-10-11)
- The reader stores the value-added tax amount as the total on one vendor's invoices: 32 invoices of a mail-scanning service across 2021, 2022 and 2023 (0.64 for 3.99, 2.07 for 12.99), all corrected by hand on 2026-10-11. Two more classes from the same import: a mobile bill is read with or without the third-party charges it lists, differently from month to month, and an amount in another currency printed next to the euro amount was taken as euros (once), a euro invoice as dollars (once). (2026-10-11)
- The dashboard under 780 receipts: step one deployed (35 s to 17 s, 817 MB to 169 MB); step two (the table loads the years it shows, opens on the newest) and step three (recurring receipts grouped automatically by vendor, item and year) are built. Open until deployed and measured on production. Plan: docs/plans/2026-10-13-dashboard-load-and-grouping.md. (2026-10-14)
- The filter bar's date operators ("after", "before", "equals") fail on the database: the adapter casts the date column to a timestamp and compares it with a text parameter. Found on 2026-10-14 through the adapter's list query; the year scope avoids them. Fix in the table adapter or in the app's server action. (2026-10-14)
- Rule sets for the tax years 2021 to 2024, so the finance area shows those years on their own forms. Today they are computed with the 2025 form, which the page says. 2024 uses the same line numbers as 2025 for every line that was declared; 2023 is shifted, 2022 and 2021 use the older, longer form. (2026-10-11)
- Asset schedules for what the returns depreciated and the app now holds as documents: camera, camera stabiliser and tripod of 2021 (the app counts two of them in full as low-value assets and holds the camera as an open check), the lens of 2022 (open check) and the laptop of 2022 (counted at full price times the study share). Until then 2021 and 2022 differ from the returns by the amounts in the plan of the import. (2026-10-11)
- A share on both forms cannot be recorded for a year without its own rule set: subscriptions the returns of 2022 and 2023 split between business and study carry only the business share, because the split is a finance-area decision and those were made for 2024 only. About 687 of study cost in 2022 is not counted for that reason. (2026-10-11)
- "Set field" on a selection loses writes: on 2026-10-11, setting the assignment on 30 selected rows reported success and left 14 of 81 rows unchanged; selections of 7 went through completely. The writes run side by side through the guarded cell update. (2026-10-11)
- A file that holds two invoices becomes one receipt with the second invoice's figures (seen on 2026-10-11 on a file with a marketplace invoice and a retailer invoice). The reader should say that the file holds more than one document, so it can be split. (2026-10-11)
- Private spending in the app (decided by the owner on 2026-10-14): overview by category and month, clean split private versus business, budgets and warnings, a document for every purchase, a live bank connection, and the loop that finds or asks for the receipt of every payment. Build order and open questions (where the notification goes, which vendors may be fetched with a stored login, the budget limits) in the plan: docs/plans/2026-10-14-personal-spending-and-bank-feed.md. (2026-10-14)
- Bring the app up to the tax session's draft for 2025. Marlin's 17 answers of 2026-10-11
(recorded in
Entscheidungen-2025.mdin the tax folder) are carried out, the bank, PayPal and pay-later lists are imported, and 112 payments are linked: business expenses in the app went from 7,547.54 to 13,979.55 euros, the draft table from 14,344.35 to 14,152.54, and one receipt is left on an estimated euro amount (CleanShot of 30 November, no payment in any source). Every change is inimport-log-2026-10-11.mdthere. The four missing Figma invoices were downloaded and imported on 2026-10-11, so the app now shows 14,074.75 euros against a draft of 14,133.55 (Notion's 37.98 without an invoice left the draft the same day). The remaining 58.80: the meal allowance flat rate (56.00) and the meals (3.01), less rounding. The five Figma receipts are linked to their pay-later purchases (116 payments linked). Waiting on Marlin: the meals in the register, and the tax adviser's answer on whether a pay-later purchase counts on the purchase day (as built) or on the day the money leaves the bank. The pay-later notice in the payments tab for 2025 (1,429.81 euros less collected from the bank than the list holds in purchases) is explained and is not missing data: the MacBook of 25 November (2,849.00) ran as a financed purchase through PayPal, so the list holds it in full while the bank only sees instalments; against that, the monthly cycle collects December 2024 (1,513.12) in 2025 and December 2025 (924.43) in 2026, and the remaining 827.78 are instalments already paid and purchases settled ahead of the statement, which the files cannot split. Open from it: the reconciliation should treat financed purchases separately, or the notice returns every year one exists; and the draft's note for the MacBook says "paid via PayPal" where the money came from a pay-later loan, which belongs to the purchase-day question for the tax adviser. (2026-10-11) - Flat rates have no place in the finance area: the meal allowance of a business trip, the home office flat rate, commuting days and semester fees paid by transfer. The employment annex therefore shows 667.75 where the 2025 draft claims 2,101.22 (1,422.14 of it without a purchase document by nature), and the business form misses 56.00. Design entries without a receipt: what evidence each needs, which form line it lands on. Same question for an expense that exists only as a bank payment and for a recurring contract rate, which on 2026-10-11 only worked because one lease confirmation could carry eleven payments. (2026-10-11)
- The payments tab asks too much and offers too little after a real import (2026-10-11, 2,150 payments): 586 open payments and 237 unanswered counterparties. Money in is always asked about, with no answer for "this whole account is private"; a payment to an unanswered counterparty cannot be linked until the counterparty is answered as business, which turns all its other payments into questions; an account takes one source, so a bank export that ends mid-year cannot be continued from another list; an import cannot be limited to a year (the bank-interface list brought 454 payments outside 2025). (2026-10-11)
- The assignment cell and the finance decision are two sources for who bears a receipt: a decision in the finance area does not update "Zuordnung" or the share in the table, so the dashboard and the finance area can disagree until both are set, and a decision has no note field (the note for the unpaid hosting invoice of 2025 stands in the receipt's name). Private receipts also carry share 100 in some rows and 0 in others. Decide which one is the source and derive the other. (2026-10-11)
- A refunded purchase has no representation: three Amazon orders of 2025 were refunded in full and were kept out by hand. Importing invoice and credit note as a pair should net to zero by itself. (2026-10-11)
- The same invoice can be imported into two companies without a warning (the duplicate check is per company and per file hash), and the active company is a session-wide switch, so importing into another company means switching the whole signed-in session, a risk when several sessions work at once. An upload should name its company itself. (2026-10-11)
- Check the reader's new date and total reading against the text production actually stores. The fixes of 2026-10-11 were measured on local text extraction of the 2025 purchase documents, with the labels torn from their amounts to imitate the text recognition, because the stored recognized text was not at hand. Read the "OCR Text" cells of the imported rows and rerun the reader over them. Known to remain: a customs invoice whose total (226.14) cannot be paired with its label once the two are torn apart, which needs the word positions the text recognition returns; four documents without a total when the model gives none (three Namecheap, one order page); two totals that came from the model and could not be reproduced (one Cursor, one Namecheap). (2026-10-11)
- Classification rules a user writes live in browser storage and never reach the upload path, so a rule such as "this vendor is private" cannot apply when a receipt is uploaded. Since 2026-10-11 the model leaves the assignment empty unless the document shows it, and an empty assignment is asked for in the review list. Decide whether the rules move to the server. (2026-10-11)
- The reader finds the invoice date on Google, Adobe, Apple and Microsoft invoices: every date is ranked by the label in front of it, a due, renewal or debit date is never taken (58 of 302 documents without a date before, none after, on local text). Totals: net, tax and their printed sum settle the total in any currency, the "Total" line stands where a credit is applied, and a long invoice reaches the model with its end, which fixes the net-for-gross readings (13 OpenAI invoices among them). The phone leasing application states no total, and that is now pinned by a test. Done 2026-10-11.
- The reader's first guess "Privat" on 65 of 221 business invoices came from the classifier prompt, which offered the three assignments with no meaning and no way to leave the field open. The model now answers only what the document shows and leaves the assignment empty otherwise; the review list asks for it ("Zuordnung fehlt"). Done 2026-10-11.
- Removed the receipts app's own contacts table and the
CONTACTS_STOREswitch (wave 4 of the shared contact list). The shared contacts database is the only contact store; a missing company or a missingCONTACTS_DATABASE_URLfails loudly instead of falling back. Migration0018_drop_own_contacts_tabledrops the tablecontactsin the receipts database on deploy (4 stale rows, copied to the shared database under the same ids on 2026-10-09). Released on Marlin's own yes. The unusedCONTACTS_STOREentry was removed from the production secret project the same day. Plan:docs/plans/2026-10-10-remove-own-contacts-table.md. (2026-10-10) - Invoicing and income: the finance area shows "Einnahmen 0" for 2026 because no issued invoice is recorded (all of them are PDF files written in a Google Doc). Order: enter the invoices of 2024 to 2026 from the list in the tax folder and link their bank payments (ends the zero, no code), tighten the matcher to whole invoice numbers, then the invoice editor with numbering at issue (next number 62) and the PDF, then reading electronic invoices (XRechnung, ZUGFeRD) that arrive in the expenses mailbox. Waiting on the owner: where invoices are written (recommended here, not in framer-clone), invoice 60, invoice 58. Plan: docs/plans/2026-10-14-invoicing-and-income.md. (2026-10-14)
- Issued invoices with more than one tax rate. An issued invoice carries one tax treatment (standard or reduced rate). Its stored tax amount is right, but the advance value-added tax return would report the whole turnover under that one rate, so an invoice with 7 and 19 percent lines would be misreported. Store the tax groups per invoice and report each under its own rate. From the finance dashboard work. (2026-10-10)
- A signed-in live write pass of
/app/financeon production, under Whiz-Art Media, which holds the receipts since the move of 2026-10-10: open each tab, write one synthetic decision, vendor rule and payment link, remove them again, and confirm the numbers return. Only a read-only pass was done (2026-10-10 08:53). From the finance dashboard work. (2026-10-10) - Company erasure hands over an export first: the company's register and contacts go into a zip (GET /api/export/company), and printed guest names are removed at once when an export was taken, otherwise held for ten years (German tax law, the Abgabenordnung, AO) and removed by the retention purge. Built in this change, 2026-10-09.
- Enable the retention purge of held guest copies only after recorded legal confirmation
of the retention policy. The implementation explicitly requires that review before go-live.
The production
RETENTION_PURGE_SECRETwas generated securely in Infisical on 2026-10-09; no schedule was enabled. The backup and scratch restoration prerequisite is complete. Once the legal policy is confirmed, run the signed purge verification and enable its daily Coolify schedule. Preparation:docs/operations/retention-purge.md. (2026-10-09)
-
Finance and tax dashboard, stage 2 (scenario engine): "what if I buy X on date Z at share S" and "what if revenue is R", computed by the same tax module as the dashboard, saved as inputs and never as results. After stage 1. Plan:
docs/plans/2026-10-07-finance-tax-dashboard-and-advisory.md. (2026-10-07) -
Finance and tax dashboard, stage 3 (advisory layer): deterministic hints that each show inputs, rule, source and euro effect, the legal form comparison (sole proprietor against a company with limited liability or a partnership) with a hand-off to a tax advisor, and last an optional language model that may only quote computed figures. Operator only until a lawyer's opinion on the tax advisory act and public bank data access exist. After stage 2. Plan:
docs/plans/2026-10-07-finance-tax-dashboard-and-advisory.md. (2026-10-07) -
Two tax rates, two columns, one order. On a receipt with two tax rates, the number column "Tax Rate" takes the first of the reader's printed tax groups ordered by gross (
src/lib/extraction/amounts.ts), while the text column "Tax Rates" follows the model's meal tax lines ordered by net (src/lib/tax-rates.ts, written inreadReceipt). The two can name a different first rate when the two sources disagree. Decide one source of truth for the order and make both columns follow it, with a test where the sources disagree. Found while landing the capture queue fix. (2026-10-10) -
Extraction defects found in the first live upload (2026-10-08, a 21-page scan of restaurant receipts uploaded with "One receipt per page"). Done 2026-10-10. Root cause behind four of the seven: the language-model classifier never ran in production (no Anthropic key is set there, the call threw, the error was swallowed), so every receipt was filed by pattern matching alone. It now runs through the OpenRouter key production has, and a missing or failed classification is recorded on the receipt. (1) and (2): total, net and tax come from the tax groups the receipt prints and are confirmed by its own arithmetic (
src/lib/extraction/amounts.ts); a receipt number, a date or a rate is no longer an amount; a printed tip is split off the total; a total that only a label vouches for is put up for a look; nothing is guessed from the largest number. (3) a page nothing could be read from is named "Nicht lesbar: " and stands in the new review list. (4) a table, a waiter, a tip line or the printed hospitality form make a restaurant receipt (meal-evidence.ts). (5) the vendor reader skips slogans, item lines and misread logos and joins a name set in several lines (vendor.ts). (6) look-alike receipts are found on read from the stored receipts and the decision "keep both" is stored (tablereceipt_reviews, migration 0014), so the question survives leaving the upload page. (7) could not be reproduced: the year list was already built from all meal receipts, complete or not; it is now pinned by tests, the current year is always offered, and meals without a date (which belong to no year) are counted on the register tab. Checked against all 21 receipts of that upload, restored from the verified backup into a local database. (2026-10-08) -
Receipts stored before 2026-10-10 kept the readings of the old reader (for example a total that includes the tip, or a slogan as the vendor). Done 2026-10-10: the review list reads the stored text again with the current reader (no model is asked, the result is the same every time) and offers what differs as "stored / newly read", field by field (
src/lib/review/reading.ts). Nothing is written until the offer is taken; "Geprüft, stimmt so" keeps what is stored and ends the offer. Only what the reader stands behind is offered, and a field is never offered to be emptied. On the restored copy of the live data 13 of 24 receipts get an offer. (2026-10-10) -
Direct model access with web search for the classifier is optional and not set up: it needs an Anthropic key in the receipts production settings (
ANTHROPIC_API_KEY, a credential only the owner can issue). Without it the classifier decides from the receipt text alone through OpenRouter, which is what runs today. (2026-10-10) -
Deleting a receipt in the dashboard table left its stored file behind. Done 2026-10-10, decided by the owner's standing goal: the dashboard deletes for good, the same way the meals page does.
deleteRowandbulkDeleteRowsinsrc/app/app/dashboard/actions.tsgo throughdeleteReceiptRows(stored file first, row second, a file another receipt still shows is kept, a receipt whose file cannot be deleted stays complete and is reported). The dashboard now asks before deleting, in the page: before, Backspace on a selection deleted at once. (2026-10-08) -
Production database backups verified: six-hourly Coolify dumps, 28 copies locally for seven days, native European Union R2 copies for 30 days, and an hourly additional copy to Hermes with 30-day retention. A scheduled dump restored in an isolated PostgreSQL container with matching counts for all 28 public tables (639 rows), logical column definitions, constraints and indexes, including a fresh copy downloaded from R2. Production health stayed green. No new fixed hosting cost; normal existing object-storage usage applies. Plan:
docs/plans/2026-10-09-production-backups.md. Recovery:docs/operations/production-recovery.md. (2026-10-09) -
Three defects in the shared table adapter (
@marlinjai/data-table-adapter-prisma0.2.1) that this app now works around and that belong fixed there: (1) nothing stops two columns of one table from carrying the same name, so a check-then-create race doubles a column (here: a per-workspace lock inensureReceiptsTable; there: a unique index on table and name, or a lock increateColumn); (2)getRowreads with a preparedSELECT *and swallows every error, so after any column was added or dropped Postgres answers "cached plan must not change result type" and the adapter reports "Row not found" (here:statement_cache_size=0insrc/lib/prisma-url.ts; there: list the columns, and let the error through); (3)updateRowsilently skips a cell whose column it does not know and still reports success. (2026-10-09) -
Migrate the
/api/*SERVICE_TOKENmachine path to tenant-scoped auth-brain API keys. Deferred out of the app-grant door flip (that slice left the sharedSERVICE_TOKENbearer unchanged); machine callers should carry a tenant-scoped key so their access is entitlement-checked like browser sessions. Still open and still wanted: the machine path is unchanged. (2026-10-11) -
Deploy Docs GitHub Actions workflow failed on every run from at least 2026-08-03. Resolved, this line was stale: the Infisical credentials were provisioned on the repository and the workflow was repaired in pull request 27 (action pin, project slug, scoped path). Last failure 2026-09-14; every run since has succeeded, latest run 37986504043 on commit 565e4e1 (2026-10-09), and https://docs.receipts.lumitra.co answers 200 (checked 2026-10-10). The standalone docs site stays. (2026-09-10)
-
The dashboard workspace dropdown still lists a drained legacy workspace labeled "Lola Stories" whose data was migrated away (leftover from the PR #18 app-grant gating cleanup). The ambiguous-label half of this is fixed (adopting the shared
WorkspaceSwitcherfromauth-brain-nextjs0.4.1 now labels a single-workspace company by its company name instead of a bare "Main"); what remains is hiding or deleting the drained legacy workspace membership, which is a production data change/audit in the auth-brain multi-tenant service, not a code fix in this repo. Needs Marlin or a data audit before touching it. Still open: the dropdown was not rechecked since. (2026-10-11) -
Contact screen, wave 3: the Kontakte tab manages organizations with address, legal form and VAT ID, links people to organizations, merges duplicates, assigns customer numbers and exports one contact. Completed 2026-10-10 on
@marlinjai/contacts-core0.2.0: the company defines its own fields (text, number, date, yes or no, one or several options, link), every contact carries a value for each and a preferred contact method, and one contact can be erased. Its printed names on meals follow the rule of the company erasure (removed when an export exists, otherwise held). The tab badge counts persons and organizations. Plan:docs/plans/2026-10-09-contact-screen.md. (2026-10-09) -
Contact screen repairs, found in the live check of 2026-10-10: (1) the guest list, the directory and the tab badge now follow each other without a page reload, in both directions (a change in one list reloads the other; an erase or a merge also reloads the meals); (2) a person can be created in the directory with its details, custom field values and preferred contact method, as an organization can; (3) the export rule was wrong for an old export: printed guest names are now removed on an erasure only when the register is identical to the company's newest export (
company_exports.register_sha256, compared by hash), and held in every other case, including an export taken before a later meal or correction. The export now also lists every printed guest name, so names on incomplete meals are part of it. Done 2026-10-10. (2026-10-10) -
Business receipts moved to the company Whiz-Art Media (Marlin's rule: business under Whiz-Art Media, personal under "marlinjai", one company per receipt by tax purpose). The whole workspace with its 24 receipts, 16 business meals and 3 guest rows moved through auth-brain with all ids kept, its 4 contacts went along (29 under Whiz-Art Media, 0 under "marlinjai"), and "marlinjai" has a fresh empty workspace. The receipts side is the signed endpoint
POST /api/internal/workspace-move(how to call it:docs/operations/workspace-move.md). Run on Marlin's yes and verified in both databases and in the browser. Plan:docs/plans/2026-10-10-business-receipts-to-whiz-art-media.md. Done 2026-10-10. -
Remove what the retired empty Whiz-Art Media workspace leaves in the receipts database. Workspace
01a12296-b963-7018-8857-5e76ffe01c87was created empty on 2026-10-09 and was retired in auth-brain on 2026-10-10, when the business receipts moved to Whiz-Art Media. A visit had created a Receipts table with 0 receipts and one tax settings row for it; nothing reads them now. Removing them is a delete on production: it needs Marlin's one-line yes, with the dumpreceipts-before-workspace-move-20261010T122443Z.dmpon the server as the way back. (2026-10-10)
In Progress
- Shared contact list, wave 2: the business-meal guests moved to the suite's shared
contacts database (company-scoped, behind the
CONTACTS_STORE=sharedswitch, default off), with a merge-aware data move that Marlin reviews before anything is dropped. Done 2026-10-09: the switch is on in production. Waves 3 to 5 are open in the knowledge-base roadmap. Plan:docs/plans/2026-10-09-shared-contacts-wave2.md. (2026-10-09) - Finance and tax dashboard, stage 1 (data foundation and live dashboard). Built:
slice 1 (tax module with rule sets for 2025 and 2026, shares for several purposes
per receipt, vendor rules, the queue of open checks, the income-surplus statement
at
/app/finance), the asset register of slice 2, slice 4 (issued invoices, revenue by payment day, profit or loss, forecast of the small-business limits) and slice 5 (dated status changes, regular value-added taxation, advance return periods), and the first part of slice 3 (payments from export files, one answer per counterparty, links to receipts and invoices, also by hand), receipt lines entered by hand, and the ten-day rule at the turn of the year for expenses and payments to the tax office. Open, in this order: the classifier reading lines off a receipt (unblocked: the receipt reader rebuild merged on 2026-10-10 as pull request 51); the rest of slice 3 (live bank sync with session expiry, card statement and pay-later importers, the ten-day rule for regularly recurring income); the income tax estimate; the year-end entry sheet; reading the expenses mailbox. Open inside what is built, each described in the plan's "Reality" sections: (1) the two older receipt columns "Business Share %" and "Zuordnung" and the per-vendor share table of the overview page are still read as a starting point and are removed only after the owner has finished entering the 2025 meals and the session preparing the 2025 return has been told; (2) the line numbers of the employment annex of 2026 are not yet compared with an official source, so those two lines show without numbers; (3) the 2027 rule set is due before 1 December 2026, when a test starts failing without it; (4) tax the buyer owes on services from abroad is not computed; (5) invoices carry no link to a contact yet. Plan:docs/plans/2026-10-07-finance-tax-dashboard-and-advisory.md. (2026-10-10) - The purchase documents of the tax years 2024, 2023, 2022 and 2021 are in the app: 514 receipts under Whiz-Art Media (2024: 155 and one ticket dated 2023, 2023: 148, 2022: 119, 2021: 91), each year a record of its filed return, with a log, a file table and a snapshot per year in the owner's tax folders. 2024 matches the return within about one euro per form line. What remains has its own lines above (rule sets, asset schedules, shares on both forms). Plan: docs/plans/2026-10-11-import-tax-years-2021-2024.md. Done 2026-10-11.
- Pay-later purchases as payments. The purchase list of the pay-later service (Klarna) is a
payment source, a debit that only funds another imported payment account (PayPal on the bank,
the pay-later settlement) counts as a transfer between own accounts, and receipts on an
estimated euro amount get a proposed payment. The first version is deployed; the fixes of the
review (the rule only on days the list covers, transfers visible and reversible, cancelled
purchases, the same money once per receipt) are on
fix/pay-later-review-findings. Open until those are deployed, the proposals for the 51 receipts of 2025 are accepted on production, and the owner has decided which day a pay-later purchase counts on (the plan's open question). Plan: docs/plans/2026-10-11-pay-later-payments.md. (2026-10-11) - Business-meal register (Bewirtungsverzeichnis) and phone capture: all six
slices are built (register, contact list, export, duplicate checks, page split,
phone capture, classifier, offline queue, service worker, share target). Left
before this can be ticked: (1) a pass by hand on a real phone (camera,
home-screen install, offline queue) and one real upload through text recognition
in production; (2) the owner's decision whether to keep the share target, which
only works on Android (it is the last commit of the second pull request and can
be reverted on its own); (3) confirm with the tax advisor the amount above which
the receipt must name the host (set to 250 euros). Plan:
docs/plans/2026-10-06-meal-register-and-phone-capture.md. (2026-10-07)- Batch actions on the meals page, asked for after the first real use with 17 open entries: a checkbox per entry and "select all" in the queue, a batch bar with "Keine Bewirtung" (out of queue and register, kept as a receipt, taken back from the new "Keine Bewirtung" list on the same page) and "Löschen" (row, stored file and guests, after an in-page confirmation), the same two actions on a single queue entry and on a register entry. A batch finishes the rest when one receipt is gone or belongs to another workspace, and a receipt whose stored file cannot be deleted is kept and reported. (2026-10-08)
- Visual polish of the meals page after the owner's review of the batch actions:
a reusable custom checkbox (
src/components/ui/Checkbox.tsx, real input, mixed state, 40 pixel hit area), no layout shift when an entry is checked (the batch bar has a reserved slot under the queue, outcome notices float, rows are a fixed grid; measured 0 pixels at desktop and phone width), thin on-brand scroll bars and the dark colour scheme app-wide, quieter row actions, a destructive button that looks destructive at rest. Checked in a headless browser with screenshots. (2026-10-08) - Receipt viewer and form fixes from the owner's live use: the receipt beside the
form can be turned in quarter steps (stored with the file reference, so it is the
same in the dashboard and on the exported register sheet; the stored file is never
rewritten), zoomed, dragged and fitted, with keys R, plus, minus and 0. A PDF
receipt is drawn to a picture in the browser (new dependency
pdfjs-dist), so a sideways scan no longer sits as a strip in a browser frame. The place field opens with name and address read from the receipt text when a complete address is found there ("Aus Beleg übernehmen" for receipts that already have a place), and picking the host from browser autofill no longer overwrites the place. (2026-10-08)
Completed
- A year filter on the receipts table (requested by the owner on 2026-10-12): chips per year with their count, one year, several or a range with Shift, kept in the address as
?jahr=. Done 2026-10-12. - Phone capture queue: a photo the server has is never sent again. Sending a queued photo and removing its entry were one step, so a failed removal looked like a failed send: the photo was reported as not processed, its attempts were raised, and every later run sent it again (kept from becoming a second receipt only by the duplicate check, which stops helping once the receipt was deleted on purpose). Now two steps with two outcomes: a failed removal marks the entry as sent, later runs only retry the removal, also without a connection, and the screen says so. This is the capture half of a reviewed fix that missed its merge: it was committed in the same second pull request 31 merged on 2026-10-07 and was never pushed. Its other half (the tax rate of a meal with several tax lines) is not ported: the receipt reader rebuild names the rate that carries most of the bill, and pull request 60 lists every rate. Done 2026-10-10. (2026-10-10)
- A newly created Receipts table now carries its company from the first moment
(
ensureReceiptsTableand the dashboard'screateTablestamp it right after creation, never overwriting an owner), with a database test. Before, a workspace created after the tenant backfill stayed without a company until the backfill was run again. (2026-10-07) - Branch the Drive-attach and Drive-browse 403 error mapping on Google's
error body so an API-disabled GCP (Google Cloud Platform) project gets its own
drive_api_disabledcode/label instead of the misleading "reconnect Google" advice meant for a genuine missing-scope 403. (2026-09-10)