chore(ui): retire the Building2 icon app-wide (#2235)

Founder request from the register walkthrough. Suppliers (nav, command
palette, empty state) use Truck; company and company-scoped surfaces
(active company badge, invite, home signpost, SIE preview, template
scopes, TIC workspace and its manifest) use Briefcase; the two bank
contexts use Landmark. The extension icon resolver no longer maps
Building2; the generated sector definitions follow the manifest.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-09-03 20:02:14 +02:00
committed by GitHub
parent b996da60ee
commit 34b677b02c
16 changed files with 39 additions and 35 deletions
+1
View File
@@ -1531,6 +1531,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-09-03] Kontakter is not a user-facing noun (founder, after the register walkthrough): the registers people see stay Leverantörer and Kunder, the new page is the queue 'Förslag från bokföringen' plus 'Bara i bokföringen', and confirming a suggestion creates the leverantör or kund row directly (promote_parties). A confirmed party with no role never appears in the UI. The party model underneath is unchanged
[2026-09-03] AGI redovisningsperiod = the payout month (agiReportingPeriod on payment_date), not salary_runs.period_*: Skatteverket files per the month the pay went out (kontantprincipen), so lön i efterskott (August work paid 25 September) is declared in September. The in-period payment-date guard (dashboard PATCH, lib/salary/update-run.ts, v1 PATCH, RunHeader min/max) is lifted rather than widened: its only stated reason was that the AGI keyed on period_*, and any residual month window would bite the next efterskott variant. Existing agi_declarations rows keep their stored period (no backfill): a declaration already filed under the earned month is a real-world correction with Skatteverket, not a re-key. New AGI_PERIOD_CONFLICT (409) refuses to overwrite a live run's declaration for the same payout month, since one month's AGI must cover every payment that month and the generator cannot merge runs. Issue #2191.
[2026-09-03] The cursor:// deeplink is its own allowlist provider (cursor_deeplink) rendered "Din egen dator" and never "Verifierad", after the skeptic, CodeRabbit and Superagent all made the same point: a custom scheme can be claimed by any local app (RFC 8252 section 8.4), so it carries loopback trust, not vendor trust, and the consent page must not say otherwise; https://www.cursor.com/... keeps the verified label. Same pass fixed the consent-page CSP for custom schemes: new URL('cursor://...').origin is the string "null", so form-action became `'self' null` and Chromium would have blocked the post-consent 303 (correctness skeptic refutation); the header now uses the scheme-source (`cursor:`) when the origin is opaque. Not done: rejecting a missing code_challenge at /authorize. A code minted without one is unexchangeable (verifyPkce against an empty challenge is always false, now pinned by a test), so it is fail-closed; making it fail earlier is a separate change touching every client.
[2026-09-03] The Lucide Building2 glyph is retired app-wide (founder, from the register walkthrough). Suppliers use Truck, companies and company-scoped things use Briefcase, banks use Landmark; the extension manifest icon name changed with it
[2026-09-03] settleInvoicePayment writes the invoice_payments row BEFORE the CAS status update and removes it in both failure branches, instead of inserting after the update: the kontantmetod cut-off reads invoice_payments only, so a paid invoice without a row is the #2019 defect itself; failing closed on the insert (voucher storno + INVOICE_PAID_BOOK_FAILED) keeps GL, sub-ledger and invoice status in step. The #2019 backfill inserts only where exactly one posted payment voucher exists (invoice_paid / invoice_cash_payment with source_id = invoice); zero or several vouchers are reported, never guessed, and every row is tagged backfill:#2019 in notes so one DELETE reverts a run.
[2026-09-03] #2019 skeptic round: the invoice_payments row is written by one helper (lib/invoices/invoice-payment-row.ts) from all four transaction-less settlement paths (dashboard, v1, MCP mark-paid, Stripe), with amount = applied amount (new paid_amount minus prior) rather than cash received, so a 3740 öre absorption never produces a negative fordran in the cut-off or a wrong storno restore. A payment row with transaction_id NULL does NOT count as "reconciled to a bank line" in the two duplicate detectors: the bank line for a manual settlement arrives later and the voucher must still be offered as a twin. The backfill dates rows from the voucher entry_date (paid_at was wall-clock before #1332), refuses rows that disagree with the voucher's 1510 credit / settlement debit, reports partially covered invoices (rows_short) instead of patching them, and records each executed run in behandlingshistorik (new event type InvoicePaymentRowBackfilled, migration 20260903180000). Not done here, pre-existing: the bank-match and pending-operations match paths still store cash received as the row amount, and the kontantmetod cut-off ignores ROT/RUT deduction_total (1513 share shows as outstanding); both filed as follow-ups.
[2026-09-03] Enable Banking ASPSP_ERROR ladder (#2202): the widest history window a bank has answered is stored per account as accounts_data[].accepted_history_days (no migration; same write-back as dedup_scope and the balance), not as an absolute date, because a bank's limit is a width from today and an absolute known-good date would age into a genuine width rejection. On a rejected window: no wider than accepted = bank unavailable after ONE call; wider = one retry at the accepted width, then unavailable (was up to five calls). AspspUnavailableError maps to 503 BANK_UNAVAILABLE on the web sync route with copy that says the connection does not need renewing; the agent path keeps the contract code BANK_SYNC_FAILED (adding a code touches core contract + structured-errors + v1 docs, left for a follow-up) but no longer persists renewal advice. The envelope's `detail` field is NOT used as a signal: one sample, "Unknown error", identical to a width rejection. A narrowed sync now returns history_from so the UI can say from which date it is complete.
+4 -3
View File
@@ -12,12 +12,13 @@ import {
BookOpen,
Receipt,
Calculator,
Building2,
FileText,
FileDown,
ExternalLink,
ChevronDown,
ChevronUp,
Briefcase,
Landmark,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { SupportLink } from '@/components/ui/support-link'
@@ -214,8 +215,8 @@ const categoryConfig = {
moms: { labelKey: 'category_moms', icon: Receipt, color: 'bg-secondary text-muted-foreground' },
faktura: { labelKey: 'category_faktura', icon: FileText, color: 'bg-secondary text-muted-foreground' },
bokföring: { labelKey: 'category_bokforing', icon: BookOpen, color: 'bg-secondary text-muted-foreground' },
bank: { labelKey: 'category_bank', icon: Building2, color: 'bg-secondary text-muted-foreground' },
företag: { labelKey: 'category_foretag', icon: Building2, color: 'bg-secondary text-muted-foreground' },
bank: { labelKey: 'category_bank', icon: Landmark, color: 'bg-secondary text-muted-foreground' },
företag: { labelKey: 'category_foretag', icon: Briefcase, color: 'bg-secondary text-muted-foreground' },
}
function TermCard({ term, isExpanded, onToggle }: { term: GlossaryTerm; isExpanded: boolean; onToggle: () => void }) {
+3 -3
View File
@@ -13,7 +13,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
import { useToast } from '@/components/ui/use-toast'
import { Plus, Building2, Lock } from 'lucide-react'
import { Plus, Lock, Truck } from 'lucide-react'
import Link from 'next/link'
import { cn } from '@/lib/utils'
import { useCompany } from '@/contexts/CompanyContext'
@@ -194,13 +194,13 @@ export default function SuppliersPage() {
) : filteredSuppliers.length === 0 ? (
searchTerm ? (
<EmptyState
icon={Building2}
icon={Truck}
title={t('no_search_results_title')}
description={<span data-ph-mask="">{t('no_search_results_description', { term: searchTerm })}</span>}
/>
) : (
<EmptyState
icon={Building2}
icon={Truck}
title={t('empty_title')}
description={t('empty_description')}
actionLabel={canWrite ? t('new_supplier') : undefined}
+5 -5
View File
@@ -6,7 +6,7 @@ import { useTranslations } from 'next-intl'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Loader2, Building2, AlertCircle } from 'lucide-react'
import { Loader2, AlertCircle, Briefcase } from 'lucide-react'
import { createClient } from '@/lib/supabase/client'
import { useToast } from '@/components/ui/use-toast'
import { getBranding } from '@/lib/branding/service'
@@ -270,7 +270,7 @@ export default function InvitePage() {
<Card className="p-6">
<div className="flex items-start gap-4">
<div className="p-2.5 rounded-lg bg-muted/50">
<Building2 className="h-5 w-5 text-muted-foreground" />
<Briefcase className="h-5 w-5 text-muted-foreground" />
</div>
<div>
<p className="font-medium">{invite.companyName}</p>
@@ -309,7 +309,7 @@ export default function InvitePage() {
<Card className="p-6">
<div className="flex items-start gap-4">
<div className="p-2.5 rounded-lg bg-muted/50">
<Building2 className="h-5 w-5 text-muted-foreground" />
<Briefcase className="h-5 w-5 text-muted-foreground" />
</div>
<div>
<p className="font-medium">{invite.companyName}</p>
@@ -337,7 +337,7 @@ export default function InvitePage() {
<Card className="p-6">
<div className="flex items-start gap-4">
<div className="p-2.5 rounded-lg bg-muted/50">
<Building2 className="h-5 w-5 text-muted-foreground" />
<Briefcase className="h-5 w-5 text-muted-foreground" />
</div>
<div>
<p className="font-medium">{invite.companyName}</p>
@@ -365,7 +365,7 @@ export default function InvitePage() {
<Card className="p-6">
<div className="flex items-start gap-4">
<div className="p-2.5 rounded-lg bg-muted/50">
<Building2 className="h-5 w-5 text-muted-foreground" />
<Briefcase className="h-5 w-5 text-muted-foreground" />
</div>
<div>
<p className="font-medium">{invite.companyName}</p>
@@ -12,7 +12,7 @@ import {
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { BookOpen, Search, Building2, Users, Globe } from 'lucide-react'
import { BookOpen, Search, Users, Globe, Briefcase } from 'lucide-react'
import { TEMPLATE_CATEGORY_LABELS, SCOPE_LABELS, getTemplateScope, applyTemplate } from '@/lib/bookkeeping/template-library'
import type { BookingTemplateCategory, EntityType } from '@/types'
import type { FormLine } from '@/components/bookkeeping/JournalEntryForm'
@@ -28,7 +28,7 @@ interface Props {
const SCOPE_ICONS = {
system: Globe,
team: Users,
company: Building2,
company: Briefcase,
} as const
export default function BookingTemplatePicker({ onApply, entityType, defaultAmount }: Props) {
+2 -2
View File
@@ -8,7 +8,6 @@ import {
ArrowLeftRight,
Users,
Wallet,
Building2,
BookOpen,
ListTree,
BarChart3,
@@ -24,6 +23,7 @@ import {
ArrowRight,
Scale,
type LucideIcon,
Truck,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { useCompany } from '@/contexts/CompanyContext'
@@ -49,7 +49,7 @@ const ACTION_ENTRIES: Entry[] = [
const PAGE_ENTRIES: Entry[] = [
{ id: 'kunder', label: 'Kunder', icon: Users, href: '/customers' },
{ id: 'leverantörer', label: 'Leverantörer', icon: Building2, href: '/suppliers' },
{ id: 'leverantörer', label: 'Leverantörer', icon: Truck, href: '/suppliers' },
{ id: 'kundfakturor', label: 'Kundfakturor', icon: ReceiptText, href: '/invoices', keywords: 'fakturor fakturering invoices kundfaktura' },
{ id: 'leverantörsfakturor', label: 'Leverantörsfakturor', icon: Wallet, href: '/supplier-invoices' },
{ id: 'bokföring', label: 'Bokföring', icon: BookOpen, href: '/bookkeeping', keywords: 'verifikat journal ledger' },
+2 -2
View File
@@ -22,7 +22,6 @@ import {
Menu,
X,
HelpCircle,
Building2,
Wallet,
TrendingUp,
ClipboardCheck,
@@ -51,6 +50,7 @@ import {
ShoppingCart,
Car,
ClipboardList,
Truck,
} from 'lucide-react'
import { getBranding } from '@/lib/branding/service'
import { BrandHomeLink } from '@/components/branding/BrandHomeLink'
@@ -256,7 +256,7 @@ const navItems: NavItem[] = [
// row. Anställda is a register (you edit an employee rarely, you run
// payroll monthly), so it lives here while Löner stays in Arbeta.
{ href: '/customers', labelKey: 'customers', icon: Users, group: 'data', fold: 'register' },
{ href: '/suppliers', labelKey: 'suppliers', icon: Building2, group: 'data', fold: 'register' },
{ href: '/suppliers', labelKey: 'suppliers', icon: Truck, group: 'data', fold: 'register' },
{ href: '/articles', labelKey: 'articles', icon: Tag, group: 'data', fold: 'register' },
{ href: '/salary/employees', labelKey: 'employees', icon: Users, group: 'data', fold: 'register', employerOnly: true },
{ href: '/assets', labelKey: 'assets', icon: Package, group: 'data', fold: 'register' },
+2 -2
View File
@@ -2,7 +2,7 @@
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import { ArrowUpRight, Building2, Loader2 } from 'lucide-react'
import { ArrowUpRight, Loader2, Briefcase } from 'lucide-react'
import { performCompanySwitch } from '@/lib/company/switch-client'
import { useToast } from '@/components/ui/use-toast'
@@ -39,7 +39,7 @@ export default function HomeDomainSignpost({
<div className="stagger-enter mx-auto max-w-lg pt-12">
<div className="flex flex-col items-center text-center">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl bg-muted/60">
<Building2 className="h-5 w-5 text-muted-foreground" />
<Briefcase className="h-5 w-5 text-muted-foreground" />
</div>
<h1 className="font-display text-2xl leading-8 tracking-tight">
{t('title')}
@@ -17,7 +17,6 @@ import {
import { useExtensionData } from '@/lib/extensions/use-extension-data'
import { useToast } from '@/components/ui/use-toast'
import {
Building2,
CheckCircle,
XCircle,
MapPin,
@@ -29,6 +28,7 @@ import {
ShieldCheck,
Users,
Receipt,
Briefcase,
} from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import Link from 'next/link'
@@ -330,7 +330,7 @@ export default function TicWorkspace({ userId }: WorkspaceComponentProps) {
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<Building2 className="h-4 w-4" />
<Briefcase className="h-4 w-4" />
{/* data-ph-mask: the looked-up company name is user data */}
<span data-ph-mask="">{profile.companyName}</span>
</CardTitle>
+2 -2
View File
@@ -18,8 +18,8 @@ import {
FileText,
AlertCircle,
CheckCircle,
Building2,
HelpCircle,
Landmark,
} from 'lucide-react'
import type { BankFileFormatId } from '@/lib/import/bank-file/types'
@@ -187,7 +187,7 @@ export default function BankFileUploadStep({
{(selectedFile.size / 1024).toFixed(1)} KB
</p>
<Badge variant="secondary" className="mt-2">
<Building2 className="mr-1 h-3 w-3" />
<Landmark className="mr-1 h-3 w-3" />
{detectedFormat === 'wise_statement'
? t('bank_format_wise_statement')
: detectedFormatName || FORMAT_NAMES[detectedFormat] || detectedFormat}
+2 -2
View File
@@ -7,7 +7,6 @@ import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { formatCurrency } from '@/lib/utils'
import {
Building2,
Calendar,
FileText,
CheckCircle,
@@ -16,6 +15,7 @@ import {
ArrowRight,
BarChart3,
Info,
Briefcase,
} from 'lucide-react'
import type { ImportPreview, ParseIssue } from '@/lib/import/types'
@@ -69,7 +69,7 @@ export default function SIEPreviewStep({
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Building2 className="h-5 w-5" />
<Briefcase className="h-5 w-5" />
Företagsinformation
</CardTitle>
<CardDescription>Information från SIE-filen</CardDescription>
+2 -2
View File
@@ -1,6 +1,6 @@
'use client'
import { Building2 } from 'lucide-react'
import { Briefcase } from 'lucide-react'
import { useTranslations } from 'next-intl'
import { cn } from '@/lib/utils'
import { useCompany } from '@/contexts/CompanyContext'
@@ -33,7 +33,7 @@ export function ActiveCompanyBadge({ className }: { className?: string }) {
)}
title={`${t('active_company')}: ${company.name}`}
>
<Building2 className="h-3 w-3 shrink-0" aria-hidden="true" />
<Briefcase className="h-3 w-3 shrink-0" aria-hidden="true" />
<span className="sr-only">{t('active_company')}: </span>
<span className="truncate">{company.name}</span>
</Badge>
+2 -2
View File
@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl'
import { Input } from '@/components/ui/input'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Search, ChevronDown, ChevronUp, AlertTriangle, Info, Building2, PenLine } from 'lucide-react'
import { Search, ChevronDown, ChevronUp, AlertTriangle, Info, PenLine, Briefcase } from 'lucide-react'
import {
getCommonTemplates,
getAdvancedTemplates,
@@ -519,7 +519,7 @@ export default function TemplatePicker({
{sortedLibraryRaw.length > 0 && (
<div>
<p className="text-xs font-medium text-muted-foreground mb-2 flex items-center gap-1.5">
<Building2 className="h-3 w-3" />
<Briefcase className="h-3 w-3" />
{t('my_templates')}
</p>
<div className="space-y-1.5">
+2 -2
View File
@@ -10,7 +10,7 @@
"definition": {
"name": "Bolagsuppgifter",
"category": "import",
"icon": "Building2",
"icon": "Briefcase",
"dataPattern": "manual",
"hasOwnData": true,
"description": "Hämta företagsinformation automatiskt vid registrering",
@@ -18,7 +18,7 @@
"quickAction": {
"label": "Företagsprofil",
"description": "Visa offentliga uppgifter",
"icon": "Building2",
"icon": "Briefcase",
"href": "/e/general/tic",
"order": 10
}
+2 -2
View File
@@ -60,7 +60,7 @@ export const EXTENSION_DEFINITIONS: Record<string, ExtensionDefinition[]> = {
"name": "Bolagsuppgifter",
"sector": "general",
"category": "import",
"icon": "Building2",
"icon": "Briefcase",
"dataPattern": "manual",
"description": "Hämta företagsinformation automatiskt vid registrering",
"longDescription": "Fyll i företagsuppgifter automatiskt genom att ange organisationsnummer. Hämtar adress, momsregistrering, F-skattestatus och bankuppgifter från offentliga register via TIC.",
@@ -68,7 +68,7 @@ export const EXTENSION_DEFINITIONS: Record<string, ExtensionDefinition[]> = {
"quickAction": {
"label": "Företagsprofil",
"description": "Visa offentliga uppgifter",
"icon": "Building2",
"icon": "Briefcase",
"href": "/e/general/tic",
"order": 10
}
+4 -2
View File
@@ -28,7 +28,8 @@ import {
Ship,
FileText,
Shield,
Building2,
Briefcase,
Truck,
ArrowRightLeft,
Mail,
MessageCircle,
@@ -65,7 +66,8 @@ const ICON_MAP: Record<string, LucideIcon> = {
Ship,
FileText,
Shield,
Building2,
Briefcase,
Truck,
ArrowRightLeft,
Mail,
MessageCircle,