* feat(refund): add refund request flow
- tui modal composer with reason select + free-form message
(pkg/tui/refund.go)
- POST /api/v1/orders/{id}/refund-request posts to orders slack thread
- 15 min server side cooldown via Order.LastRefundRequestAt (429 +
retry-after)
- shared refundrequestreasons + can requestrefund hoisted to pkg/models
- slack message includes customer name, email, order total, quoted body
- tests over invalid-state, missing-message, and cooldown paths
* style(tui): run gofmt to fix formatting
* refactor(tui): port to charm v2
bumped bubbletea, lipgloss, bubbles, wish, and huh to v2. Mostly
mechanical chur, new import paths, vewport getter/setter api, view()
returns tea.view now, huh.theme became an interface so the theme package
needed a small reshape. Render plumbing is gone (no more lipgloss.Render
field on the Model or MakeRenderer in main.go). All pages still work
over ssh. setting up for the refund composer next
* style(tui): gofmt main.go and menu.go
Pulls events off the new order_events table and shows them as a timeline
block in the order detail view: Paid, Label created, In transit,
Delivered (etc) with timestamps. Filters out ops-only events like
payment_critical, label_orphaned, and tracking_marked_manually so the
customer never sees internal plumbing.
DisplayLabel on OrderEvent decides what's customer-visible and what's
redacted, so the same order_events rows can later power an admin view
with no filter.
the big colored status on the order detail page used to just say
"Shipped" the moment i bought the label, which didnt really feel right
since the carrier hadnt actually picked anything up yet. now it walks
through "Label created" -> "In transit" -> "Delivered" as real scans
come in, and keeps the "Delivery Issue" / "Returned to sender" labels
for unhappy paths
also wired up the existing tracking_status_details column to the tui. i
skipped it before but the response actually includes a description of
the delivery, that is now subtext under the colored statu.
and the shippo webhook now returns a small json body instead of an empty
200. this is just for the operator that runs the curls commands to
update the shipment, which makes debugging a lot less guesswork
realized per-shipment table is kinda bad, switched to single embedded tracking blob. since im only
shipping from my house no split is needed. adds tracking_status, tracking_status_details,
tracking_status_updated_at columns. reverse-backfills any historical shipments rows back onto orders
(idempotent, table-guarded). UpdateTracking rewritten to write to otder columns direcly
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>
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>