style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)

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>
This commit is contained in:
Jakob Wennberg
2026-07-04 15:58:06 +02:00
committed by GitHub
co-authored by Claude Sonnet 5
parent a5154ee884
commit ec27228a8e
1588 changed files with 28388 additions and 10890 deletions
+13 -13
View File
@@ -7,7 +7,7 @@
* pitfalls, scope, idempotent/reversible/dryRun flags, and a worked example.
*
* To make this work, every v1 route file needs to import-side-effect call
* registerEndpoint() — which they all do at module load time. The doc
* registerEndpoint(): which they all do at module load time. The doc
* builder triggers that load via lib/api/v1/load-routes.ts.
*
* Adding a new endpoint means editing the route file's registerEndpoint
@@ -34,21 +34,21 @@ interface ResourceGroup {
const RESOURCES: ResourceGroup[] = [
{ slug: 'companies', label: 'Companies', description: 'List and read companies the API key can access.', matcher: (p) => /\/companies(?:\/:companyId)?$/.test(p) },
{ slug: 'customers', label: 'Customers', description: 'CRM-side: who you invoice. Business and individual (sole-trader) customers with VIES validation.', matcher: (p) => /\/customers(\/|$)/.test(p) },
{ slug: 'invoices', label: 'Invoices', description: 'Outbound invoicing — draft, send, mark paid, credit, PDF download. Mixed-rate VAT supported.', matcher: (p) => /\/invoices(\/|$)/.test(p) },
{ slug: 'invoices', label: 'Invoices', description: 'Outbound invoicing: draft, send, mark paid, credit, PDF download. Mixed-rate VAT supported.', matcher: (p) => /\/invoices(\/|$)/.test(p) },
{ slug: 'suppliers', label: 'Suppliers', description: 'AP-side counterparties. Mirrors customers on the supplier vertical.', matcher: (p) => /\/suppliers(\/|$)/.test(p) },
{ slug: 'supplier-invoices', label: 'Supplier invoices', description: 'AP lifecycle: register, approve, mark paid, credit. With ROT/RUT and reverse-charge support.', matcher: (p) => /\/supplier-invoices(\/|$)/.test(p) },
{ slug: 'transactions', label: 'Transactions', description: 'Bank transactions — ingest, categorise, match to invoices, reconcile.', matcher: (p) => /\/transactions(\/|$)/.test(p) },
{ slug: 'transactions', label: 'Transactions', description: 'Bank transactions: ingest, categorise, match to invoices, reconcile.', matcher: (p) => /\/transactions(\/|$)/.test(p) },
{ slug: 'reconciliation', label: 'Reconciliation', description: 'Run bank-to-ledger reconciliation and read the current matching status.', matcher: (p) => /\/reconciliation(\/|$)/.test(p) },
{ slug: 'journal-entries', label: 'Journal entries', description: 'The bookkeeping engine surface — verifikation lifecycle (draft, commit, reverse, correct).', matcher: (p) => /\/journal-entries(\/|$)/.test(p) },
{ slug: 'journal-entries', label: 'Journal entries', description: 'The bookkeeping engine surface: verifikation lifecycle (draft, commit, reverse, correct).', matcher: (p) => /\/journal-entries(\/|$)/.test(p) },
{ slug: 'voucher-gap-explanations', label: 'Voucher gap explanations', description: 'Documented explanations for gaps in the voucher series, per BFNAR 2013:2.', matcher: (p) => /\/voucher-gap/.test(p) },
{ slug: 'fiscal-periods', label: 'Fiscal periods', description: 'Period lifecycle — lock, close, year-end, opening balances, FX revaluation. Async via the operations substrate.', matcher: (p) => /\/fiscal-periods(\/|$)/.test(p) },
{ slug: 'fiscal-periods', label: 'Fiscal periods', description: 'Period lifecycle: lock, close, year-end, opening balances, FX revaluation. Async via the operations substrate.', matcher: (p) => /\/fiscal-periods(\/|$)/.test(p) },
{ slug: 'accounts', label: 'Accounts', description: 'Read the chart of accounts (BAS).', matcher: (p) => /\/accounts(\/|$)/.test(p) },
{ slug: 'documents', label: 'Documents', description: 'Multipart upload, signed-URL download (15-min TTL), link to journal entries.', matcher: (p) => /\/documents(\/|$)/.test(p) },
{ slug: 'employees', label: 'Employees', description: 'Payroll roster — CRUD with personnummer masking on list endpoints.', matcher: (p) => /\/employees(\/|$)/.test(p) },
{ slug: 'salary-runs', label: 'Salary runs', description: 'Payroll lifecycle — create, calculate, approve, mark paid, book, generate AGI XML.', matcher: (p) => /\/salary-runs(\/|$)/.test(p) },
{ slug: 'reports', label: 'Reports', description: 'Read-only reports — trial balance, P&L, balance sheet, GL, VAT, salary journal, SIE export, +9 more.', matcher: (p) => /\/reports(\/|$)/.test(p) },
{ slug: 'imports', label: 'Imports', description: 'Bulk async ingest — SIE files (Fortnox/Visma/BL/SpeedLedger/Bokio migrations) and bank statements (11 formats).', matcher: (p) => /\/imports(\/|$)/.test(p) },
{ slug: 'compliance', label: 'Compliance check', description: 'Pre-flight verification — voucher gaps, year-end readiness, before submitting to Skatteverket.', matcher: (p) => /\/compliance(\/|$)/.test(p) },
{ slug: 'employees', label: 'Employees', description: 'Payroll roster: CRUD with personnummer masking on list endpoints.', matcher: (p) => /\/employees(\/|$)/.test(p) },
{ slug: 'salary-runs', label: 'Salary runs', description: 'Payroll lifecycle: create, calculate, approve, mark paid, book, generate AGI XML.', matcher: (p) => /\/salary-runs(\/|$)/.test(p) },
{ slug: 'reports', label: 'Reports', description: 'Read-only reports: trial balance, P&L, balance sheet, GL, VAT, salary journal, SIE export, +9 more.', matcher: (p) => /\/reports(\/|$)/.test(p) },
{ slug: 'imports', label: 'Imports', description: 'Bulk async ingest: SIE files (Fortnox/Visma/BL/SpeedLedger/Bokio migrations) and bank statements (11 formats).', matcher: (p) => /\/imports(\/|$)/.test(p) },
{ slug: 'compliance', label: 'Compliance check', description: 'Pre-flight verification: voucher gaps, year-end readiness, before submitting to Skatteverket.', matcher: (p) => /\/compliance(\/|$)/.test(p) },
{ slug: 'webhooks', label: 'Webhooks', description: 'Subscribe to events with HMAC-signed delivery, exponential retries, and dead-letter replay.', matcher: (p) => /\/webhooks|\/webhook-deliveries/.test(p) },
{ slug: 'operations', label: 'Operations', description: 'Poll long-running async operations (year-end closing, imports, currency revaluation).', matcher: (p) => /\/operations(\/|$)/.test(p) },
]
@@ -122,7 +122,7 @@ function renderEndpoint(ep: EndpointDefinition): string {
// Module-level memoisation. The endpoint registry is populated once at
// module load (via the side-effect import of load-routes) and is then
// immutable for the process lifetime. The Markdown serialisation is
// pure derivation — reusing a single result avoids repeated work on the
// pure derivation: reusing a single result avoids repeated work on the
// .md route handlers (which Next.js doesn't statically pre-render) AND
// halves the cost on each generateMetadata + page render pair on the
// HTML routes. (Greptile P2, round 1.)
@@ -159,7 +159,7 @@ export function buildResourcePages(): BuiltResourcePage[] {
lines.push('## Endpoints')
lines.push('')
for (const ep of endpoints) {
lines.push(`- [\`${ep.method}\` \`${ep.path}\`](#${endpointAnchor(ep)}) — ${ep.summary}`)
lines.push(`- [\`${ep.method}\` \`${ep.path}\`](#${endpointAnchor(ep)}): ${ep.summary}`)
}
lines.push('')
lines.push('---')
@@ -188,7 +188,7 @@ export function buildReferenceOverviewMd(): string {
const lines: string[] = []
lines.push('# API reference')
lines.push('')
lines.push(`> Every endpoint exposed by the Accounted REST API, grouped by resource. Auto-generated from the same Zod registry that powers the [OpenAPI 3.1 spec](/api/v1/openapi.json), the MCP tool surface, and runtime validators — there is no separate doc-source to keep in sync.`)
lines.push(`> Every endpoint exposed by the Accounted REST API, grouped by resource. Auto-generated from the same Zod registry that powers the [OpenAPI 3.1 spec](/api/v1/openapi.json), the MCP tool surface, and runtime validators: there is no separate doc-source to keep in sync.`)
lines.push('')
lines.push('## Resources')
lines.push('')