9fc05c383f feat(notices): one aggregated notice line instead of stacked degraded-state banners (#1733)
* feat(notices): lib/notices aggregator + single notice line on Hem

Degraded-state surfaces (broken/expiring bank connections, Skatteverket
reconnect, failing cloud backups, wrong-account hint) each hand-rolled
their own detection and stacked independently on the dashboard. This adds
lib/notices, mirroring lib/worklist, as the single owner of every health
predicate, and de-clutters the surfaces:

- lib/notices/{types,predicates,categories,aggregate}: five documented
  categories with a fixed priority order; every predicate soft-fails to
  null; pure decision helpers live in predicates.ts so 'use client' pages
  can import them without pulling server-only modules. Broken supersedes
  expiring for the same bank connection by construction (status filter).
- GET /api/notices + POST /api/notices/dismiss (withRouteContext), and a
  notice_dismissals table (per company+user+notice_id, RLS user-scoped).
  Notice ids embed a state discriminator, so a dismissal hides exactly
  the state the user saw and a NEW failure surfaces again.
- Hem renders only the highest-priority notice as ONE AttnLine where the
  boxed BackupHealthBanner card sat (banner deleted; its multi-provider
  sentence logic moved into the backup_failing predicate), with a quiet
  "+N till" inline expander. otherAccountHint joins the same list as the
  lowest-priority category instead of an unconditional extra line.
- transactions and skattekonto keep their own AttnLine copy/CTA but source
  the reconnect decision from the shared skvStatusNeedsReconnect /
  skvAuthErrorNeedsReconnect predicates; Hem's Bevaka row imports the
  expiring-consent day-math instead of duplicating it.
- design.md convention 6 addendum: max one global notice line + max one
  page-domain attn line (locked convention: needs founder sign-off).
- i18n: new notices namespace in sv+en; moved banner/hint keys deleted.
- notice_dismissals classified as archive-excluded (UI state, not
  räkenskapsinformation) to satisfy the full-archive contract.

SkatteverketPromoCard keeps its localStorage dismiss for now; migrating it
to notice_dismissals is a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(notices): stable dismissals with reaping, bounded ids, unnamed-bank copy

Review fixes on the notice aggregator:

- Migration renamed 20260819080000 -> 20260819190000_notice_dismissals.sql
  (version collision with another in-flight PR; content unchanged).
- backup_failing dismissal stability: the id no longer embeds
  last_auto_sync_at / needs_reauth_at, which the cron re-stamps while the
  SAME incident persists and so resurrected a dismissed notice daily. The
  id is now stable per (provider, reason), and the opposite direction is
  kept correct by stale-dismissal reaping in getCompanyNotices: when a
  category is currently healthy, the caller's stored dismissals for that
  category (matched on the 'category:' id prefix) are best-effort deleted,
  so error -> dismiss -> healthy (reaped) -> new error resurfaces. Audit of
  the other ids: bank ids embed connection id + status/expiry and skv
  embeds the incident's first-error/expiry timestamp (markNeedsReconsent
  only fires post-connect), all stable per incident; they get the same
  reaping as hygiene. Contract documented on Notice.id in types.ts.
- NULL bank_name no longer interpolates the Swedish fallback 'banken' into
  the English message: a bank_broken_one_unnamed message variant (sv + en)
  is selected instead of a name param.
- Bounded notice ids: folding several connections into one discriminator
  now collapses to count + first 8 hex of a sha256 over the sorted parts
  (node:crypto, server-only) instead of concatenating uuids; single
  connection ids stay human-readable. Dismiss schema cap tightened to 200
  with an updated rationale.
- Tests: persisting failure stays dismissed across two aggregations,
  healthy state reaps, new failure after reap resurfaces, hint never
  reaped, failed reap swallowed, 30-connection id under 200 chars and
  stable across orderings, unnamed-bank variant, sorted backup id stable
  across cron re-stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(notices): pg-real coverage for the notice_dismissals policies

The coverage gate is right to flag the migration: every policy on this table
binds company membership AND auth.uid(), and nothing exercised it. The suite
pins the property that makes the table different from the rest of the schema:
a dismissal is personal, so a colleague in the same company keeps seeing a
notice the other member hid. It also covers the upsert re-stamp (which needs
the UPDATE policy), cross-tenant refusal, dismissing on behalf of another
user, the caller-scoped DELETE that reaping relies on, and the composite key.

