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
+8 -8
View File
@@ -10,7 +10,7 @@ import { guardSandbox } from '@/lib/sandbox/guard'
import { requireCapability } from '@/lib/entitlements/has-capability'
import { CAPABILITY } from '@/lib/entitlements/keys'
// Make sure extensions are loaded — the chat loop dispatches against the
// Make sure extensions are loaded: the chat loop dispatches against the
// agent tool registry which is populated by the mcp-server extension at load.
ensureInitialized()
@@ -62,7 +62,7 @@ const BodySchema = z.object({
// POST /api/agent/invoke
//
// Streams NDJSON events from the chat loop. Each line is a JSON object whose
// `kind` identifies the event type — see lib/agent/chat/run-turn.ts StreamEvent.
// `kind` identifies the event type: see lib/agent/chat/run-turn.ts StreamEvent.
//
// Auth: the user must be a member of the resolved company.
//
@@ -72,7 +72,7 @@ export async function POST(request: Request) {
const { data: { user } } = await supabase.auth.getUser()
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
// Generous per-user rate limit — bounds runaway Bedrock spend (loop-firing
// Generous per-user rate limit: bounds runaway Bedrock spend (loop-firing
// sessions). Fails open on infra error.
const rate = await checkAgentRateLimit(supabase, user.id)
if (!rate.ok) {
@@ -108,7 +108,7 @@ export async function POST(request: Request) {
.maybeSingle()
if (!membership) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
// No Anthropic Bedrock calls in the sandbox — the demo runs entirely on
// No Anthropic Bedrock calls in the sandbox: the demo runs entirely on
// seed data and the assistant is gated to a "look, don't touch" preview.
const blocked = await guardSandbox(supabase, companyId)
if (blocked) return blocked
@@ -116,12 +116,12 @@ export async function POST(request: Request) {
const capBlocked = await requireCapability(supabase, companyId, CAPABILITY.ai)
if (capBlocked) return capBlocked
// onboarding.intake completion signal — once the user has actually
// onboarding.intake completion signal: once the user has actually
// engaged (typed a real reply, not the auto-fired greeting prompt that
// mounts the chat), stamp intake_completed_at on the profile so re-entry
// logic and opportunistic follow-up logic in other intents can tell the
// intake happened. Idempotent: the IS NULL guard ensures we never
// overwrite the first engagement timestamp. Best-effort — failure here
// overwrite the first engagement timestamp. Best-effort: failure here
// doesn't break the chat; the next user turn retries.
if (
body.intent_id === 'onboarding.intake' &&
@@ -136,7 +136,7 @@ export async function POST(request: Request) {
.eq('company_id', companyId)
.is('intake_completed_at', null)
} catch {
// ignored — see comment above
// ignored: see comment above
}
}
@@ -210,7 +210,7 @@ export async function POST(request: Request) {
}
}
// Stream — NDJSON events from the chat loop.
// Stream: NDJSON events from the chat loop.
const encoder = new TextEncoder()
// Conversation id is set above; capture into a non-null local for the
// streaming closure's first emission.