A Lunar 2026 export (Date, Time, Title, Amount, Balance, Transaction ID)
that reached the manual "Annan CSV" mapping was seeded with Time as the
description: no description keyword matched Title, and the positional
fallback took the first non-numeric, non-date column, which is the clock
time sitting between Date and Title.
- suggestColumnMapping: add title / titel to the description keywords;
exclude clock-time columns from every description pass, by header
label (Time, Tid, Tidpunkt, Klockslag, Transaktionstid, ...) and by
HH:MM / HH:MM:SS values, so header-less files are covered too. Last
resort still seeds something the user can correct.
- Lunar detector: sniff the delimiter (comma, semicolon, tab) instead of
refusing any file containing a semicolon, so a re-saved or localized
copy of the same English header set is parsed by the dedicated parser
and never reaches the mapping flow. Header cells are matched exactly
(date, title|text, amount, balance), the same resolution parse() uses,
which also stops substring hits like Update/Context from claiming a
file.
- Mapping UI header-row detection: add title / balance to the keyword
list for English exports.
Regression tests: Lunar-style header through the generic path maps Title,
a header-less Time column is skipped by value, Datum;Tid;Titel maps
Titel, semicolon- and tab-delimited 2026 Lunar files detect and parse,
Swedish and non-Lunar English headers are not claimed. All 7 fail without
the fix.
Closes#1671
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Seven radii were in circulation (4/5/6/8/12/16px + pill) with no rule for
which went where; one toolbar row on /transactions mixed four shape
languages. This locks a 4-tier ladder (design.md convention 16):
- pill: interactive toolbar controls (buttons, chips, pickers, segmented
controls, toolbar search, count nubs)
- rounded-xl (12px): overlay tier: page panel, dialogs, slide-overs
- rounded-lg (8px): cards, form fields, popover/menu content, boxes
- rounded-sm (4px): nested leaves (menu items, checkboxes, kbd/code nubs)
Changes:
- New SegmentedControl primitive (pill-in-pill tablist, h-8) replaces the
hand-rolled bg-muted/70 tablist copied across 11 files
- New ToolbarSearch primitive (pill, h-8) adopted on 9 page toolbars;
dialog/picker searches keep the rounded-lg Input
- dialog.tsx 8px -> 12px, matching SettingsModal/slide-over/CommandPalette
- ContextPicker chips at the shared h-8 toolbar height
- ~300 rounded-md / bare rounded call sites remapped by role; auth icon
tiles and the mobile nav sheet come down from 16px to 12px
- rounded-md, bare rounded, rounded-2xl and rounded-[Npx] are dead
vocabulary, enforced by a new off-ladder-radius check in check:guards
Verified: lint 0 errors, 14422 unit tests pass, check:guards green, tsc
clean on all changed files, sandbox screenshots of transactions/
bookkeeping/granskning toolbars and the Ny verifikation dialog.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(design): stop synthesizing bold on Hedvig display headings
Hedvig Letters Serif ships weight 400 only, but the h1-h3 base rule
forced font-weight 500 and DialogTitle/SheetTitle stacked font-semibold
on top, so every display heading rendered browser-synthesized bold: the
smudged heavy look on dialog titles and page headings. Drop the base
rule to 400, remove the weight utilities from the title primitives, and
sweep the 41 files that hand-set font-medium/semibold/bold on serif
headings (a pattern design.md already forbids). Headings that opt into
font-sans keep their weight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(design): drop empty className left by the weight sweep
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes#1173. invoices.total_sek stays NULL when the Riksbanken rate
fetch fails at creation, and every `total_sek || total` fallback then
treated a raw foreign amount as kronor:
- lib/calendar/utils: new invoiceSekAmount() returns null for
unconverted non-SEK invoices; period summaries and day totals skip
them and PeriodSummary exposes unconvertedCount. PaymentSummaryCard
shows a one-line note when invoices were excluded; CalendarDayView
renders each invoice in its own currency instead.
- Deadlines page: the overdue attn sum now skips unconverted FX
invoices and appends "(+N i utlandsk valuta)" instead of adding EUR
into a kr total.
- Supplier-invoice payment toast formats the amount with the invoice's
currency (key drops its hardcoded " kr" in both locales).
- AR aging drill-down row labels Betalt with the invoice currency,
mirroring the outstanding cell.
- BankFileColumnMappingStep: comment pinning why SEK is safe there
(generic-csv hardcodes it).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.
Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.
Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.
Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat(bookkeeping): make blocked fiscal-year creation actionable
When creating a new räkenskapsår is blocked because a prior period is
still open, the "Skapa räkenskapsår" dialog no longer dead-ends on an
English toast. The API now returns the canonical bilingual error envelope
with the blocking periods (id/name/dates) under details, and the dialog
renders a Swedish panel that locks them inline (reversible locked_at) via
the existing /lock endpoint and retries creation.
The guard rule is unchanged and remains BFL-compliant: BFL 6 kap allows
löpande bokföring of the new year in parallel with the prior year's
bokslut, so a lock (not a full close) is sufficient and reversible.
- Add PERIOD_CREATE_BLOCKED_BY_OPEN_PERIODS structured error code
- Return envelope + details.blockingPeriods from the 409 (was English string)
- CreatePeriodDialog: inline "lås och skapa" panel + lock-and-retry
- Update route tests for the new envelope shape
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ui): prevent mouse wheel from mutating number inputs
A focused <input type="number"> would change its value on scroll,
silently turning e.g. a 20000 salary into 19998. Blur number inputs
on wheel so the page scrolls instead of editing the value. Applied
at the Input primitive so all number fields are protected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(salary): auto-derive skattetabell and kolumn for employees
Replace the opaque manual "Skattetabell (29-42)" and "Kolumn (1-6)" inputs
on the employee form with a self-deriving flow: the user picks their
folkbokföringskommun from a searchable dropdown and the tax table fills
itself in, while the column derives from the personnummer we already collect.
- Add a searchable municipality picker (MunicipalityCombobox) backed by a
new cached GET /api/salary/tax-tables/kommuner endpoint.
- Wrap the whole "Skatt" card in a self-contained EmployeeTaxCard used by
both the create and edit pages, with InfoTooltips and named column options.
- deriveTaxColumn(): auto-select column 1 for under-66 employees; leave the
ambiguous 66+ case (pension vs working senior) to a clearly-named manual
choice.
- Fix fetchKommunTaxRates() to page through all ~1300 församling rows instead
of a single 500-row page (which silently dropped ~200 kommuner, incl.
Göteborg) and normalize the uppercase names to title case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(import): correct CSV amount-column guess and surface skipped rows
Manual CSV column-mapping auto-guess walked each data row right-to-left
and picked the first numeric cell as the amount, so on the common
...;Belopp;Saldo layout it grabbed the trailing running-balance column.
Extract the guess into a pure, tested suggestColumnMapping(): match
header labels first (belopp/amount -> amount, saldo/balance -> balance),
auto-fill the balance field, and fall back to value heuristics that skip
the balance column and prefer a column carrying negative values.
Also surface stats.skipped_rows + parse warnings in BankFileConfirmStep -
the manual-mapping path skips the preview step that was the only place
they showed, so skipped rows were silently dropped from view.
Add a unit test reproducing the Saldo-as-amount regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add "Save as draft" functionality for invoices
- Implemented a new feature to allow users to save invoices as unnumbered drafts without generating an invoice number until finalized.
- Added a `save_as_draft` flag to the CreateInvoiceInput schema to handle draft saving logic.
- Updated the invoice creation API to skip number allocation when saving as a draft.
- Introduced a new endpoint for finalizing drafts, which allocates an invoice number and emits an `invoice.created` event.
- Enhanced the UI to include a "Save as draft" button, with loading states and tooltips.
- Updated tests to cover the new draft saving and finalization logic, including race conditions for concurrent modifications.
- Added relevant error handling for draft finalization and deletion scenarios.
* feat(employee): add employment start and end date fields to employee forms
* feat: enhance invoice and salary run handling with improved validation and event logging
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(api): implement caching and logging in health check endpoint
- Added in-memory caching for health check responses to reduce load on Postgres.
- Introduced logging for error handling in health check.
- Updated response structure to exclude error details from public responses.
feat(api): enhance OAuth consent UI and scope handling
- Improved consent UI to reflect exact requested scopes and added better user guidance.
- Updated scope handling logic to ensure least-privilege access.
- Enhanced styling for better user experience and accessibility.
chore(docker): improve security and resource management in Docker setup
- Updated Docker Compose configuration to enforce read-only file systems and resource limits.
- Added health checks and logging options for better observability.
- Introduced optional Caddy reverse proxy for TLS termination.
fix(migrations): resolve ambiguity in create_company_with_owner function
- Dropped orphaned 3-arg overload of create_company_with_owner function.
- Recreated canonical 4-arg version with cash account seeding logic.
- Ensured proper permissions for function execution in Postgres.
* feat: enhance security checks for team membership in company creation
* test: add CSP tests for OAuth authorization endpoint
* feat: enhance error handling and reporting in bank file import process
* feat: add script to import Skatteverket monthly tax tables as fallback TypeScript module
- Implemented a new script `import-tax-tables.ts` to parse fixed-width TXT tax tables from Skatteverket (SKV 434).
- The script generates a TypeScript module for emergency fallback when the Skatteverket open-data API is unavailable.
- Supports command-line argument for specifying the year and handles parsing of B-rows only.
- Outputs a structured TypeScript file containing tax data for specified years.
* feat: gate salary module behind dev-only flag
Temporarily disable the Lön module in production while the feature is
being completed. Sidebar entries ("Löner", "Anställda") still render but
are not clickable and show a "Kommer snart" badge. Middleware redirects
/salary* to / and returns 404 on /api/salary/* so the feature can't be
reached by direct URL. All gates check NODE_ENV === 'development' so
local dev keeps full access for continued development.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: refactor bank file import wizard to streamline column mapping and enhance CSV handling
* fix: bump migration timestamp to avoid collision with logos_bucket
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: enhance AGI generation and salary entry calculations with improved status checks and error handling
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three confirmed issues from user feedback:
1. Reverse charge preview now uses per-item VAT rates and correct accounts
(2645/2647, 2614/2624/2634) instead of hardcoded 25%/2614
2. Bank sync uses 90-day lookback on first sync (when last_synced_at is null)
instead of hardcoded 7 days for all syncs
3. Bank file import improvements:
- Shared date normalizer supporting DD.MM.YYYY, DD/MM/YYYY, YYYYMMDD
- Silent row skips now reported with reason in issues[]
- Decimal separator mismatch detection in generic CSV
- Swedish error message with format diagnostics on detection failure
- Date format selector in column mapping UI
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: enhance validation for moms_period and conditionally set vat_number and moms_period based on vat_registered
* Enhance preprocessing for first year fields, fiscal year end month, and accounting method in schema validation
* fix: simplify schema validation by removing unnecessary preprocessors
* fix: prevent setting invalid values in select components across multiple forms
* fix: improve layout responsiveness and conditional rendering in onboarding steps
- SIE parser: Windows-1252 and CP437 encoding detection and decoding
- Bank file parser: add Nordea Business (Företag) CSV format
- Bank file parser: improve format detection for SEB, Länsförsäkringar, generic CSV
- INK2 engine: calculate årets resultat (7222) from income statement for open fiscal years
- Dashboard: parallel Supabase queries, simplified dashboard page
- Fix Swedish characters (å, ä, ö) in BAS data descriptions, validation messages, AI consent disclosures
- Import wizard UI improvements across all steps
- Migration: add 'bas_range' match type to sie_account_mappings constraint
- Extensive new tests for SIE parser encoding and bank file parser
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: mobile UX improvements across dashboard pages
- Responsive layouts with stacked mobile forms and grid desktop views
- Safe area handling (viewportFit cover, bottom nav insets, main padding)
- Touch-friendly dialogs, tabs, and page headers
- Mobile nav drawer open/close animation
- Dashboard layout parallel Supabase queries
- SIE parser: handle quoted VER/TRANS fields
- Skip account override for template-based transaction booking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review feedback from Greptile
- Fix nav drawer close timer race condition with useRef + clearTimeout
- Show locked VAT rate as read-only label on mobile instead of hiding it
- Remove dead bottom-16 class overridden by inline style
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add LICENSE (AGPL-3.0-or-later), CONTRIBUTING.md, SECURITY.md, DCO, and NOTICE files.
Rewrite README for open-source audience with self-hosting instructions.
Redesign color palette to grayscale chrome theme across all components.
Add transaction uncategorize API route with tests.
Fix VAT account name mismatches in migration 052.
Improve import page with SIE file support and loading skeleton.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace PSD2 bank integration as the default with file-based bank
import (CSV/XML), which better suits Swedish sole traders and small
companies. Enable Banking is now an opt-in extension.
- Phase 1: Extract generic transaction ingestion service (ingest.ts)
with dedup, auto-categorization, and OCR-based invoice matching
- Phase 2: Bank file parser library supporting Nordea, SEB, Swedbank,
Handelsbanken CSV formats and ISO 20022 camt.053 XML
- Phase 3: Database migration adding import_source, reference columns
and bank_file_imports tracking table
- Phase 4: Import wizard UI (5-step flow) and API routes for parse/execute
- Phase 5: Move Enable Banking to extensions/enable-banking/ with
commented-out loader entry for opt-in activation
- Phase 6: 104 new tests (ingestion + all parser formats), fixing
Nordea detection overlap and camt.053 XML tag collision bugs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>