Falsification-verified against a real Postgres: weakening the SELECT policy
to company-only scoping fails the colleague-isolation test.

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>
2026-08-20 10:27:52 +02:00
2026-07-21 23:00:15 +02:00
2026-06-12 16:35:30 +02:00
2026-07-21 23:00:15 +02:00
2026-07-24 15:03:50 +02:00
2026-07-21 23:00:15 +02:00
2026-07-05 03:05:09 +02:00
2026-07-24 15:03:50 +02:00
2026-07-23 16:16:55 +02:00
2026-07-05 03:05:09 +02:00
2026-07-05 03:05:09 +02:00

Accounted

Open-source Swedish accounting software for sole traders (enskild firma) and limited companies (aktiebolag). Double-entry bookkeeping that complies with Swedish accounting law, built to be operated by you or by your AI agent.

License: AGPL-3.0-or-later Core Build pg-real tests Docker

Website · Hosted app · Documentation

Why Accounted?

Compliant by construction. Accounted implements double-entry bookkeeping under Swedish accounting law (Bokföringslagen). Voucher immutability, sequential voucher numbering, period locks, and 7-year document retention are enforced by database triggers, not by convention. Corrections are made the legal way, with reversal entries (storno), never by editing history. See ARCHITECTURE.md for how.

Agent-native. The full bookkeeping engine is exposed as 100+ MCP (Model Context Protocol) tools with scoped API keys, so an AI agent can do the books in Accounted: categorize transactions, draft vouchers, reconcile periods, and prepare declarations. Posting is staged for human approval, so the agent proposes and you decide.

Yours to run. AGPL-3.0 licensed and fully self-hostable with Docker and Supabase. Use the hosted version at app.gnubok.se or run your own.

Features

  • Double-entry bookkeeping -- BAS 2026 chart of accounts, draft/commit workflow, sequential voucher numbering
  • Invoicing -- Create, send, and track invoices with mixed VAT rates and PDF generation
  • Bank reconciliation -- PSD2 bank connection via Enable Banking, 4-pass automatic matching
  • VAT declaration -- SKV 4700 form mapping, per-rate breakdown, EU/export handling
  • Tax reports -- NE-bilaga, INK2, SRU export for Skatteverket
  • Payroll -- Salary runs, payslips, and AGI (arbetsgivardeklaration) employer declarations
  • Supplier invoices -- Registration, payment tracking, input VAT deduction
  • Document archive -- SHA-256 integrity, 7-year retention enforcement, full archive ZIP export
  • SIE import/export -- Standard Swedish accounting interchange format
  • Agent access (MCP) -- 100+ bookkeeping tools over the Model Context Protocol, with scoped API keys and staged approvals
  • Extension system -- Opt-in plugins for AI categorization, receipt OCR, email, calendar, and more

Self-Hosting

git clone https://github.com/erp-mafia/accounted.git
cd accounted
./setup.sh              # Prompts for Supabase credentials, generates .env
docker compose up -d

You need a Supabase project and must apply the database migrations before first use. See docs/SELF-HOSTING.md for the full step-by-step guide, including Supabase setup, auth configuration, optional features (AI, email, push notifications), and troubleshooting.

Development Setup

Prerequisites: Node.js 20+, a Supabase project.

npm install
npm run dev       # Start dev server (auto-generates extension registry)
npm test          # Run tests
npm run build     # Production build
npm run lint      # ESLint

See CONTRIBUTING.md for the full development workflow.

Tech Stack

  • Framework: Next.js 16 (App Router), React 19, TypeScript (strict)
  • Database: Supabase (PostgreSQL + Row Level Security + email/password auth + TOTP MFA)
  • Styling: Tailwind CSS 4 + shadcn/ui
  • Integrations: Enable Banking (PSD2), Anthropic SDK, LangChain, OpenAI, Resend, JSZip

Documentation

Community

Contributing

Contributions are welcome. See CONTRIBUTING.md for the full guide.

All commits require a DCO sign-off (git commit -s).

License

AGPL-3.0-or-later with an extension exception: third-party extensions that interact solely through the documented Extension API may be licensed under any terms, including proprietary. See LICENSE for details and NOTICE for third-party attributions.

S
Description
Accounted — svensk bokföringsmotor (AGPL, BAS 2026, BFL-compliant, 150+ MCP tools). Finance-kapacitet brevet ERPNext. ADR-ENGAGEMENT-001-tillägg.
Readme AGPL-3.0
56 MiB
Languages
TypeScript 93%
PLpgSQL 5.8%
JavaScript 0.4%
HTML 0.3%
MDX 0.2%
Other 0.1%