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.
when encountering an error, the error would never be removed, i just reset the error msg in the switchPage function, this will have to be redone later to make error messages dissapear after a while even on the same page
replace SSHPublicKeyStr field on Model with a closure-based authCmd that
fires, the public key lives only in the goroutine stack not on the
long-lived Model struct
Prevents key material exposure in heap dumps if the process crashed
added dedicated *Switch() methods (ShopSwitch, CartSwitc, AccountSwitch, ...) for page transitions and remove the catch-all resetPageState(). Each switch owns its pages zero-init, viewport setup, and async data fetching
the checkout flow was a bit wonky because it would display the same info twice which looked weird and confusing. this is now changed to only show once and update one of the pages independantly.
Now added the possibility to modify card and address list in the account page and not in the checkout flow
when the user added their card it would instantly complete the order. this could be unwanted so now i have added an extra input requirement to confirm the payment after adding the card
adds a /pay/{token} redirect endpoint that generates a short URL
pointing to a stripe checkout sessions in setup mode. The TUI shows a QR
code and URL on the payment page so users can add a card via the browser
instead of entering raw card data over SSH.
the checkout.session.completed webhook saves the card to the database.
the TUI polls GET /api/v1/cards every second and auto-proceeds to
confirmation once the new card is detected.
also adds a dev.sh startup script that runs all four required processes.
(SSH server, API server, Stripe CLI listener, ngrok) in a single
terminal
Three bugs fixed, two of them from the last commit, one of them has been
long living.
- the cart query in the ViewInit handler used user_id on cart_items, but
that column does not exist, cart_items links to carts via cart_id. Fixed
by first finding the users cart and then querying items by cart_id,
which is how the reso of the cart handlers already do it.
- GetViewInit was missing the json.Decode call entirely. so the response
body was never parsed. This meant m.User was never set, blocking all
cart syncs and address saves, and the cart always loaded empty.
- after a successful order, the cart was never cleared locally even
though the server deleted the cart items. this meant that the user had
to quit and ssh back in to see the cart update. fixed by clreaing
m.Cart, m.CartCursor and m.ShippingInfo in the CheckoutResultMsg success
handler
previously main.go handled auth directly, it connected to the database,
ran migrations, created a JWT manager and SSHAuthService, and
authenticated the SSH key before the TUI even started. this commit rips
all of that out
Auth now happens inside the TUI during the splash screen via
splashAuthCmd, which calls the /auth/token endpoint. if no user exists
or the fingerprint it auto-creates one using the SSH public ke, so there
is no longer a separate registartion flow. The token and user com back
in the response.
once authed splashViewInitCmd fires a single /view/init request that
loads everything needed to start the app, products, cart, addresses,
cards, and orders, before switching to the shop page
on the model side, User/*models.user and gossh.PublicKey were replaced
with plain Fingerprint and SSHPublicKeyStr strings, and NewModelWithAuth
was updated too
prices stored as float64 could lead to rounding errors when converting to cents for stripe (19.99 * 100 = 1998.99... truncating to 1998). canged Coffee.Price to int, storing cents directly. Updated the seed data, fallback products, API handlers, and all TUI display code to be in sync with this change.