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
+2 -2
View File
@@ -16,7 +16,7 @@ const STORAGE_KEY_PREFIX = 'Accounted:cash-account:'
interface Props {
/**
* Current selection — a BAS ledger account number ('1930', '1932', …).
* Current selection: a BAS ledger account number ('1930', '1932', …).
* `null` would only be meaningful if "all accounts" were an option, which
* isn't currently supported (reconciliation is always single-account).
*/
@@ -104,7 +104,7 @@ export function CashAccountSelector({
return () => {
cancelled = true
}
// onReady excluded — lifecycle callback, shouldn't retrigger on parent renders.
// onReady excluded: lifecycle callback, shouldn't retrigger on parent renders.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [company?.id])
+1 -1
View File
@@ -12,7 +12,7 @@ interface DataStateProps {
error: string | null
/** When true (and not loading/error), render `empty` instead of `children`. */
isEmpty?: boolean
/** Retry handler — typically `useFetch().refetch`. Shows a retry button. */
/** Retry handler: typically `useFetch().refetch`. Shows a retry button. */
onRetry?: () => void
/** Loading placeholder. Defaults to three skeleton rows. */
skeleton?: React.ReactNode
+3 -3
View File
@@ -22,7 +22,7 @@ export const ALL_YEARS_VALUE = '__all__'
interface Props {
/**
* Current selection. `null` means "all years" — no filter applied.
* Current selection. `null` means "all years": no filter applied.
*/
value: string | null
/**
@@ -103,7 +103,7 @@ export function FiscalYearSelector({
const today = new Date().toISOString().split('T')[0]
fetched = fetched.filter((p) => p.period_start <= today)
}
// Newest first — most migrations list recent years at the top
// Newest first: most migrations list recent years at the top
fetched.sort((a, b) => b.period_start.localeCompare(a.period_start))
setPeriods(fetched)
setLoaded(true)
@@ -176,7 +176,7 @@ export function FiscalYearSelector({
)}
{periods.map((p) => (
<SelectItem key={p.id} value={p.id}>
{p.name} ({p.period_start} — {p.period_end})
{p.name} ({p.period_start} till {p.period_end})
{p.locked_at ? t('suffix_locked') : p.is_closed ? t('suffix_closed') : ''}
</SelectItem>
))}
+5 -5
View File
@@ -17,7 +17,7 @@ export type DateRangeValue = {
type Preset = 'full_year' | 'ytd' | 'this_month' | 'last_month' | 'this_quarter' | 'custom'
interface Props {
/** Selected fiscal period — bounds the range. */
/** Selected fiscal period: bounds the range. */
periodStart: string
periodEnd: string
value: DateRangeValue
@@ -30,7 +30,7 @@ const STORAGE_KEY_PREFIX = 'Accounted:report-range-preset:'
const PRESETS: Preset[] = ['full_year', 'ytd', 'this_month', 'last_month', 'this_quarter', 'custom']
function todayIso(): string {
// Local calendar date — using toISOString() returns UTC, which falls a day
// Local calendar date: using toISOString() returns UTC, which falls a day
// behind for Swedish users between midnight and 01:00/02:00 local time and
// would silently truncate "today" from YTD / this-month / this-quarter.
const d = new Date()
@@ -49,7 +49,7 @@ function clampToPeriod(date: string, periodStart: string, periodEnd: string): st
/**
* Resolve a preset to a concrete range inside the fiscal period.
*
* Endpoints are always clamped to the period — e.g. "this month" outside the
* Endpoints are always clamped to the period: e.g. "this month" outside the
* period collapses to a zero-width range at whichever boundary you're nearest.
* `full_year` returns `{}` so the API call omits the params entirely and the
* report falls back to its full-period default (preserves cache parity with
@@ -113,7 +113,7 @@ function resolvePreset(
*
* Default = "Hittills i år" (YTD) which matches Fortnox/Visma. A "Hela året"
* preset clears the range entirely so the API falls back to full-period
* behaviour. Custom range is clamped to the fiscal period — cross-year
* behaviour. Custom range is clamped to the fiscal period: cross-year
* ranges are out of scope.
*/
export function ReportDateRange({
@@ -128,7 +128,7 @@ export function ReportDateRange({
const [preset, setPreset] = useState<Preset>('ytd')
// Restore last-used preset per company, then resolve it against the
// current fiscal period. The period selector lives upstream — when it
// current fiscal period. The period selector lives upstream: when it
// changes, we re-resolve so the dates always sit inside the visible year.
useEffect(() => {
if (!company?.id || typeof window === 'undefined') return