fea5dfd1f912ec1111cec662cd8362f9aa55dfd9
21 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c187fabf92 |
feat(shopify): Shopify order/refund feed into the transactions inbox (#1474)
* feat(shopify): Shopify order/refund feed into the transactions inbox
New extensions/general/shopify feed extension, modeled on the WooCommerce
feed: connect a Shopify store with Dev Dashboard custom-app client
credentials (client credentials grant, ~24h tokens, never stored), then a
nightly cron + manual sync imports paid orders and refunds via the GraphQL
Admin API (pinned 2026-07) into the transactions inbox on clearing account
1584. Feed-only: nothing auto-books. Zero PII fields are queried, keeping
the app outside Shopify's protected customer data program.
- shopify_connections migration (RLS, revoke-never-delete, encrypted
client id/secret) + shopify_sync capability and bank_sync-mirrored
backfill
- frozen external_id scheme shopify_{shop_domain}_order|refund_{id},
scoped on the shop domain so reconnects never re-import
- cursor sync on updated_at windows with 24h overlap, lock-date drop at
map time, ingest-failure cursor floor, deadline stop-and-resume,
revoked-credential flip
- /import card + settings panel, sv/en i18n, cron 03:15 in vercel.json +
regenerated Docker crontabs, logo, events, panel registry
- 65 unit tests + pg-real RLS test; extensions.schema.json enum also
gains the missing stripe entry (pre-existing drift)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(shopify): review findings from PR 1474
- token exchange: a 429 that survives every retry is throttling, not a
credential failure; stop remapping retryable 4xx to 401 so sustained
throttling can no longer flip the connection to revoked and delete the
stored credentials (CodeRabbit critical)
- order sync: advance a scanned-through watermark (run start, capped by
the failure floor) after a fully-listed window, so empty first runs and
quiet stores rotate to the back of the cron's oldest-first selection
instead of permanently occupying the 50-connection batch (CodeRabbit
major, starvation)
- add handler-level tests for the orders cron route (auth 401, disabled
503, unconfigured no-op, query failure, capability skip, happy path,
per-connection failure isolation, revoked marking)
- add 401 tests for /sync, /transaction-sync and /disconnect; pin the
cursor floor rule with a two-order page; stub the encryption key via
vi.stubEnv
- note in the panel description (sv/en) that orders can mix VAT rates and
must be split at booking (Swedish review advisory)
- DECISIONS.md: wrap underscore identifiers in backticks (MD037)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
93f81f03e8 |
feat(providers): WINT migration provider behind WINT_MIGRATION_ENABLED (#1446)
* feat(providers): WINT migration provider behind WINT_MIGRATION_ENABLED Adds WINT (wint.se) as a sixth migration provider, built against the OpenAPI specs WINT's own API host serves publicly. Tier A scope: only the partner-facing v1 endpoints are used; the general ledger is fetched as vouchers/accounts and rendered as SIE 4E by our own sie-builder, with opening balances for earlier years derived backward from the current-year Ib anchor. Auth is the user's WINT login exchanged once for a JWT pair; the password is never stored. Ships dark: the wizard shows a disabled "Kommer snart" card, and the server-side /connect gate rejects WINT until WINT_MIGRATION_ENABLED=true. Live verification against a real WINT account is still outstanding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(providers): harden WINT provider per PR #1446 review findings Addresses CodeRabbit and Swedish accounting review feedback in one pass: - Ib anchor selection now uses WINT's unfiltered fiscal-year list, so an active year outside the allowed import window can never silently anchor the wrong year; the voucher chain is extended through the anchor and a per-year fetch failure fails that year loudly instead of sinking the whole migration. - Auth token exchange is strict: only LoginState Success with a complete access+refresh pair mints a consent (a pair without a refresh token is unrefreshable and would break days later). - WintApiError no longer retains full response bodies (bounded 300-char diagnostic; bodies can carry customer data and errors get logged). - sie-builder refuses to render structurally invalid vouchers (missing account number or booking date) and documents deleted-voucher gaps in a #PROSA record per BFL 5 kap 6-7 §. - Account classification: 20xx is equity, 83xx is financial income. - SIE validator accepts EUBAS97 as BAS-based (standard kontoplanstyp; it previously produced a false non-BAS warning on every WINT/Bollbok file). - New tests: resolveConsent WINT refresh flow, credential upsert payload (no mail/password persisted), WINT fetch failure path, EUBAS97 warning regression, builder invalid-data rejection, vi.clearAllMocks hygiene. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(import): pin EUBAS97 acceptance to the exact SIE spec value Review follow-up on PR #1446: match EUBAS97 exactly instead of any EUBAS* prefix, so the non-BAS kontoplan warning stays pinned to the four kontoplanstyp values the SIE 4B spec enumerates (BAS95, BAS96, EUBAS97, NE2007) rather than silently accepting unknown future variants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
707d597b2e |
feat(woocommerce): store order/refund feed extension (#1442)
* feat(woocommerce): store order/refund feed extension Connect a WooCommerce store via the wc-auth key handshake (manual key fallback) with per-store consumer key/secret AES-256-GCM encrypted at rest, and import paid orders and refunds into the transactions inbox as a bank-style feed on the 1680 cash account. Feed-only: nothing auto-books, gateway fees/payouts are out of scope (core wc/v3 does not expose them). Sync is cursor-paginated on modified_after (offset pages only inside same-second date_modified ties), terminates on an empty page, holds the cursor below failed refund fetches / ingest errors / deadline-skipped work, checks the time budget between refund fetches, and drops rows dated on or before bookkeeping_locked_through on every run. Nightly cron gated on the extension registry + new paid capability woocommerce_sync (backfilled to existing bank_sync grant holders). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(migrations): move woocommerce migrations past main's 20260806090000 origin/main gained 20260806090000_recurring_schedule_interval_months while this branch was in flight; identical version timestamps abort the Supabase apply, so the two new migrations move to 20260806170000/20260806170100. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(woocommerce): resolve CodeRabbit review findings - callback 503s early when WOOCOMMERCE_CREDENTIALS_ENCRYPTION_KEY is unset: encryptCredential would otherwise throw after the probe and strand the pending row without error_message - disconnect and upstream-revoke clear the encrypted consumer key/secret: nothing reads them after revoke and keeping decryptable dead credentials is unnecessary retention - manual sync gets a 240s time budget and the panel reports a truncated run as 'partial, sync again' instead of a normal completion - listOrderRefunds terminates on an empty batch (hosts may cap per_page), dedupes by id against hosts that ignore page, and caps total pages - unparseable money strings count as errors and log instead of being silently identical to a zero total - pg test uses per-run unique store URLs so committed rows cannot hit the store_url partial unique index across pg-real runs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(woocommerce): resolve CodeRabbit cycle-2 findings - listOrderRefunds throws when the page cap is exhausted with data still flowing, instead of returning a silently partial list the sync cursor would advance past; the error routes into the existing held-cursor refund-retry path - partial sync results keep the row-error count, and the partial toast string surfaces it (ICU plural, hidden at zero) in both locales Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: retrigger CI after dropped push event Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1a7152a7af |
feat(settings): skyline masthead on Abonnemang + AI works-with marks on API tab (#1241)
The Abonnemang tab gets a quiet decorative masthead: the marketing site's halftone Stockholm skyline as a wide banner strip on the frame tint, waterline pinned to the strip's bottom edge (same physics as the onboarding backdrop). Shown in every billing state; purely decorative. The API tab's "Anslut MCP-klient" group gets a works-with strip using the site's monochrome halftone Claude and OpenAI marks (copied into public/illustrations and registered in the shared manifest), with a bilingual caption. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d47b19fd74 |
docs: correct the systemdokumentation template to match the actual system (#1240)
The downloadable BFL 5 kap. 11 § template (Hjälp > Dokumentmallar) had not been touched since March and had drifted into describing a system we no longer run. Users archive this document as räkenskapsinformation for 7 years, so the wrong facts were being filed as compliance evidence. Corrected against the code: - Auth was "Magic link via e-post (lösenordsfri)". It is e-post + lösenord with TOTP two-factor, plus optional BankID (lib/auth/require-auth.ts, lib/auth/bankid.ts). - Access control claimed "en användare kan enbart se och redigera sin egen data" with a single "Kontoägare" role. Data is company-scoped via RLS and there are four roles, owner/admin/member/viewer (20260330130000_multi_tenant_company_refactor.sql). - Rättelse described storno only. Inline rättelse in the same verifikat has existed since 20260723210000_verifikat_inline_rattelse.sql; the template now documents both tracks and the rule that a locked or closed period leaves storno as the only route. - Voucher numbering claimed uniqueness "per räkenskapsår och användare" and a single series. It is per company, fiscal year and series, and the series is configurable per source type (lib/bookkeeping/engine.ts). - Product is Accounted, not erp-base (lib/branding/service.ts). - OpenAI is listed as an embeddings integration; only a stray env var remains in lib/init.ts. Removed. Added Skatteverket, BankID and PostHog, which were missing. - Navigation paths were pre-redesign: Kontoplan is under Data, moms under Skatt, and behandlingshistorik exports from Importera/Exportera > Säkerhetsbackup, not the "Rapporter > Audit trail" that does not exist. - BAS 2025/2026 -> BAS 2026. Added the sections a systemdokumentation needs and this one lacked: API keys and machine access (external agents can write to the ledger under scoped keys and are logged as the actor), löner/AGI, anläggningsregister, periodiseringar and dimensioner as delsystem, and säkerhetskopiering. The granular "BFNAR 2013:2 punkt 9.x" citations are dropped rather than renumbered; see DECISIONS.md. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2da96c0be2 |
fix(agent): stop sending every page view to a third-party avatar CDN (#1226)
* fix(agent): stop sending every page view to a third-party avatar CDN Eight avatar SVGs were loaded from api.dicebear.com on every render. In an accounting product that meant every authenticated page view told a third party who was looking at it, from a domain we do not control, on the path of a logged-in surface. A firewalled or self-hosted install showed no faces at all. The SVGs are now generated once and served from public/agent-avatars. Each entry records the seed it came from, so the set can be regenerated reproducibly, and the command to do it is in the file. The licence question that made this look like a founder decision resolved itself on inspection: Notionists is by Zoish under CC0 1.0, public domain, no attribution required. Confirmed on dicebear.com/licenses and, more usefully, in each downloaded file's own RDF metadata, so the terms travel with the asset rather than living in a commit message. Tests pin the properties that matter rather than the file list: no entry may be a remote URL, every entry must have a file behind it, and no shipped SVG may carry an <image href>, a url(https://…), an xlink:href or a <script>, since self-hosting a file that then phones home would reintroduce exactly the request this removes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(agent): assert the property, not a list of elements, for avatar externals The external-reference check enumerated <image href>, url(https://…) and xlink:href, which left <use href>, <feImage href> and scheme-relative //host through: exactly the requests the guard claims to prevent, via elements it happened not to list. That is how this sort of allowlist rots. It now strips the parts that legitimately carry URLs and are never fetched (the RDF metadata block, xmlns declarations) and then asserts that NOTHING in what remains points off-origin. Verified by injecting each of the four bypasses into a real asset and confirming the test fails on all of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c210a01db5 |
docs(design): sync design guidance with the shipped system + new Stripe mark (#1211)
* docs(design): sync design guidance with the shipped frame-layout system The design-scan skills and workflow predate the 2026-07 UI migration; they delegated to .claude/rules/design.md but their inline checklists knew nothing of the locked conventions, so scans could not flag violations of frame layout, one-line rows, chips-as-exceptions or .attn. This adds the conventions to scout-design, loop-design-scan, the /create-ticket design prompt and the design-scan workflow contract. design.md itself gets the two post-migration decisions it was missing: the fiscal-year primitive is FyPicker (ContextPicker chip-dropdown, convention 8), not the legacy FiscalYearSelector, and the founder-chosen Fonster settings language (flat hairline rows, switches, dirty-only sticky save bar, 920x680 modal, replaceState tab switching) is now convention 15. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(import): swap the Stripe chip to the current parallelogram mark Replaces the pre-rebrand Stripe wordmark svg with the current white-on-purple parallelogram icon (same asset the website repo uses). stripe.svg had exactly one reference; it is removed rather than left orphaned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d3c37dac08 |
feat(stripe): enable Stripe integration and update UI components (#1159)
* feat(stripe): enable Stripe integration and update UI components * refactor(import): streamline user ID and sandbox status fetching logic |
||
|
|
2bec5acedb |
feat(ui): rest-of-nav 1 — Viktiga datum, Skattekonto, Periodiseringar, Import to the concept language (#1140)
* feat(ui): concept scenes 17/24/32/33 for Viktiga datum, Skattekonto, Periodiseringar and Import (rest-of-nav 1) Viktiga datum: thread rows with type-icon circles behind a type seg (Alla/Skatt/Fakturering/Egna), Narmast countdown pane, Ny deadline lifted to the page header, both banners replaced by one AttnLine, mark-done via ConfirmDialog. DeadlineCard/DeadlineFilters die; DeadlineRow is the row. Skattekonto: card-less saldo hero with OCR + quiet copy, shortfall AttnLine computed from the next drain date with a betalningsuppgifter dialog (bankgiro 5050-1055 + OCR), one dry-table with Kommande/Forfallna/ Genomforda band rows, chips only on unbooked genomforda rows, quiet hover actions. Tabs and per-row badge noise are gone; the concept's Saldo column is dropped because SKV stores no per-row running balance. Periodiseringar: banner becomes an AttnLine with inline Bokfor forfallna (now confirm-first), house seg with Aktiva count, dry-table with muted normal states and animated RowFoldout for installments, Los upp nu as a quiet hover link through the shared ConfirmDialog. Import: tabs collapse into one two-column row list (Importera | Exportera) in the concept row language; SIE export moves into a small dialog and Molnsynkronisering folds the CloudBackupCard open in place. The /import?view=export#sie-export and /import#cloud-backup deep links keep working. Sandbox notice is an AttnLine. All four pages get stagger-enter, a help popover behind ? and sv+en keys for every new string. 9189 tests green, lint clean, guards pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ui): align dashboard loading skeletons with the migrated page silhouettes Folds in the parallel WIP from this checkout at the founder's request: every loading.tsx under (dashboard) now mirrors its migrated page row-for-row (24px title block, pill actions, borderless table heads, single-line rows), and the shared (dashboard)/loading.tsx takes Hem's greeting + Att gora silhouette. Also lands the pending DECISIONS.md lines (onboarding swap plan note + rest-of-nav 1 deviations). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * polish(ui): authority logos, stat-tile skattekonto hero, quieter type on rest-of-nav 1 Viktiga datum: statutory deadlines wear the receiving authority's mark (Skatteverket for tax dates, Bolagsverket for arsredovisning/arsstamma) as a small badge on white; other deadlines keep the neutral type icons. Row dates go muted, titles drop font-medium, the Narmast countdown steps down to the house text-4xl display scale. Skattekonto: the 32px serif hero becomes two compact metric tiles in the KPIHeroCards idiom (saldo with the Skatteverket mark + OCR meta, nasta dragning with date and event count), matching how numbers read on the migrated pages. Periodiseringar: chevron column dropped; rows expand on click exactly like the verifikat list. Import: provider logo chips return on Hamta fran annat system (live- version parity) and Koppla bank carries the Enable Banking mark. Adds skatteverket(_color), bolagsverket, enable-banking plus claude/ anthropic marks (for future use) under public/logos/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * polish(ui): keep Importera and Exportera as separate tabs on the import page Founder feedback: the merged two-column landing goes back to the familiar split. The house seg switches between the Importera rows and the Exportera rows (SIE 4 dialog + Molnsynkronisering fold), ?view=export selects the export tab again and the hash deep links flip to it before opening their surface. Row language and logo chips stay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): readable Enable Banking mark and full-width skattekonto The enable-banking.webp is the full stacked logo in white-on-transparent: invisible on the light chip and mush at 16px. The chip now uses a cropped 368px icon square (enable-banking-icon.png) with the marketing site's grayscale+brightness treatment in light mode and a white lift in dark. Skattekonto loses its max-w-3xl cap so the table stretches the content column exactly like Bokforing and Transaktioner; the saldo tiles take KPI-card width (lg:grid-cols-4). Import's tab columns stretch too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): address review-bot findings on rest-of-nav 1 CodeRabbit triage, all three confirmed real: the Bokfor forfallna attn action is hidden for read-only users instead of rendering a no-op link; a failed deadline edit rethrows so the form stays open with the user's input; authority marks are reserved for statutory (system-generated) deadlines: a manual tax-category deadline keeps the neutral icon. Compliance swarm's two high findings verified clean, no change needed: /api/bookkeeping/accruals/:id/dissolve and /api/deadlines/:id (+ /complete) all run through withRouteContext with company_id scoping and 404 on miss. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e11f70b347 |
Bug/gh issues fiz (#1103)
* refactor: optimize page loading and data fetching * fix: resolve recurring production runtime errors * feat: add MCP company and customer updates * fix: handle year-end tax adjustments * feat: harden annual report compliance * fix: expand invoice logo and font support * fix: sanitize API route error responses * fix: sanitize user-facing error messages * feat: persist onboarding and tax assessment notices * fix: reduce cloud backup audit churn * feat: refine invoice editor layout * fix: show saved tax adjustments in INK2 * fix: complete annual report API mappings * docs: record operational safeguards and decisions * fix: harden annual report review findings * fix: adjust column span for description based on VAT registration * New css class name |
||
|
|
e2d6c92e3a |
feat(onboarding): illustrated halftone backdrop from marketing-site art (#1080)
* feat(onboarding): illustrated halftone backdrop from marketing-site art Ports the gnubok-website halftone illustration set into the onboarding flow so signup -> app feels like one product: - OnboardingBackdrop: petal field across the paper background (radially masked to stay calm behind the form), Stockholm skyline dissolving into the bottom edge, two clouds drifting at reading pace that bounce off the viewport and the onboarding panel (IllustrationFloaters, physics ported from the website's BouncingFloaters). - Per-step instrument ghosted in white ink on the dark card header: pencil -> notebook -> adding machine -> calculator, one per step. - Dark mode via invert/hue-rotate filters; prefers-reduced-motion parks the floaters; all art is aria-hidden and pointer-events-none. Applies to /onboarding, /onboarding/agent and /select-company via the shared (onboarding) layout. No new strings, no API changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(onboarding): strip backdrop to skyline + step art per founder review Founder kept the Stadshuset skyline and the per-step header instruments; the petal field and drifting clouds are cut. Removes the now-unused floater physics component and the petals/cloud assets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
20989379bb |
feat(sandbox,branding): prod-parity demo with AI gating + accounted rebrand (#585)
* feat(sandbox,branding): prod-parity demo with AI gating + accounted rebrand Sandbox now ships with seeded suppliers, supplier invoices, an asset, a verified agent_profile, and pending operations so the demo company exercises every prod surface. Server-side `guardSandbox()` short- circuits any AI or paid-external API call (Bedrock chat/composer, Resend invoice send, Riksbanken FX, VIES, etc.) and the AgentSheet swaps in a SandboxAgentPreview that explains what's gated and offers a register CTA. DashboardContent no longer mounts the NewUserChecklist when the agent is already built, fixing the path that let sandbox users still trigger /onboarding/agent. Visible branding flips from Gnubok to Accounted: new BrandWordmark component (Hedvig Letters Serif 700), new app/icon.png + PWA icons generated from the accounted icon, default appName updated. URLs, header names, API key prefixes, hostnames, and event/cookie/ localStorage keys keep `gnubok` — the rebrand is visual only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sandbox): hardcode supplier-invoice arrival numbers in seed get_next_arrival_number is MAX(arrival_number) + 1 against the same table we're about to insert into. Calling it twice before either row lands made both calls return 1, which then violated the (company_id, arrival_number) unique index — POST /api/sandbox/seed 500'd on first sandbox start. The seeded company is brand new in this branch so 1 and 2 are guaranteed unused; hardcoding side-steps the race entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sandbox): set paid_amount=0 on unpaid supplier invoice PostgREST normalizes the column set across rows in a bulk insert, so the second supplier invoice (Espresso House, status=registered) was being sent with paid_amount=null because the first row (Telia, paid) set it. supplier_invoices.paid_amount is NOT NULL DEFAULT 0; the default only kicks in when the column is *absent* from the payload, not when it's explicitly null. Set it inline to side-step the normalization. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sandbox): set actor_type=agent_chat on seeded pending_operations pending_operations only allows user-scoped INSERTs via the `pending_operations_chat_insert` policy, which requires actor_type='agent_chat' alongside auth.uid()=user_id + company membership. The seed was inserting with the default actor_type='user', tripping the RLS check. Also lift risk_level from preview_data (where it was unused) onto the row itself, matching the column added in 20260430120000_pending_operations_actor_and_risk. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(pr-review): address PR #585 review feedback Fixes called out by the core-only CI check, Greptile, and the compliance + Swedish-accounting bots: - AGI Programnamn pinned back to 'gnubok' (CI blocker). The XML Skatteverket receives must keep the stable software identifier regardless of the visual rebrand — same rule as the v1 health endpoint's `service: 'gnubok'` literal. - handleCreateAccount in SandboxAgentPreview + ChatEmptyState now wraps signOut() in try/catch so a transient Supabase failure doesn't strand the user on a dead button (greptile P2 × 2). - /api/currency/rate hard-fails on missing companyId instead of conditionally skipping the sandbox guard (greptile P2 / compliance V8.2.1). - topUpSandboxAdditions now delegates to ensureSandboxAgentProfile; the assistant persona lives in exactly one place across the seed, layout backfills, and top-up path (greptile P2 outside-diff / compliance SOC2 CC6.1). - ensureSandboxAgentProfile drops the userId param and sets verified_by_user_id to NULL — synthetic seed data should not attribute verification to a real user (compliance V8.2.1 / GDPR Art. 25(2)). Errors now logged via the structured logger instead of being silently swallowed (V16). - Sandbox seed swaps real-world company names (Telia, Espresso House) for clearly-synthetic Demo-prefixed brands using the 5559... documentation org-number range (compliance A.8.33). Asset cost bumped 24 000 → 35 000 SEK so the demo clears the förbrukningsinventarier threshold and illustrates capitalization unambiguously (swedish-asset-accounting). - Representation pending-operation preview corrected: VAT label fixed from 6% → 12%, and input VAT split between the avdragsgill (2641) and ej-avdragsgill (5811) portions to match swedish-vat / ML 8 kap rules (swedish-vat). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(pr-review): seed preview consistency + AGI Programnamn constant Two last review-bot items before merge: - Sandbox seed: the representation pending-operation preview was splitting the 240 SEK café meal 60/180 between 5810 and 5811, which is wrong for a single attendee under the 300 SEK / person avdragsgill cap (ML 8 kap) — the entire amount is fully avdragsgill in that case. Collapse the preview to a single 5810 + 2641 + 2440 entry so it matches the supplier_invoice_items row 1:1 and stops teaching demo users an incorrect bookkeeping pattern. - Hoist the AGI Programnamn 'gnubok' literal into a named constant with a comment pointing to potential future Skatteverket vendor registration (per the swedish-compliance bot's nit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sandbox): avoid BFL duplicate-verification on pending op + fix VAT cap comment Swedish-compliance bot caught two final nits: - The pending operation for the Demokafé representation was using the same supplier_invoice_number as the already-seeded supplier_invoices row (88245). If the sandbox user approved the staged operation, the insert would have created (or attempted) a duplicate verification — BFL 5 kap. requires each affärshändelse be recorded exactly once. Swap the staged operation's invoice number to a distinct value (INKOMMANDE-2026-001) so approval cleanly creates a new row. - The preview comment described the 300 SEK threshold as an "avdragsgill cap". The actual rule (ML 8 kap. 9 §) caps the deductible VAT at 25 % × 300 SEK × antal_personer = 75 SEK per person — the 300 SEK is the tax base, not the total. Math here is correct either way, but the comment now states the correct formula so future seed edits don't propagate the wrong understanding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5e1b0f791d |
feat(branding): implement dynamic branding in service worker and reports (#383)
* feat(branding): implement dynamic branding in service worker and reports * refactor(service-worker): remove push notification handling code * feat(service-worker): implement dynamic branding in service worker and related scripts |
||
|
|
064fb7f7a9 |
Add/white label (#381)
* feat(branding): add BrandingService with default-preserving env layer Introduce lib/branding/service.ts mirroring lib/email/service.ts. Defaults match current gnubok values exactly, so production behaviour is unchanged unless an env var (NEXT_PUBLIC_BRANDING_*, BRANDING_*) or extension override (via registerBrandingService) is set. Resolution order: defaults < env vars < extension override. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(branding): route root layout, manifest, and PWA assets through branding service - app/layout.tsx now reads title, description, themeColor, and apple-touch-icon from getBranding() instead of hardcoded values. - public/manifest.json replaced by dynamic app/manifest.ts so PWA name, short_name, description, theme_color, background_color, and icon paths are resolved at request time. The manifest now serves at /manifest.webmanifest (Next.js convention for the metadata file route). The previous /manifest.json URL is no longer populated; nothing in core references it after this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(branding): route email service and templates through branding service - resend-service.ts: From line uses getBranding().appName instead of hardcoded "Gnubok" in both the with-fromName and bare cases. - invite-templates.ts: subject, HTML header, body, plain text, and the team-invite variants all read from branding (sentence case in prose, uppercased for the styled <p> header). - consent-notification-templates.ts: signature fallback (companyName || branding) for both HTML and plain text variants. Defaults preserve the exact current strings ("Gnubok", "GNUBOK", "gnubok" in their respective contexts) so no email content changes for production. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(branding): route OAuth consent page through branding service The MCP OAuth consent page rendered for Claude Desktop / Claude.ai connector flows now reads the app name from getBranding() for both the HTML <title> and the body copy. Default still produces "gnubok" in lowercase prose, matching current behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(branding): route auth, dashboard, and onboarding text through branding service Replace user-visible "gnubok" / "Gnubok" references with calls to getBranding(). Touches: - Auth pages (login, register, mfa/enroll): logo src/alt, MFA TOTP friendlyName. - Onboarding (companies/new, invite, sandbox, WelcomeOnboarding, Step2CompanyDetails, NewUserChecklist, BankIdCompanyPicker, ArcimMigrationWorkspace): logo, headings, error/help text. - Dashboard fallback (companyName="gnubok") and settings (backup copy, ApiKeysPanel MCP connector name + login note, CompanyDangerZone, retention-notice). - API routes (support contact subject prefix, enable-banking consent email companyName fallback, AI inbox receipt-request appUrl, pain001 messageId prefix). - MCP server "open the gnubok web app" review message. - Salary/reports filings (AGI Programnamn, KU10 Programnamn, payslip footer, full-archive system metadata, SRU #PROGRAM line). Internal identifiers (cookie names gnubok-company-id / gnubok-invite-token, API key prefix gnubok_sk_, invite token prefix gnubok_inv_, MCP tool names, npm package gnubok-mcp, GNUBOK_API_KEY env name) are deliberately left unchanged — they're stable contracts that whitelabels must not break. Defaults match current behaviour exactly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(branding): support legal page field-level swaps for entity and contact Privacy and DPA pages now interpolate appName, legalEntity, and privacyEmail from the branding service instead of hardcoding "Gnubok", "Arcim", and "privacy@gnubok.se". Page metadata uses generateMetadata() so titles also reflect the brand. lib/support.ts now falls back to getBranding().supportEmail when SUPPORT_RECIPIENT_EMAIL is unset, so a single BRANDING_SUPPORT_EMAIL env var configures both the support form recipient and the displayed support address. Whitelabels with a different legal jurisdiction or entirely different DPA text should override the page route from an extension. Phase 1 intentionally only supports field-level swaps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(branding): add WHITELABEL.md and example branding extension WHITELABEL.md: fork checklist, env var reference, the "do not change" list (cookies, API key prefixes, invite token prefixes, MCP tool names, gnubok-mcp npm package, GNUBOK_API_KEY env name), out-of-scope items, the upstream sync workflow YAML to copy into a fork, conflict avoidance guidance, and a verification checklist. extensions/general/_example-branding/: copy-paste starter extension with index.ts (commented placeholder values for registerBrandingService), manifest.json, and README.md. Disabled by default (not added to extensions.config.json); whitelabels cp the folder, edit, and enable. sectors.test.ts: bumped expected extension count 12 -> 13 to account for the new starter extension on disk. The generated registry is unchanged because the example is disabled. The sync workflow YAML is documented inline in WHITELABEL.md rather than checked in as a workflow file. It's only meaningful in a fork -- gnubok itself has nothing to sync from. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(branding): address PR review — lazy support email + escape brand in HTML/XML Three issues from code review: P1 — lib/support.ts: SUPPORT_RECIPIENT_EMAIL was a module-level const, evaluated at import time before extensions register branding overrides via ensureInitialized(). Convert to getSupportRecipientEmail() lazy accessor; update the only caller in app/api/support/contact/route.ts. Extension-supplied supportEmail values now route correctly. P2 — app/api/mcp-oauth/authorize/route.ts: appName was interpolated into the consent page HTML without escapeHtml(), inconsistent with the existing escaping of companyName. Wrap appName.toLowerCase() in escapeHtml() at use sites in <title> and the body paragraph. P2 — lib/salary/agi/xml-generator.ts and lib/salary/ku/ku10-generator.ts: appName placed inside <gem:Programnamn> / <Programnamn> XML elements without escapeXml(), the helper already used for other admin-controlled fields in the same files. Wrap accordingly to prevent malformed XML if a brand name contains XML reserved characters. All admin-controlled inputs only — no user-exploitable path. Defense in depth, not a known incident. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(branding): security follow-up — lazy metadata, SRU/email header sanitization Self-audit after the PR review surfaced four more concerns. Fixes them with the same defense-in-depth posture as the prior review fixes. 1. app/layout.tsx — same eager-evaluation class as P1 support.ts. The module-level `const branding = getBranding()` froze branding before extensions registered, so extension-based overrides for title, description, themeColor, and apple-touch-icon silently never applied. - Convert to generateMetadata() / generateViewport() (lazy, run per request, see extension-registered overrides). - Inline getBranding() inside RootLayout for the apple-touch-icon href so it picks up overrides too. - Add ensureInitialized() at module level so extensions are loaded before the first metadata call. Mirrors the API route pattern. 2. app/manifest.ts — same class. The dynamic manifest function reads getBranding() per request, but if the manifest is requested before any other module has triggered ensureInitialized(), extensions are still unloaded. Add ensureInitialized() at module level. 3. lib/reports/ink2/sru-generator.ts — appName interpolated into the SRU `#PROGRAM` directive without sanitization. SRU's reserved char is `#` (directive marker) and CRLF injects new directives. Wrap in the existing sanitizeString() helper to match the pattern used for other admin-controlled fields in this file (#NAMN, #ADRESS, etc.). 4. extensions/general/email/lib/resend-service.ts — appName and the user-controlled fromName both flow into the From header. Resend's API does its own validation, but defense in depth: strip CRLF and angle brackets via a small sanitizeHeaderPart() helper before building the header string. fromName was a pre-existing surface; appName is new with this whitelabel work. All four are admin-controlled inputs (env vars or extension code), not user-exploitable. No known incidents — defense in depth, and correctness for extension-based whitelabels. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6d75b9a1bf |
feat: BankID authentication via TIC Identity API (#192)
* feat: add BankID authentication via TIC Identity API Integrate BankID as a login/signup method using the TIC Identity API. Users can authenticate with BankID QR codes (desktop) or deep links (mobile), link BankID to existing accounts, and skip TOTP MFA when BankID is linked. Removes Step 0 (role choice) from onboarding for all users. Adds enrichment data support for pre-filling company details from Bolagsverket during signup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — server-side rate limit, unlink clears MFA bypass - Add per-IP rate limit (5s cooldown) on /bankid/start to prevent unbounded billable TIC sessions from unauthenticated callers - Add /bankid/unlink endpoint that deletes bankid_identities AND clears app_metadata.bankid_linked so MFA enforcement resumes after unlink - Update BankIdSettings to call server-side unlink instead of client-side delete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move rate limiter to module scope, add BankID logo and year-end skill Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2ad8731dc9 |
feat: arcim migration wizard UX, import fixes, Sentry setup (#22)
* feat: import system improvements, INK2 fix, and Swedish text corrections - 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: arcim migration wizard UX fixes, Sentry setup, and extension scaffolding Arcim migration wizard improvements: - Progress bar now excludes non-interactive steps (migrating/result) - Fix OAuth text to match target="_blank" behavior (new tab, not redirect) - Display month names instead of "Månad X" in preview - Fix Swedish typo "förifylla" in no-company-info message - Replace native checkboxes with shadcn Switch in options step - Add ConfirmationDialog before starting migration - Show progress percentage during migration - Add "Nästa steg" guidance and navigation links in result step - Add "Försök igen" button in error state (returns to options) - Add Bokio company ID help text (GUID from URL) - Add Fortnox integration add-on hint on connection failure Also includes: SIE import system improvements, INK2 fixes, Swedish text corrections, Sentry error tracking setup, and arcim-migration extension scaffolding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback - Fix OAuth error recovery blank page (restore provider from URL params) - Pass real userId to MigrationWizard instead of empty string - Remove ~50 debug console.log statements from sie-import.ts - Fix comment referencing account 3740 → 3741 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
091d043c85 |
feat: UI polish, lint fixes, onboarding redesign, help page expansion, and test improvements
Broad update across dashboard pages, components, extensions, and lib code. Includes ESLint config additions, onboarding flow redesign, settings page refactor, help page content expansion, dead code removal, and test mock fixes. Adds dev docs and public assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
66a4027f1e |
feat: BAS data overhaul, currency revaluation, expenses, UI polish, and cleanup
- Update BAS account catalog with comprehensive SRU codes and K2 flags - Add currency revaluation service with tests and API route - Add expenses page and account deletion API - Enhance booking templates with new patterns and improved tests - Improve transaction categorization with template picker and description matching - Polish dashboard, onboarding, import, and transaction UIs - Refactor year-end service for multi-step closing - Move SRU generator to ne-bilaga, remove standalone SRU export - Remove unused dev docs, mock data, and extension hooks - Add invoice delivery note sequences migration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
838dc6b8b5 |
refactor: clean up codebase, remove dead code and obsolete docs
Remove influencer-era documentation, unused components, boilerplate assets, and ghost tiktok cron job. Add supplier invoice management, document API routes, and PWA icons. Replace boilerplate README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
0aecf5b41c |
Refactor to generic ERP base
Remove influencer-specific features (campaigns, TikTok, gifts, shadow ledger, contracts, briefings) and consolidate into a clean ERP foundation with core bookkeeping, invoicing, receipts, tax reporting, and calendar functionality. Reorganize database migrations into a clean numbered sequence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
a25d10a528 | Initial copy from influencer-biz |