Add self-hosted Umami analytics to replace the third-party cloud.umami.is
tracker. Creates @harb/analytics package with typed event helpers and
instruments the conversion funnel: CTA clicks (landing), wallet connect,
swap initiated, and stake created (web-app).
- Add Umami Docker service sharing existing postgres (separate DB)
- Add Caddy /analytics route to proxy Umami dashboard
- Configure via VITE_UMAMI_URL and VITE_UMAMI_WEBSITE_ID env vars
- Document setup and funnel events in docs/ENVIRONMENT.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The direct cast of Error to Record<string, unknown> is rejected by
TypeScript because the types don't sufficiently overlap. Cast via
unknown first to satisfy the compiler.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
viem's BaseError extends Error, so the instanceof Error branch was
firing and returning error.message before the isRecord branch could
check shortMessage. Check shortMessage first inside instanceof Error
so terse viem messages are shown to users instead of verbose full ones.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>