ec27228a8e
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>
3.0 KiB
3.0 KiB
Event Handlers Reference
Extensions subscribe via eventHandlers. The bus dispatches with Promise.allSettled(): failures are isolated.
Signature & Registration
async function handleSomeEvent(
payload: EventPayload<'transaction.synced'>, ctx?: ExtensionContext
): Promise<void> { /* ... */ }
export const myExtension: Extension = {
eventHandlers: [
{ eventType: 'transaction.synced', handler: handleSomeEvent },
],
}
ctx can be undefined in cron job contexts: always provide fallbacks.
All Event Types
Source: lib/events/types.ts. Every payload includes userId.
| Event | Payload |
|---|---|
journal_entry.drafted |
{ entry: JournalEntry, userId } |
journal_entry.committed |
{ entry: JournalEntry, userId } |
journal_entry.corrected |
{ original, storno, corrected: JournalEntry, userId } |
document.uploaded |
{ document: DocumentAttachment, userId } |
invoice.created |
{ invoice: Invoice, userId } |
invoice.sent |
{ invoice: Invoice, userId } |
credit_note.created |
{ creditNote: CreditNote, userId } |
transaction.synced |
{ transactions: Transaction[], userId } |
transaction.categorized |
{ transaction, account, taxCode, userId } |
transaction.reconciled |
{ transaction, journalEntryId, method, userId } |
period.locked |
{ period: FiscalPeriod, userId } |
period.year_closed |
{ period: FiscalPeriod, userId } |
customer.created |
{ customer: Customer, userId } |
receipt.extracted |
{ receipt, documentId, confidence, userId } |
receipt.matched |
{ receipt, transaction, confidence, autoMatched, userId } |
receipt.confirmed |
{ receipt, businessTotal, privateTotal, userId } |
supplier_invoice.received |
{ inboxItem: InvoiceInboxItem, userId } |
supplier_invoice.extracted |
{ inboxItem, confidence, userId } |
supplier_invoice.confirmed |
{ inboxItem, supplierInvoice, userId } |
Standard Handler Pattern
async function handleTransactionSynced(
payload: EventPayload<'transaction.synced'>, ctx?: ExtensionContext
): Promise<void> {
const { transactions, userId } = payload
const log = ctx?.log ?? console
const settings = ctx
? { ...DEFAULT_SETTINGS, ...(await ctx.settings.get<Partial<MySettings>>() || {}) }
: await getSettings(userId)
if (!settings.featureEnabled) return // Settings gate
try {
const supabase = ctx?.supabase ?? await (await import('@/lib/supabase/server')).createClient()
// Business logic...
await ctx?.emit({ type: 'receipt.matched', payload: { /* ... */ userId } })
} catch (error) { log.error('Handler failed:', error) }
}
Key Rules
- Handle
ctx = undefined: fallback for supabase, logging, settings - Handlers run concurrently: order undefined, don't depend on other handlers
- Use settings gates: let users control features
- Wrap in try/catch: don't throw unhandled errors
- Emit events for cascading pipelines: e.g., OCR emits
receipt.extracted→ triggers push notifications