Document and respect per-IP rate limits (200/min global, 20/min auth,
60/min webhooks) by holding scenarios well below the ceilings so the
histogram captures real handler latency instead of 429s. Total combined
~148 req/min over ~10h. Adds summaryTrendStats so reports include
p95/p99/max alongside avg.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the manual string-concat + separator layout with a
lipgloss/table row, mirroring the reference TUI structure. Drops the
parallel bold-vs-gray styling for a single-weight accent/base scheme,
extracts a small tab() closure to remove the duplicated active/inactive
branches, and uses m.renderer.NewStyle() for the border so per-session
color profiles render correctly under SSH.
Cart label still tracks inCartFlow() (covers shipping/payment/review/
confirm), which the reference doesn't need since it has no multi-step
checkout.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add loadtest/run.pid and loadtest/run.err to .gitignore so k6 runtime
state stops tagging along on git add. Also ignore the orphan /00 file
(stray redirect of pkg/observability/tracing.go) and ROADMAP.md (AI-
synthesised audit doc, not part of the project canon).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Package-level slog.With() vars are evaluated at init time, before
observability.InitLogger() runs in main. They captured the original default
slog handler and bypassed the countingHandler that increments
log_messages_total — so warn/error calls from these packages weren't being
counted.
Convert to functions so each call resolves slog.Default() at use time and
inherits the active counting handler.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Convert log.Printf calls in webhook, reconcile, addresses, and auth handlers
to slog with a consistent `component` attribute. Severity now reflects
operational urgency:
- warn: signature failures, declined cards, unknown fingerprints, vendor
validation rejections, reconciler patching a missed charge
- error: DB write failures, malformed Stripe payloads, token generation
failures
- info: successful order/card events, normal reconciler ticks
Lets Loki/Prometheus filter by component (e.g. {component="webhook"}) and
gives the log-level dashboard panel real warn/error series instead of a
flat info line.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Audit events now emit at Info/Warn/Error so the log-level counter has variety
instead of being pinned to info. Panic recovery uses structured slog.Error
with method/path/stack attrs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Prometheus instrumentation across the checkout funnel, auth flow,
Stripe calls, rate limiters, and HTTP middleware (inflight gauge, request
and response sizes). Introduces a slog handler that counts log lines by
level so the dashboard can show error/warn rates; existing log.Printf
calls are routed through slog and counted as info.
Backs the new "terminalshop — Service Overview" Grafana dashboard.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Default prometheus.DefBuckets bottoms out at 5ms, which made every
route's p95 collapse to 4.75ms (histogram_quantile interpolation
within the smallest bucket). Custom buckets from 100µs to 5s now
distinguish microsecond-fast routes (health) from slower ones
(stripe-backed cart endpoints).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Counter (http_requests_total) and Histogram
(http_request_duration_seconds) labeled by method, chi route pattern,
and status. Wired through a new Metrics middleware (after Recovery so
panics-turned-500s are still counted) and an unauthenticated /metrics
endpoint at the root for Prometheus to scrape.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace ErrorMsg string with typed VisibleError struct. Add catch-all
error handler at top of Update() that auto-recovers cart on shop/cart
errors. ESC now dismisses error banners. Refactor shipping address and
payment card checkout lists to use CreateBox + formatListItem for
consistent bordered cards with radio indicators.
Written with Claude Code (claude.ai/code)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
replacing duplicated border/selection logic in
account_orders.go and cart.go. Also fixes a latent SSH rendering bug
in cart.go where lipgloss.NewStyle() was used instead of theme.Base().
lipgloss.NewStyle() uses the default renderer which can't output colors
over SSH. Switching to m.theme.Base() uses the session renderer, fixing
BorderForeground. Also reverted to NormalBorder since color now works.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BorderForeground color wasn't rendering differently over SSH. Use
DoubleBorder (╔═╗║╚═╝) for selected vs NormalBorder (┌─┐│└─┘) for
unselected — visually distinct regardless of color support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use explicit #4682B4 with bold to ensure the selected border is
visually distinct on all terminal themes including gruvbox.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scroll triggers when the next item would be off-screen, not when the
current item hits the edge. Selected border now uses highlight color
(steel blue) instead of accent (white) for visibility on all themes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Simplify order list cards to 2 content lines (matching reference) so
itemHeight is always 4. Remove extra margin/buffer from viewport height
calc since account is a viewport page with no margins.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The scroll-to-item calculations used wrong item heights (7/5 instead of
9/7) and ignored the 5-line title header offset, causing the viewport to
under-scroll. Selected order cards now light up in accent color for
better visibility across terminal themes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auth handler used FirstOrCreate which couldn't see soft-deleted keys
(GORM filters by deleted_at IS NULL) but also couldn't insert (unique
index still covers deleted rows), returning 500 on every login after
a key deletion. Use Unscoped lookup and restore soft-deleted keys.
Splash screen also got permanently stuck when auth failed because
splashViewInitCmd was never fired, leaving splash.dataReady false.