diff --git a/cli/.goreleaser.yaml b/cli/.goreleaser.yaml index 1652259..7ce8a3f 100644 --- a/cli/.goreleaser.yaml +++ b/cli/.goreleaser.yaml @@ -1,5 +1,5 @@ # goreleaser config for the hookdrop CLI. -# Released locally via scripts/release.sh (see RELEASING.md) — no CI. +# Released locally via scripts/release.sh (see RELEASING.md). No CI. # The backend is deployed separately by scripts/deploy.sh and never tagged. version: 2 diff --git a/cli/README.md b/cli/README.md index 85dd1f4..add4773 100644 --- a/cli/README.md +++ b/cli/README.md @@ -14,7 +14,7 @@ hookdrop listen my-slug -f 3000 # stream webhooks + forward to localhost:30 ## Install -### curl (recommended — one line, no extra steps) +### curl (recommended, one line, no extra steps) ```sh curl -fsSL https://raw.githubusercontent.com/EOEboh/hookdrop/main/scripts/install.sh | sh @@ -41,7 +41,7 @@ fails with "Refusing to load formula … from untrusted tap".) Download the archive from [GitHub Releases](https://github.com/EOEboh/hookdrop/releases). (Note: replacing `hookdrop.exe` while a `listen` session is running will fail -on Windows — stop it first. On macOS/Linux, upgrading while running is fine; +on Windows, so stop it first. On macOS/Linux, upgrading while running is fine; the active session keeps the old binary until it exits.) ## Use @@ -69,8 +69,8 @@ Shell completions are available via `hookdrop completion bash|zsh|fish|powershel Forwarded requests carry `X-Hookdrop-Forwarded: true` and `X-Hookdrop-Original-Id` headers; hop-by-hop headers (`Host`, -`Content-Length`, …) are regenerated, everything else — including provider -signature headers — is preserved byte-for-byte. +`Content-Length`, …) are regenerated, everything else, including provider +signature headers, is preserved byte-for-byte. ## Config diff --git a/cli/cmd/listen.go b/cli/cmd/listen.go index ae1b5ea..63d8086 100644 --- a/cli/cmd/listen.go +++ b/cli/cmd/listen.go @@ -46,7 +46,7 @@ var listenCmd = &cobra.Command{ Short: "Stream webhooks live into your terminal (and forward them locally)", Long: `Streams every webhook hitting your hookdrop endpoint into the terminal, one line per event. With -f/--forward, each webhook is also re-sent to a local -server — the way the web UI's replay works — so your dev server receives traffic +server. The way the web UI's replay works, so your dev server receives traffic the hosted backend can't deliver directly. The endpoint is optional: with one named endpoint it's picked automatically, @@ -169,7 +169,7 @@ func resolveEndpoint(ctx context.Context, client *api.Client, explicit, frontend for _, e := range endpoints { slugs = append(slugs, e.Slug) } - return "", fmt.Errorf("multiple endpoints (%s) — specify one: hookdrop listen ", strings.Join(slugs, ", ")) + return "", fmt.Errorf("multiple endpoints (%s). Specify one: hookdrop listen ", strings.Join(slugs, ", ")) } return pickEndpoint(endpoints) } @@ -229,7 +229,7 @@ func runListen(ctx context.Context, apiURL, token, endpoint, forwardURL string) } printer.Line(printer.Event(&req, forwarding)) if forwarding && !fwd.Enqueue(&req) { - printer.Line(printer.Status("⚠ forward queue full — dropped " + output.ShortID(req.ID) + " (still visible in the dashboard)")) + printer.Line(printer.Status("⚠ forward queue full. Dropped " + output.ShortID(req.ID) + " (still visible in the dashboard)")) } } } @@ -251,7 +251,7 @@ func runListen(ctx context.Context, apiURL, token, endpoint, forwardURL string) printer.Line(printer.Status("stopped")) return nil case errors.Is(err, api.ErrUnauthorized): - return errors.New("your session is no longer valid — the token may have been revoked. Run 'hookdrop login' again") + return errors.New("your session is no longer valid. The token may have been revoked. Run 'hookdrop login' again") case errors.Is(err, api.ErrNotFound): return fmt.Errorf("endpoint %q not found on your account (it may have expired if it was a temporary session). Run 'hookdrop endpoints' to see yours", endpoint) case errors.Is(err, api.ErrPaymentRequired): @@ -264,7 +264,7 @@ func runListen(ctx context.Context, apiURL, token, endpoint, forwardURL string) } attempt++ jittered := delay + time.Duration(rand.Int63n(int64(delay/2+1))) - printer.Line(printer.Status(fmt.Sprintf("⟳ connection lost (%v) — reconnecting in %s (attempt %d)", err, jittered.Round(time.Second), attempt))) + printer.Line(printer.Status(fmt.Sprintf("⟳ connection lost (%v). Reconnecting in %s (attempt %d)", err, jittered.Round(time.Second), attempt))) select { case <-time.After(jittered): diff --git a/cli/cmd/login.go b/cli/cmd/login.go index a86e11b..8992da6 100644 --- a/cli/cmd/login.go +++ b/cli/cmd/login.go @@ -46,7 +46,7 @@ or --no-browser on headless machines.`, if token == "" && !loginNoBrowser { token, err = browserLogin(cmd.Context(), cfg.FrontendURL) if err != nil { - fmt.Fprintf(os.Stderr, "Browser login didn't complete (%v) — falling back to manual entry.\n", err) + fmt.Fprintf(os.Stderr, "Browser login didn't complete (%v). Falling back to manual entry.\n", err) } } if token == "" { @@ -110,7 +110,7 @@ func browserLogin(ctx context.Context, frontendURL string) (string, error) { fmt.Printf("Opening your browser to authorize the CLI…\n %s\n", authURL) if err := openBrowser(authURL); err != nil { - fmt.Fprintln(os.Stderr, "Couldn't open a browser automatically — open the URL above manually.") + fmt.Fprintln(os.Stderr, "Couldn't open a browser automatically. Open the URL above manually.") } fmt.Println("Waiting for authorization…") @@ -135,7 +135,7 @@ func waitForCallback(ctx context.Context, listener net.Listener, state string, t } w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, ` -

✓ You're logged in

Return to your terminal — you can close this tab.

+

✓ You're logged in

Return to your terminal. You can close this tab.

`) // Flush before signaling: the wait returns and closes the server as // soon as it has the token, which would race the buffered response. diff --git a/cli/cmd/login_test.go b/cli/cmd/login_test.go index 44b60d7..bffe3d0 100644 --- a/cli/cmd/login_test.go +++ b/cli/cmd/login_test.go @@ -129,12 +129,12 @@ func TestWaitForCallbackTimesOut(t *testing.T) { t.Fatal("expected timeout error") } if elapsed := time.Since(start); elapsed < 150*time.Millisecond { - t.Fatalf("returned too early (%v) — timeout not honored", elapsed) + t.Fatalf("returned too early (%v). Timeout not honored", elapsed) } } // Two listeners bound back-to-back get distinct OS-assigned ports and each -// delivers independently — demonstrating the port-0 design has no +// delivers independently. Demonstrating the port-0 design has no // fixed-port collision even with concurrent logins. func TestConcurrentCallbacksDistinctPorts(t *testing.T) { l1, l2 := listen(t), listen(t) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index c5d3867..5af38a2 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -14,7 +14,7 @@ var rootCmd = &cobra.Command{ Use: "hookdrop", Short: "Stream and forward your hookdrop webhooks from the terminal", Long: `hookdrop streams webhooks captured at hookdrop.app into your terminal and -forwards each one to a local server the hosted backend can't reach directly — +forwards each one to a local server the hosted backend can't reach directly. a local webhook forwarder for developing against real webhook traffic.`, Example: ` hookdrop login # authenticate (opens your browser) hookdrop listen my-slug -f 3000 # stream + forward to localhost:3000 diff --git a/cli/internal/api/types.go b/cli/internal/api/types.go index 8de891e..7c48a6c 100644 --- a/cli/internal/api/types.go +++ b/cli/internal/api/types.go @@ -3,7 +3,7 @@ package api import "time" // Wire types mirroring the backend's JSON responses. Source of truth: -// internal/models/request.go in the backend module — keep field names and +// internal/models/request.go in the backend module. Keep field names and // JSON tags in sync (internal/ packages can't be imported across modules). // CapturedRequest is one webhook as delivered over SSE and /requests. diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go index e4741e3..8c4c139 100644 --- a/cli/internal/config/config.go +++ b/cli/internal/config/config.go @@ -1,5 +1,5 @@ // Package config manages the CLI's local configuration file, which holds -// the API token — permissions are locked down and writes are atomic. +// the API token. Permissions are locked down and writes are atomic. package config import ( @@ -50,12 +50,12 @@ func Load() (*Config, error) { data, err := os.ReadFile(path) switch { case errors.Is(err, os.ErrNotExist): - // fresh install — fall through to defaults + // fresh install. Fall through to defaults case err != nil: return nil, fmt.Errorf("read config %s: %w", path, err) default: if jsonErr := json.Unmarshal(data, cfg); jsonErr != nil { - return nil, fmt.Errorf("config file %s is corrupt (%v) — run 'hookdrop login' to recreate it", path, jsonErr) + return nil, fmt.Errorf("config file %s is corrupt (%v). Run 'hookdrop login' to recreate it", path, jsonErr) } warnLoosePermissions(path) } @@ -123,7 +123,7 @@ func warnLoosePermissions(path string) { } if info.Mode().Perm()&0o077 != 0 { fmt.Fprintf(os.Stderr, - "warning: %s is readable by other users (mode %o) — consider: chmod 600 %s\n", + "warning: %s is readable by other users (mode %o). Consider: chmod 600 %s\n", path, info.Mode().Perm(), path) } } diff --git a/cli/internal/forward/forwarder.go b/cli/internal/forward/forwarder.go index 12d889a..0b70f1d 100644 --- a/cli/internal/forward/forwarder.go +++ b/cli/internal/forward/forwarder.go @@ -69,7 +69,7 @@ func (f *Forwarder) Start(ctx context.Context) { } // Enqueue adds a webhook to the delivery queue. Returns false when the -// queue is full — callers should surface the drop, never block the SSE +// queue is full. Callers should surface the drop, never block the SSE // reader on a slow local server. func (f *Forwarder) Enqueue(req *api.CapturedRequest) bool { select { @@ -115,7 +115,7 @@ func (f *Forwarder) deliver(ctx context.Context, original *api.CapturedRequest) // shouldSkipHeader filters headers that break or are meaningless when // forwarded. Parity port of shouldSkipHeader in the backend's -// internal/replay/engine.go — keep the two lists identical so behavior +// internal/replay/engine.go. Keep the two lists identical so behavior // matches whether a request is replayed from the web UI or forwarded live. func shouldSkipHeader(key string) bool { skip := map[string]bool{ diff --git a/cli/internal/forward/forwarder_test.go b/cli/internal/forward/forwarder_test.go index a2c5866..0e1c4d4 100644 --- a/cli/internal/forward/forwarder_test.go +++ b/cli/internal/forward/forwarder_test.go @@ -89,7 +89,7 @@ func TestDeliverUnreachableTarget(t *testing.T) { func TestEnqueueOverflowDropsInsteadOfBlocking(t *testing.T) { f := New("http://127.0.0.1:1", func(Result) {}) - // worker not started — queue just fills + // worker not started. Queue just fills for i := 0; i < queueSize; i++ { if !f.Enqueue(&api.CapturedRequest{}) { t.Fatalf("enqueue %d should succeed", i) diff --git a/cli/internal/output/printer.go b/cli/internal/output/printer.go index a302dd0..9f0df20 100644 --- a/cli/internal/output/printer.go +++ b/cli/internal/output/printer.go @@ -76,7 +76,7 @@ func (p *Printer) Event(req *api.CapturedRequest, showID bool) string { // ForwardResult renders the delivery outcome, indented under its event: // -// ↳ (4dbb48bd) 200 in 45ms +// ↳ (4dbb48bd) 200 in 45ms func (p *Printer) ForwardResult(res forward.Result) string { id := Colorize(p.Colors, Dim, "("+ShortID(res.Request.ID)+")") if res.Err != nil { @@ -105,7 +105,7 @@ func (p *Printer) Status(s string) string { func (p *Printer) Ready(inboxURL, forwardURL string) string { check := Colorize(p.Colors, Green, "✓") var b strings.Builder - fmt.Fprintf(&b, "%s Ready — listening on %s\n", check, Colorize(p.Colors, Bold, inboxURL)) + fmt.Fprintf(&b, "%s Ready. Listening on %s\n", check, Colorize(p.Colors, Bold, inboxURL)) if forwardURL != "" { fmt.Fprintf(&b, " → forwarding to %s\n", Colorize(p.Colors, Bold, forwardURL)) } diff --git a/cli/internal/sseclient/client.go b/cli/internal/sseclient/client.go index 53bde46..9cf0cd0 100644 --- a/cli/internal/sseclient/client.go +++ b/cli/internal/sseclient/client.go @@ -1,6 +1,6 @@ // Package sseclient is a minimal Server-Sent Events client for the hookdrop // /events stream. The format is three line types: "event:", "data:", and -// ":" comments (keepalives) — no library needed. +// ":" comments (keepalives). No library needed. package sseclient import ( @@ -41,7 +41,7 @@ func New(baseURL, token string) *Client { return &Client{ BaseURL: strings.TrimRight(baseURL, "/"), Token: token, - // No overall client timeout — the stream is long-lived. Individual + // No overall client timeout. The stream is long-lived. Individual // phases are bounded instead; mid-stream silence is the watchdog's job. http: &http.Client{ Transport: &http.Transport{ @@ -111,7 +111,7 @@ func (c *Client) Stream(ctx context.Context, identifier string, events chan<- Ev } } case line[0] == ':': - // comment/keepalive — watchdog already reset + // comment/keepalive. Watchdog already reset case bytes.HasPrefix(line, []byte("event:")): eventName = string(bytes.TrimSpace(line[len("event:"):])) case bytes.HasPrefix(line, []byte("data:")): @@ -123,7 +123,7 @@ func (c *Client) Stream(ctx context.Context, identifier string, events chan<- Ev } if watchdogFired.Load() { - return fmt.Errorf("no data for %s — connection stale", watchdogTimeout) + return fmt.Errorf("no data for %s. Connection stale", watchdogTimeout) } if ctx.Err() != nil { return ctx.Err() diff --git a/internal/billing/entitlements.go b/internal/billing/entitlements.go index 5dd8212..1dd302f 100644 --- a/internal/billing/entitlements.go +++ b/internal/billing/entitlements.go @@ -62,8 +62,8 @@ const PastDueGrace = 7 * 24 * time.Hour // IsActive reports whether a subscription is usable right now. // // The period is authoritative, not the status. Paystack does not retry a -// failed subscription charge — "when a payment attempt fails, it will not be -// attempted again" — so a lapsed subscription simply stops producing events +// failed subscription charge: "when a payment attempt fails, it will not be +// attempted again", so a lapsed subscription simply stops producing events // and sits at status "active" forever. Trusting the status alone handed those // customers Pro indefinitely. func IsActive(status string, periodEnd *time.Time) bool { diff --git a/internal/billing/lemonsqueezy.go b/internal/billing/lemonsqueezy.go index 7c8f2e1..f0aa2a9 100644 --- a/internal/billing/lemonsqueezy.go +++ b/internal/billing/lemonsqueezy.go @@ -116,7 +116,7 @@ func (p *LemonSqueezyProvider) CreateCheckout( // Lemonsqueezy uses JSON:API format. // - // store_id and variant_id are READ-ONLY response attributes — the only + // store_id and variant_id are READ-ONLY response attributes. The only // attributes accepted on create are custom_price, product_options, // checkout_options, checkout_data, preview, test_mode and expires_at. // The store and variant are addressed through relationships. @@ -142,7 +142,7 @@ func (p *LemonSqueezyProvider) CreateCheckout( }, // The free trial is configured on the variant in the - // Lemonsqueezy dashboard — there is no per-checkout trial + // Lemonsqueezy dashboard. There is no per-checkout trial // override. checkout_options.skip_trial would REMOVE it, so // it is deliberately not set here. "checkout_options": map[string]interface{}{ @@ -243,8 +243,8 @@ func (p *LemonSqueezyProvider) GetPortalURL( url := result.Data.Attributes.URLs.CustomerPortal if url == "" { - // Do not silently bounce the user back to the page they came from — - // that hides a real failure behind a no-op redirect. + // Do not silently bounce the user back to the page they came from. + // That hides a real failure behind a no-op redirect. return "", fmt.Errorf( "lemonsqueezy customer %s returned no customer_portal URL", customerID) } @@ -256,8 +256,8 @@ func (p *LemonSqueezyProvider) GetPortalURL( // Every other Lemonsqueezy event carries a DIFFERENT object shape: // order_created sends an Order (no top-level variant_id, status "paid"), // subscription_payment_* send a Subscription invoice. Parsing either as a -// subscription writes a garbage row — plan "free", status "paid" and an order -// ID in provider_sub_id — over a paying customer. +// subscription writes a garbage row. Plan "free", status "paid" and an order +// ID in provider_sub_id, over a paying customer. var lsSubscriptionEvents = map[string]bool{ "subscription_created": true, "subscription_updated": true, @@ -294,8 +294,8 @@ func (p *LemonSqueezyProvider) HandleWebhook( // Parse the event envelope. // // Note there is deliberately no first_subscription_item here: it is null - // while the subscription is on trial, and it carries no interval field — - // the interval is derived from variant_id instead. + // while the subscription is on trial, and it carries no interval field. + // The interval is derived from variant_id instead. var envelope struct { Meta struct { EventName string `json:"event_name"` @@ -329,7 +329,7 @@ func (p *LemonSqueezyProvider) HandleWebhook( } if !lsSubscriptionEvents[envelope.Meta.EventName] { - // Not a subscription event — nothing to persist. + // Not a subscription event, so there is nothing to persist. return nil, nil } diff --git a/internal/billing/lemonsqueezy_test.go b/internal/billing/lemonsqueezy_test.go index ed4f850..9e7f155 100644 --- a/internal/billing/lemonsqueezy_test.go +++ b/internal/billing/lemonsqueezy_test.go @@ -35,7 +35,7 @@ func handle(t *testing.T, payload string) (*WebhookEvent, error) { } // subscriptionPayload mirrors the shape Lemonsqueezy actually sends for -// subscription events. Note first_subscription_item is null — that is what a +// subscription events. Note first_subscription_item is null. That is what a // subscription on a free trial looks like. const trialCreatedPayload = `{ "meta": { @@ -100,7 +100,7 @@ func TestHandleWebhook_TrialSubscriptionCreated(t *testing.T) { if ev.Status != "trialing" { t.Errorf("Status = %q, want trialing", ev.Status) } - // The customer ID must be the LS customer, not the order or subscription — + // The customer ID must be the LS customer, not the order or subscription. // GetPortalURL calls GET /customers/{id} with it. if ev.CustomerID != "4210987" { t.Errorf("CustomerID = %q, want 4210987 (customer_id, not order_id)", ev.CustomerID) @@ -112,7 +112,7 @@ func TestHandleWebhook_TrialSubscriptionCreated(t *testing.T) { t.Errorf("Interval = %q, want month", ev.Interval) } if ev.TrialEnd == 0 { - t.Error("TrialEnd = 0, want the parsed trial_ends_at — a nil trial_end renders 'Trial ends soon' forever") + t.Error("TrialEnd = 0, want the parsed trial_ends_at. A nil trial_end renders 'Trial ends soon' forever") } if want := time.Date(2026, 8, 10, 12, 0, 0, 0, time.UTC).Unix(); ev.TrialEnd != want { t.Errorf("TrialEnd = %d, want %d", ev.TrialEnd, want) @@ -172,7 +172,7 @@ func TestHandleWebhook_IgnoresNonSubscriptionEvents(t *testing.T) { t.Fatalf("unexpected error: %v", err) } if ev != nil { - t.Fatalf("expected nil event, got %+v — this would corrupt the subscriptions row", ev) + t.Fatalf("expected nil event, got %+v. This would corrupt the subscriptions row", ev) } }) } @@ -193,7 +193,7 @@ func TestHandleWebhook_CancelKeepsAccessUntilEndsAt(t *testing.T) { t.Fatalf("unexpected error: %v", err) } if ev.Type == "subscription.canceled" { - t.Error("subscription_cancelled mapped to subscription.canceled — that drops the customer to free during a grace period they paid for") + t.Error("subscription_cancelled mapped to subscription.canceled. That drops the customer to free during a grace period they paid for") } if ev.Type != "subscription.updated" { t.Errorf("Type = %q, want subscription.updated", ev.Type) @@ -229,7 +229,7 @@ func TestHandleWebhook_ExpiredDowngrades(t *testing.T) { t.Errorf("Type = %q, want subscription.canceled", ev.Type) } if ev.Status != "canceled" { - t.Errorf("Status = %q, want canceled — an expired subscription reported as active keeps Pro forever", ev.Status) + t.Errorf("Status = %q, want canceled. An expired subscription reported as active keeps Pro forever", ev.Status) } } diff --git a/internal/billing/paystack.go b/internal/billing/paystack.go index fa43239..c0e94c3 100644 --- a/internal/billing/paystack.go +++ b/internal/billing/paystack.go @@ -353,7 +353,7 @@ func (p *PaystackProvider) HandleWebhook(payload []byte, signature string) (*Web }, nil case "charge.success": - // Subscription renewals arrive as charge.success — Paystack sends no + // Subscription renewals arrive as charge.success. Paystack sends no // subscription.* event when a recurring payment goes through. Without // handling it, current_period_end is written once at signup and then // silently goes stale. @@ -397,7 +397,7 @@ func (p *PaystackProvider) HandleWebhook(payload []byte, signature string) (*Web Type: "subscription.updated", CustomerID: charge.Customer.CustomerCode, // charge.success names no subscription, so this cannot set - // provider_sub_id — the user resolves by customer code. + // provider_sub_id. The user resolves by customer code. Plan: "pro", Status: "active", Currency: "ngn", @@ -414,7 +414,7 @@ func (p *PaystackProvider) HandleWebhook(payload []byte, signature string) (*Web // only notice we get that the subscription has stopped paying. // // This exists to make the downgrade prompt. It is NOT what guarantees - // correctness — IsActive expires access once the period lapses, which + // correctness. IsActive expires access once the period lapses, which // holds whether or not this event ever arrives or is shaped as // expected. Parsed defensively for that reason: only fields present in // every Paystack payload observed so far are relied on. @@ -455,7 +455,7 @@ func (p *PaystackProvider) HandleWebhook(payload []byte, signature string) (*Web Type: "subscription.updated", CustomerID: inv.Customer.CustomerCode, // The subscription code is nested and its shape is unconfirmed, so - // it is deliberately not read — processWebhookEvent preserves the + // it is deliberately not read. ProcessWebhookEvent preserves the // stored one and the user resolves by customer code. Plan: "pro", Status: "past_due", @@ -556,7 +556,7 @@ func (p *PaystackProvider) planFromCode(code string) string { // IntervalForPlanCode reports the billing interval for one of our configured // plan codes. The second return is false for any code that is not ours. // -// This is the authority on interval — the client-supplied value is not +// This is the authority on interval. The client-supplied value is not // trusted, since it drives current_period_end. func (p *PaystackProvider) IntervalForPlanCode(code string) (string, bool) { switch { @@ -588,7 +588,7 @@ type PaystackTransaction struct { // Channel is how it was paid: card, bank_transfer, ussd and so on. Channel string // Reusable reports whether the authorization can be charged again. - // Paystack subscriptions need one that can — bank transfer produces + // Paystack subscriptions need one that can. Bank transfer produces // reusable=false, so the plan is charged once and no subscription is // created at all. Reusable bool @@ -609,7 +609,7 @@ type PaystackPlanInfo struct { // paystackVerifyResponse mirrors GET /transaction/verify/{reference}. // // Plan is json.RawMessage because Paystack returns it as a plan code string on -// some transactions and as a full plan object on others — decoding it into a +// some transactions and as a full plan object on others. Decoding it into a // fixed shape aborts the whole parse mid-way, which is what originally made // legitimate payments fail verification. type paystackVerifyResponse struct { @@ -765,7 +765,7 @@ func (p *PaystackProvider) VerifyTransaction( // object, which is the only route from a customer *code* to a subscription. // // GET /subscription?customer= expects the numeric customer id, not the code, -// and silently returns zero rows when given a code — which is what made the +// and silently returns zero rows when given a code, which is what made the // old cancel path fail without complaining. type PaystackCustomerSubscription struct { SubscriptionCode string diff --git a/internal/billing/paystack_test.go b/internal/billing/paystack_test.go index 81d7757..d07350b 100644 --- a/internal/billing/paystack_test.go +++ b/internal/billing/paystack_test.go @@ -178,7 +178,7 @@ func TestVerifyTransaction_Success(t *testing.T) { t.Fatalf("unexpected error: %v", err) } if *calls != 1 { - t.Errorf("made %d requests, want 1 — a success must not retry", *calls) + t.Errorf("made %d requests, want 1. A success must not retry", *calls) } if tx.Amount != 350000 || tx.Currency != "NGN" { t.Errorf("amount/currency = %d/%s", tx.Amount, tx.Currency) @@ -206,7 +206,7 @@ func TestVerifyTransaction_FailuresAreErrors(t *testing.T) { p, calls := paystackStub(t, body, 200) tx, err := p.VerifyTransaction(context.Background(), "T_bad") if err == nil { - t.Fatalf("expected an error, got tx=%+v — this is the free-Pro escalation", tx) + t.Fatalf("expected an error, got tx=%+v. This is the free-Pro escalation", tx) } if *calls != 3 { t.Errorf("made %d attempts, want 3 (retries preserved)", *calls) @@ -290,7 +290,7 @@ func TestHandleWebhook_ReadsUserIDFromTransactionMetadata(t *testing.T) { } } -// metadata:0 must not abort the parse — the event still has to resolve by +// metadata:0 must not abort the parse. The event still has to resolve by // customer code downstream. func TestHandleWebhook_SurvivesIntegerMetadata(t *testing.T) { p := NewPaystackProvider("sk", testSecret512, PaystackPlans{ProMonthly: "PLN_monthly"}) @@ -308,7 +308,7 @@ func TestHandleWebhook_SurvivesIntegerMetadata(t *testing.T) { t.Errorf("UserID = %q, want empty", ev.UserID) } if ev.CustomerID != "CUS_1" { - t.Errorf("CustomerID = %q, want CUS_1 — the fallback key", ev.CustomerID) + t.Errorf("CustomerID = %q, want CUS_1. The fallback key", ev.CustomerID) } } @@ -347,7 +347,7 @@ func TestHandleWebhook_NotRenewFlagsCancelAtPeriodEnd(t *testing.T) { // realSubscriptionCreatePayload is a verbatim capture of what Paystack // actually sent on subscription.create (test mode, 2026-07-28), trimmed of // identifying values. Note data.plan is an OBJECT here, though it is a bare -// code string on transaction payloads — decoding it as a string failed the +// code string on transaction payloads. Decoding it as a string failed the // whole parse and dropped the event with a 400. const realSubscriptionCreatePayload = `{ "event": "subscription.create", @@ -406,7 +406,7 @@ func TestHandleWebhook_RealSubscriptionCreatePayload(t *testing.T) { // The object-shaped plan must still resolve to our plan. if ev.Plan != "pro" { - t.Errorf("Plan = %q, want pro — the object-shaped plan was not recognised", ev.Plan) + t.Errorf("Plan = %q, want pro. The object-shaped plan was not recognised", ev.Plan) } if ev.SubscriptionID != "SUB_xhhcq6g7fl194tn" { t.Errorf("SubscriptionID = %q, want the SUB_ code", ev.SubscriptionID) @@ -414,7 +414,7 @@ func TestHandleWebhook_RealSubscriptionCreatePayload(t *testing.T) { // metadata is null on subscription events, so the customer code is the // only key that can resolve the user. if ev.UserID != "" { - t.Errorf("UserID = %q, want empty — Paystack sends metadata:null here", ev.UserID) + t.Errorf("UserID = %q, want empty. Paystack sends metadata:null here", ev.UserID) } if ev.CustomerID != "CUS_mpzcgx3mniosw1j" { t.Errorf("CustomerID = %q, want the customer code", ev.CustomerID) diff --git a/internal/billing/provider.go b/internal/billing/provider.go index 5ed8822..08a5fa7 100644 --- a/internal/billing/provider.go +++ b/internal/billing/provider.go @@ -56,7 +56,7 @@ type WebhookEvent struct { // // Currency selection happens on the client (detectCurrency in // ui/src/context/BillingContext.tsx), which keeps its own timezone list -// derived from this map — keep the two in step. +// derived from this map. Keep the two in step. var PaystackCountries = map[string]bool{ "NG": true, "GH": true, "ZA": true, "KE": true, "CI": true, "RW": true, "TZ": true, "EG": true, diff --git a/internal/email/resend.go b/internal/email/resend.go index 229c775..529277d 100644 --- a/internal/email/resend.go +++ b/internal/email/resend.go @@ -69,7 +69,7 @@ func (s *Sender) SendMagicLink(toEmail, magicURL string) error { if resp.StatusCode >= 400 { // Include the body and the recipient. Resend explains its rejections - // there — reporting the bare status code made a recurring 422 + // there. Reporting the bare status code made a recurring 422 // impossible to diagnose. body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10)) return fmt.Errorf("resend API error %d sending to %s: %s", diff --git a/internal/handler/auth.go b/internal/handler/auth.go index e2aa7e0..332ba19 100644 --- a/internal/handler/auth.go +++ b/internal/handler/auth.go @@ -22,7 +22,7 @@ type AuthHandler struct { EmailLimiter *middleware.EmailRateLimiter } -// POST /auth/request — send a magic link +// POST /auth/request. Send a magic link func (h *AuthHandler) RequestLink(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) @@ -82,7 +82,7 @@ func (h *AuthHandler) RequestLink(w http.ResponseWriter, r *http.Request) { }) } -// GET /auth/verify?token=xxx — verify token, return JWT +// GET /auth/verify?token=xxx. Verify token, return JWT func (h *AuthHandler) VerifyLink(w http.ResponseWriter, r *http.Request) { token := r.URL.Query().Get("token") if token == "" { diff --git a/internal/handler/billing.go b/internal/handler/billing.go index 479dbe8..a981ad2 100644 --- a/internal/handler/billing.go +++ b/internal/handler/billing.go @@ -185,7 +185,7 @@ func (h *BillingHandler) handleWebhook( route webhookRoute, ) { // MaxBytesReader errors on an oversized body rather than silently - // truncating it — a truncated payload would fail signature verification + // truncating it. A truncated payload would fail signature verification // and be reported as a forgery. payload, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20)) if err != nil { @@ -222,7 +222,7 @@ func (h *BillingHandler) handleWebhook( if event != nil { record.UserID = event.UserID // Scope the ordering check by subscription where there is one, else by - // customer — charge.success identifies only the customer. + // customer, charge.success identifies only the customer. record.ObjectID = event.SubscriptionID if record.ObjectID == "" { record.ObjectID = event.CustomerID @@ -237,7 +237,7 @@ func (h *BillingHandler) handleWebhook( case errors.Is(err, store.ErrDuplicateBillingEvent): // The provider retried something we already have. 200 stops the // retries; re-processing would be wasted work at best. - log.Printf("%s webhook: duplicate delivery %s (%s) — already recorded", + log.Printf("%s webhook: duplicate delivery %s (%s). Already recorded", route.provider, eventType, eventKey[:12]) writeWebhookOK(w) return @@ -275,7 +275,7 @@ func (h *BillingHandler) handleWebhook( return } } else { - log.Printf("%s webhook: %s carries no timestamp — no ordering protection", + log.Printf("%s webhook: %s carries no timestamp. No ordering protection", route.provider, eventType) } @@ -333,7 +333,7 @@ func (h *BillingHandler) processWebhookEvent( // custom_data.user_id should always be present on subscription events, but // if it ever isn't, recover the user from the subscription ID we already - // stored rather than dropping the event on the floor — a dropped + // stored rather than dropping the event on the floor. A dropped // cancellation leaves a customer on Pro forever. if userID == "" { existing, via, err := h.resolveWebhookUser(event) @@ -346,7 +346,7 @@ func (h *BillingHandler) processWebhookEvent( providerName, event.Type, event.SubscriptionID, event.CustomerID) } userID = existing.UserID - log.Printf("WARNING: %s webhook %s carried no user_id — resolved user=%s via %s", + log.Printf("WARNING: %s webhook %s carried no user_id. Resolved user=%s via %s", providerName, event.Type, userID, via) } @@ -355,7 +355,7 @@ func (h *BillingHandler) processWebhookEvent( t := time.Unix(event.PeriodEnd, 0) periodEnd = &t } else if existing, err := h.Store.GetSubscription(userID); err == nil { - // Not every event moves the period — a failed renewal reports no new + // Not every event moves the period. A failed renewal reports no new // date. The upsert writes every column, so passing nil would blank // current_period_end, and a nil period grants access indefinitely. // That would turn the expiry gate off for exactly the subscriptions it @@ -487,7 +487,7 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) return } if body.Interval != "" && body.Interval != interval { - log.Printf("VerifyPaystack: client claimed interval=%q, plan %s says %q — using %q", + log.Printf("VerifyPaystack: client claimed interval=%q, plan %s says %q. Using %q", body.Interval, tx.Plan.Code, interval, interval) } @@ -498,8 +498,8 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) switch { case !tx.Plan.AmountKnown: // Paystack returned a bare plan code with no plan object. The plan - // code check above already carries the weight here — attaching our - // plan code makes Paystack charge that plan's price — so accept, but + // code check above already carries the weight here. Attaching our + // plan code makes Paystack charge that plan's price, so accept, but // say so. log.Printf("WARNING: VerifyPaystack: plan %s amount unknown, cannot cross-check charge of %d (user=%s ref=%s)", tx.Plan.Code, tx.Amount, user.ID, body.Reference) @@ -536,14 +536,14 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) http.Error(w, "this payment has already been redeemed", http.StatusConflict) return } - // Same user re-verifying must not fail — a double submit or a retried - // handlePaystackSuccess should not lock a paying customer out. But it must + // Same user re-verifying must not fail. A double submit or a retried + // handlePaystackSuccess should not lock a paying customer out, but it must // not be *applied* twice either: the period is extended from whatever is // left, so replaying a reference would hand out free time. // // Recording the verification claims the reference. The UNIQUE index on // event_key decides, which is stronger than comparing against - // provider_sub_id — that only remembers the most recent reference and + // provider_sub_id. That only remembers the most recent reference and // would let an older one be replayed after a renewal. claim := sha256.Sum256([]byte("paystack-verify:" + body.Reference)) claimKey := hex.EncodeToString(claim[:]) @@ -556,7 +556,7 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) ObjectID: tx.CustomerCode, }); { case errors.Is(err, store.ErrDuplicateBillingEvent): - log.Printf("VerifyPaystack: reference %s already applied for user=%s — returning current state", + log.Printf("VerifyPaystack: reference %s already applied for user=%s. Returning current state", body.Reference, user.ID) current, err := h.Store.GetSubscription(user.ID) if err != nil { @@ -583,16 +583,16 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) // Paystack subscriptions require an authorization that can be charged // again. A bank transfer cannot, so Paystack takes the plan amount once - // and creates no subscription — the customer has bought a single period. + // and creates no subscription. The customer has bought a single period. // They keep what they paid for; the UI has to stop calling it a // subscription. if !tx.Reusable { - log.Printf("VerifyPaystack: %s payment is not reusable — user=%s gets one period and will NOT auto-renew", + log.Printf("VerifyPaystack: %s payment is not reusable. User=%s gets one period and will NOT auto-renew", tx.Channel, user.ID) } // NGN pricing is substantially cheaper than USD, and the currency is - // chosen client-side — localStorage hookdrop_currency is enough to pick + // chosen client-side. LocalStorage hookdrop_currency is enough to pick // it. The card's country is the only server-side evidence of where the // payer really is. // @@ -601,7 +601,7 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) // a worse failure than the mispricing. This makes the exposure visible so // it can be judged on evidence. if tx.CardCountry != "" && !billing.PaystackCountries[tx.CardCountry] { - log.Printf("WARNING: VerifyPaystack: NGN pricing paid with a %s card — user=%s ref=%s customer=%s payer_ip=%s (review: possible currency mispricing)", + log.Printf("WARNING: VerifyPaystack: NGN pricing paid with a %s card. User=%s ref=%s customer=%s payer_ip=%s (review: possible currency mispricing)", tx.CardCountry, user.ID, body.Reference, tx.CustomerCode, tx.PayerIP) } @@ -615,7 +615,7 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) // Stack onto whatever is left rather than restarting from now. A // non-recurring subscription is renewed by paying again, and someone who // renews early should not silently lose the days they already had. - // An expired period is not carried forward — that would backdate the + // An expired period is not carried forward. That would backdate the // renewal to a date already passed. base := now if current, err := h.Store.GetSubscription(user.ID); err == nil && @@ -647,7 +647,7 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) CreatedAt: now, } - log.Printf("VerifyPaystack: upserting — user=%s plan=pro status=%s customer=%s", + log.Printf("VerifyPaystack: upserting. User=%s plan=pro status=%s customer=%s", user.ID, subStatus, customerCode) if err := h.Store.UpsertSubscription(sub); err != nil { @@ -656,7 +656,7 @@ func (h *BillingHandler) VerifyPaystack(w http.ResponseWriter, r *http.Request) return } - log.Printf("VerifyPaystack: upsert SUCCESS — user=%s is now Pro (status=%s)", + log.Printf("VerifyPaystack: upsert SUCCESS. User=%s is now Pro (status=%s)", user.ID, subStatus) w.Header().Set("Content-Type", "application/json") @@ -717,7 +717,7 @@ func (h *BillingHandler) CancelSubscription(w http.ResponseWriter, r *http.Reque } if sub.CancelAtPeriodEnd { - // Already scheduled for cancellation — return success idempotently + // Already scheduled for cancellation. Return success idempotently w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "cancelled": true, @@ -747,11 +747,11 @@ func (h *BillingHandler) CancelSubscription(w http.ResponseWriter, r *http.Reque } else { // There is nothing callable. Honour the intent locally, but say // plainly that the provider was not told and is still billing. - log.Printf("WARNING: CancelSubscription: user=%s has no Paystack subscription code (provider_sub_id=%q) — cancelled locally only; Paystack was NOT notified and will keep billing", + log.Printf("WARNING: CancelSubscription: user=%s has no Paystack subscription code (provider_sub_id=%q). Cancelled locally only; Paystack was NOT notified and will keep billing", user.ID, sub.ProviderSubID) } } else { - // LemonSqueezy — use stored sub ID directly. + // LemonSqueezy. Use stored sub ID directly. // // This one is NOT best-effort: if the DELETE fails, Lemonsqueezy keeps // billing the customer. Marking them cancelled locally would show them diff --git a/internal/handler/billing_test.go b/internal/handler/billing_test.go index 4a86c01..6dc408a 100644 --- a/internal/handler/billing_test.go +++ b/internal/handler/billing_test.go @@ -158,12 +158,12 @@ func TestVerifyPaystack_RejectsUnverifiableTransaction(t *testing.T) { `{"reference":"T_fake","plan":"pro","interval":"month"}`)) if rec.Code == http.StatusOK { - t.Fatalf("got 200 — unverified transaction granted Pro") + t.Fatalf("got 200. Unverified transaction granted Pro") } sub, _ := h.Store.GetSubscription(user.ID) if sub.Plan != "free" { - t.Errorf("plan = %q, want free — nothing may be written on a failed verify", sub.Plan) + t.Errorf("plan = %q, want free. Nothing may be written on a failed verify", sub.Plan) } }) } @@ -197,7 +197,7 @@ func TestVerifyPaystack_EmailComparisonIsForgiving(t *testing.T) { `{"reference":"T_ref","plan":"pro","interval":"month"}`)) if rec.Code != http.StatusOK { - t.Fatalf("got %d, want 200 — a case/whitespace difference locked out a real customer: %s", + t.Fatalf("got %d, want 200. A case/whitespace difference locked out a real customer: %s", rec.Code, rec.Body.String()) } } @@ -241,7 +241,7 @@ func TestVerifyPaystack_RejectsReplayByAnotherUser(t *testing.T) { `{"reference":"T_ref","plan":"pro","interval":"month"}`)) if rec.Code != http.StatusConflict { - t.Fatalf("got %d, want 409 — a redeemed reference was accepted again: %s", + t.Fatalf("got %d, want 409. A redeemed reference was accepted again: %s", rec.Code, rec.Body.String()) } @@ -252,7 +252,7 @@ func TestVerifyPaystack_RejectsReplayByAnotherUser(t *testing.T) { // The rightful owner keeps their subscription. ownerSub, _ := h.Store.GetSubscription(owner.ID) if ownerSub.Plan != "pro" { - t.Errorf("owner plan = %q, want pro — the replay must not disturb them", ownerSub.Plan) + t.Errorf("owner plan = %q, want pro. The replay must not disturb them", ownerSub.Plan) } } @@ -303,7 +303,7 @@ func TestVerifyPaystack_RejectsAmountBelowPlanPrice(t *testing.T) { } // Paystack has no native free trial, so a ₦0 charge against a priced plan is -// never legitimate — it used to be treated as a trial and granted Pro. +// never legitimate. It used to be treated as a trial and granted Pro. func TestVerifyPaystack_RejectsZeroAmountCharge(t *testing.T) { h, user := newBillingTestHandler(t, verifyBody("success", "buyer@example.com", testPlanMonthly, 0, monthlyKobo)) @@ -350,7 +350,7 @@ func TestVerifyPaystack_NeverProducesATrial(t *testing.T) { sub, _ := h.Store.GetSubscription(user.ID) if sub.TrialEnd != nil { - t.Errorf("stored trial_end = %v, want nil — Paystack grants no trial", sub.TrialEnd) + t.Errorf("stored trial_end = %v, want nil. Paystack grants no trial", sub.TrialEnd) } if sub.Status != "active" { t.Errorf("stored status = %q, want active", sub.Status) @@ -372,7 +372,7 @@ func TestVerifyPaystack_IgnoresClientSuppliedInterval(t *testing.T) { sub, _ := h.Store.GetSubscription(user.ID) if sub.Interval != "month" { - t.Errorf("interval = %q, want month — the client's claim was trusted", sub.Interval) + t.Errorf("interval = %q, want month. The client's claim was trusted", sub.Interval) } // A month's access, not a year's. if sub.CurrentPeriodEnd == nil { @@ -519,7 +519,7 @@ func TestWebhook_StaleDeliveryDoesNotResurrectPro(t *testing.T) { sub, _ = h.Store.GetSubscription(user.ID) if sub.Plan != "free" { - t.Errorf("plan = %q, want free — a stale delivery resurrected Pro", sub.Plan) + t.Errorf("plan = %q, want free. A stale delivery resurrected Pro", sub.Plan) } if sub.Status != "canceled" { t.Errorf("status = %q, want canceled", sub.Status) @@ -537,7 +537,7 @@ func TestWebhook_ExpiredStatusDropsPlanToFree(t *testing.T) { sub, _ := h.Store.GetSubscription(user.ID) if sub.Plan != "free" { - t.Errorf("plan = %q, want free — subscription_updated carrying status=expired left the row inconsistent", sub.Plan) + t.Errorf("plan = %q, want free, subscription_updated carrying status=expired left the row inconsistent", sub.Plan) } } @@ -586,7 +586,7 @@ func TestWebhook_IgnoredEventRecordedButNotApplied(t *testing.T) { sub, _ := h.Store.GetSubscription(user.ID) if sub.Plan != "free" { - t.Errorf("plan = %q, want free — order_created must not touch the subscription", sub.Plan) + t.Errorf("plan = %q, want free, order_created must not touch the subscription", sub.Plan) } } @@ -658,7 +658,7 @@ func TestPaystackRenewalAdvancesPeriodWithoutLosingSubID(t *testing.T) { sub, _ := h.Store.GetSubscription(user.ID) if sub.ProviderSubID != "SUB_original" { - t.Errorf("provider_sub_id = %q, want SUB_original — the renewal blanked it", sub.ProviderSubID) + t.Errorf("provider_sub_id = %q, want SUB_original. The renewal blanked it", sub.ProviderSubID) } if sub.Plan != "pro" || sub.Status != "active" { t.Errorf("plan/status = %s/%s, want pro/active", sub.Plan, sub.Status) @@ -682,7 +682,7 @@ func TestPaystackChargeWithoutPlanIsIgnored(t *testing.T) { } sub, _ := h.Store.GetSubscription(user.ID) if sub.Plan != "free" { - t.Errorf("plan = %q, want free — a plan-less charge granted Pro", sub.Plan) + t.Errorf("plan = %q, want free. A plan-less charge granted Pro", sub.Plan) } } @@ -706,7 +706,7 @@ func TestPaystackForeignPlanChargeIgnored(t *testing.T) { } // A failed renewal must mark the subscription past_due WITHOUT blanking the -// period — a nil period grants access indefinitely, which would disable the +// period. A nil period grants access indefinitely, which would disable the // very expiry gate this event exists to trigger. func TestPaystackInvoiceFailureMarksPastDueAndKeepsPeriod(t *testing.T) { h, user := newPaystackWebhookHandler(t) @@ -742,7 +742,7 @@ func TestPaystackInvoiceFailureMarksPastDueAndKeepsPeriod(t *testing.T) { t.Errorf("status = %q, want past_due", sub.Status) } if sub.CurrentPeriodEnd == nil { - t.Fatal("current_period_end was blanked — the expiry gate can no longer fire") + t.Fatal("current_period_end was blanked. The expiry gate can no longer fire") } if !sub.CurrentPeriodEnd.Equal(period) { t.Errorf("current_period_end = %v, want %v unchanged", sub.CurrentPeriodEnd, period) @@ -766,12 +766,12 @@ func TestPaystackInvoiceSuccessIgnored(t *testing.T) { } sub, _ := h.Store.GetSubscription(user.ID) if sub.Plan != "free" { - t.Errorf("plan = %q, want free — a paid invoice must not create a subscription", sub.Plan) + t.Errorf("plan = %q, want free. A paid invoice must not create a subscription", sub.Plan) } } -// A foreign card on NGN pricing is recorded and flagged, never refused — -// blocking a real customer after charging them is worse than the mispricing. +// A foreign card on NGN pricing is recorded and flagged, never refused. +// Blocking a real customer after charging them is worse than the mispricing. func TestVerifyPaystack_ForeignCardOnNgnPricingIsAllowedNotBlocked(t *testing.T) { h, user := newBillingTestHandler(t, verifyBodyFrom("success", "buyer@example.com", testPlanMonthly, monthlyKobo, monthlyKobo, "US")) @@ -781,7 +781,7 @@ func TestVerifyPaystack_ForeignCardOnNgnPricingIsAllowedNotBlocked(t *testing.T) `{"reference":"T_ref","plan":"pro","interval":"month"}`)) if rec.Code != http.StatusOK { - t.Fatalf("got %d, want 200 — a foreign card must not be refused: %s", + t.Fatalf("got %d, want 200. A foreign card must not be refused: %s", rec.Code, rec.Body.String()) } sub, _ := h.Store.GetSubscription(user.ID) @@ -802,14 +802,14 @@ func TestVerifyPaystack_NonReusablePaymentIsGrantedButNotRecurring(t *testing.T) `{"reference":"T_ref","plan":"pro","interval":"month"}`)) if rec.Code != http.StatusOK { - t.Fatalf("got %d, want 200 — they paid, they get the period: %s", rec.Code, rec.Body.String()) + t.Fatalf("got %d, want 200. They paid, they get the period: %s", rec.Code, rec.Body.String()) } sub, _ := h.Store.GetSubscription(user.ID) if sub.Plan != "pro" || sub.Status != "active" { t.Errorf("plan/status = %s/%s, want pro/active", sub.Plan, sub.Status) } if sub.AutoRenews { - t.Error("auto_renews = true for a bank transfer — it cannot be charged again") + t.Error("auto_renews = true for a bank transfer. It cannot be charged again") } } @@ -862,7 +862,7 @@ func TestPaystackWebhookPreservesAutoRenews(t *testing.T) { } sub, _ := h.Store.GetSubscription(user.ID) if sub.AutoRenews { - t.Error("a webhook flipped auto_renews to true — it cannot see the authorization") + t.Error("a webhook flipped auto_renews to true. It cannot see the authorization") } } @@ -890,14 +890,14 @@ func TestVerifyPaystack_ReplayingAReferenceDoesNotApplyTwice(t *testing.T) { for i := 2; i <= 4; i++ { if rec := post(); rec.Code != http.StatusOK { - t.Fatalf("attempt %d: got %d, want 200 — a retry must not lock the customer out: %s", + t.Fatalf("attempt %d: got %d, want 200. A retry must not lock the customer out: %s", i, rec.Code, rec.Body.String()) } } after, _ := h.Store.GetSubscription(user.ID) if !after.CurrentPeriodEnd.Equal(firstEnd) { - t.Errorf("current_period_end moved from %v to %v — replaying a reference granted extra time", + t.Errorf("current_period_end moved from %v to %v. Replaying a reference granted extra time", firstEnd, after.CurrentPeriodEnd) } } @@ -938,7 +938,7 @@ func TestVerifyPaystack_RenewalExtendsFromTheCurrentExpiry(t *testing.T) { } } -// An expired period must not be carried forward — that would backdate the +// An expired period must not be carried forward. That would backdate the // renewal to a date already gone. func TestVerifyPaystack_RenewalAfterExpiryStartsFromNow(t *testing.T) { h, user := newBillingTestHandler(t, diff --git a/internal/handler/health.go b/internal/handler/health.go index 67cc2c2..c8148f3 100644 --- a/internal/handler/health.go +++ b/internal/handler/health.go @@ -38,8 +38,8 @@ func (h *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store") // monitors must never get a cached result if err := h.Store.Ping(); err != nil { - // Deliberately don't leak err.Error() in the response — - // internal error details have no business being public. + // Deliberately don't leak err.Error() in the response. + // Internal error details have no business being public. // Full detail goes to the server log only. resp.Status = "degraded" resp.Database = "error" diff --git a/internal/handler/me.go b/internal/handler/me.go index dafab2e..b9b3490 100644 --- a/internal/handler/me.go +++ b/internal/handler/me.go @@ -10,7 +10,7 @@ import ( ) // MeHandler returns the authenticated user's identity, plan, and limits. -// Works with both JWTs and API tokens — the CLI uses it for `hookdrop whoami` +// Works with both JWTs and API tokens. The CLI uses it for `hookdrop whoami` // and to validate a token at login. type MeHandler struct { Store *store.Store diff --git a/internal/handler/replay.go b/internal/handler/replay.go index eb747ee..7d6f434 100644 --- a/internal/handler/replay.go +++ b/internal/handler/replay.go @@ -44,7 +44,7 @@ func (h *ReplayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // Ownership check on the captured request's session/endpoint — foreign + // Ownership check on the captured request's session/endpoint. Foreign // requests return the same 404 as missing ones. user := middleware.GetUser(r) if _, ok := h.Store.ResolveIdentifierForUser(original.SessionID, user.ID); !ok { diff --git a/internal/handler/requests.go b/internal/handler/requests.go index eb6f0d2..6a2c228 100644 --- a/internal/handler/requests.go +++ b/internal/handler/requests.go @@ -24,7 +24,7 @@ func (h *RequestsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Resolve slug/endpoint/session to the canonical ID requests are stored - // under, and enforce ownership — foreign resources return the same 404 as + // under, and enforce ownership. Foreign resources return the same 404 as // missing ones. user := middleware.GetUser(r) sessionID, ok := h.Store.ResolveIdentifierForUser(identifier, user.ID) diff --git a/internal/handler/sse.go b/internal/handler/sse.go index b0b7599..4394a7f 100644 --- a/internal/handler/sse.go +++ b/internal/handler/sse.go @@ -23,7 +23,7 @@ func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { identifier = strings.Trim(identifier, "/") // Resolve slug/endpoint/session to the canonical ID the broadcaster keys - // by, and enforce ownership — foreign resources look identical to missing + // by, and enforce ownership. Foreign resources look identical to missing // ones (404, no existence leak). user := middleware.GetUser(r) sessionID, ok := h.Store.ResolveIdentifierForUser(identifier, user.ID) @@ -38,7 +38,7 @@ func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Connection", "keep-alive") w.Header().Set("X-Accel-Buffering", "no") // tells nginx: don't buffer this - // 3. Flush support — needed to push chunks immediately + // 3. Flush support. Needed to push chunks immediately flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "streaming not supported", http.StatusInternalServerError) @@ -48,7 +48,7 @@ func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 4. Register this browser tab as a client client := &sse.Client{ SessionID: sessionID, - Send: make(chan []byte, 32), // buffered — absorbs short bursts + Send: make(chan []byte, 32), // buffered. Absorbs short bursts } h.Broadcaster.Register(client) defer h.Broadcaster.Deregister(client) @@ -57,11 +57,11 @@ func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "event: connected\ndata: {\"session_id\":\"%s\"}\n\n", sessionID) flusher.Flush() - // 6. Keep-alive ticker — browsers disconnect if nothing arrives for ~30s + // 6. Keep-alive ticker. Browsers disconnect if nothing arrives for ~30s ticker := time.NewTicker(20 * time.Second) defer ticker.Stop() - // 7. Main loop — wait for events or disconnection + // 7. Main loop. Wait for events or disconnection for { select { case payload := <-client.Send: @@ -70,7 +70,7 @@ func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { flusher.Flush() case <-ticker.C: - // SSE comment — keeps the connection alive, browsers ignore it + // SSE comment. Keeps the connection alive, browsers ignore it fmt.Fprintf(w, ": keepalive\n\n") flusher.Flush() diff --git a/internal/handler/tokens.go b/internal/handler/tokens.go index 551da52..20c54c1 100644 --- a/internal/handler/tokens.go +++ b/internal/handler/tokens.go @@ -54,7 +54,7 @@ func (h *TokensHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *TokensHandler) create(w http.ResponseWriter, r *http.Request, userID string) { if h.MintLimiter != nil && !h.MintLimiter.Allow(userID) { w.Header().Set("Retry-After", "3600") - http.Error(w, "too many tokens created — try again later", http.StatusTooManyRequests) + http.Error(w, "too many tokens created. Try again later", http.StatusTooManyRequests) return } diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index d9bc5df..91438f0 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -14,7 +14,7 @@ type contextKey string const UserContextKey contextKey = "user" -// Auth methods — token-management routes are JWT-only so a leaked API token +// Auth methods. Token-management routes are JWT-only so a leaked API token // cannot mint or revoke tokens. const ( AuthMethodJWT = "jwt" diff --git a/internal/middleware/cors.go b/internal/middleware/cors.go index f59b93a..677bc55 100644 --- a/internal/middleware/cors.go +++ b/internal/middleware/cors.go @@ -8,10 +8,10 @@ import ( func CORS(next http.Handler, allowedOrigins string) http.Handler { if allowedOrigins == "" { - log.Fatal("CORS: ALLOWED_ORIGIN is not set — refusing to start with open CORS") + log.Fatal("CORS: ALLOWED_ORIGIN is not set. Refusing to start with open CORS") } - // Build a lookup map — supports comma-separated list of origins + // Build a lookup map. Supports comma-separated list of origins // e.g. "https://hookdrop.app,https://www.hookdrop.app" allowed := make(map[string]bool) for _, origin := range strings.Split(allowedOrigins, ",") { @@ -36,7 +36,7 @@ func CORS(next http.Handler, allowedOrigins string) http.Handler { log.Printf("CORS: rejected origin %q (allowed: %v)", origin, allowedOrigins) } - // Always handle preflight — even for rejected origins, return 204 + // Always handle preflight. Even for rejected origins, return 204 if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) diff --git a/internal/models/request.go b/internal/models/request.go index 2c2c087..45a336b 100644 --- a/internal/models/request.go +++ b/internal/models/request.go @@ -85,8 +85,8 @@ type Subscription struct { CurrentPeriodEnd *time.Time `json:"current_period_end"` TrialEnd *time.Time `json:"trial_end"` CancelAtPeriodEnd bool `json:"cancel_at_period_end"` - // AutoRenews is false when the payment method cannot be charged again — - // a Paystack bank transfer, for example. Such a subscription is a single + // AutoRenews is false when the payment method cannot be charged again. + // A Paystack bank transfer, for example. Such a subscription is a single // paid period that will simply expire, and the UI must say so. AutoRenews bool `json:"auto_renews"` Currency string `json:"currency"` @@ -107,8 +107,8 @@ type BillingEvent struct { EventType string Payload string // EventKey is the SHA-256 of the raw payload. Neither provider sends a - // unique event ID, but retries are byte-identical — they have to be, since - // the signature is computed over the raw body — so the hash is stable + // unique event ID, but retries are byte-identical. They have to be, since + // the signature is computed over the raw body, so the hash is stable // across them. EventKey string // EventAt is the provider's own timestamp for the event, used to reject diff --git a/internal/replay/engine.go b/internal/replay/engine.go index e8bb988..ffba924 100644 --- a/internal/replay/engine.go +++ b/internal/replay/engine.go @@ -20,7 +20,7 @@ func NewEngine() *Engine { return &Engine{ client: &http.Client{ Timeout: 30 * time.Second, - // Don't follow redirects — return them as-is so the + // Don't follow redirects. Return them as-is so the // developer sees exactly what their server responded CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse @@ -35,7 +35,7 @@ func (e *Engine) Replay( replayReq *models.ReplayRequest, ) (*models.ReplayResponse, error) { - // 1. Determine body — use override if provided, else original + // 1. Determine body. Use override if provided, else original bodyBytes := original.Body if replayReq.Body != "" { bodyBytes = []byte(replayReq.Body) @@ -80,7 +80,7 @@ func (e *Engine) Replay( } defer resp.Body.Close() - // 7. Read response body — cap at 1MB + // 7. Read response body. Cap at 1MB respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { return nil, fmt.Errorf("read response: %w", err) diff --git a/internal/session/session.go b/internal/session/session.go index 137f306..a1d136e 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -21,7 +21,7 @@ func NewManager(s *store.Store) *Manager { func (m *Manager) CreateSession(userID string) (*models.Session, error) { session := &models.Session{ - ID: uuid.NewString()[:8], // short ID — easier to read in URLs + ID: uuid.NewString()[:8], // short ID. Easier to read in URLs UserID: userID, CreatedAt: time.Now().UTC(), ExpiresAt: time.Now().UTC().Add(DefaultTTL), diff --git a/internal/sse/broadcaster.go b/internal/sse/broadcaster.go index 92b431c..41a5602 100644 --- a/internal/sse/broadcaster.go +++ b/internal/sse/broadcaster.go @@ -56,7 +56,7 @@ func (b *Broadcaster) Broadcast(sessionID string, req *models.CapturedRequest) { clients, ok := b.clients[sessionID] if !ok || len(clients) == 0 { - return // no one is watching — that's fine + return // no one is watching. That's fine } payload, err := json.Marshal(req) @@ -70,7 +70,7 @@ func (b *Broadcaster) Broadcast(sessionID string, req *models.CapturedRequest) { case client.Send <- payload: // delivered default: - // client's channel is full — it's too slow, skip it + // client's channel is full. It's too slow, skip it // it will catch up via the REST history endpoint log.Printf("SSE client too slow, dropping event for session %s", sessionID) } diff --git a/internal/store/api_tokens.go b/internal/store/api_tokens.go index 689741f..fed9003 100644 --- a/internal/store/api_tokens.go +++ b/internal/store/api_tokens.go @@ -16,7 +16,7 @@ func (s *Store) CreateAPIToken(t *models.APIToken) error { return err } -// GetAPITokenByHash returns the token only if it is active — revoked or +// GetAPITokenByHash returns the token only if it is active. Revoked or // expired tokens are filtered out in SQL so callers can't misuse them. func (s *Store) GetAPITokenByHash(hash string) (*models.APIToken, error) { t := &models.APIToken{} @@ -85,7 +85,7 @@ func (s *Store) RevokeAllAPITokens(userID string) error { } // TouchAPIToken records usage, throttled to one write per 5 minutes per -// token via the WHERE clause — no in-memory state needed. +// token via the WHERE clause. No in-memory state needed. func (s *Store) TouchAPIToken(id string, now time.Time) error { _, err := s.db.Exec( `UPDATE api_tokens SET last_used_at = ? diff --git a/internal/store/store.go b/internal/store/store.go index 3bfd7ab..8d8ce8c 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -26,7 +26,7 @@ func (s *Store) Ping() error { return fmt.Errorf("connection: %w", err) } - // A real query catches issues Ping() alone misses — locked file, + // A real query catches issues Ping() alone misses. Locked file, // corrupted schema, disk full. SELECT 1 against sqlite_master is // the lightest possible real query. var result int @@ -212,7 +212,7 @@ func (s *Store) migrate() error { return err } - // Safe column additions — idempotent, safe to run on every startup + // Safe column additions. Idempotent, safe to run on every startup migrations := []struct { table string column string @@ -490,7 +490,7 @@ func (s *Store) ConsumeMagicLink(token string) (*models.User, error) { return nil, fmt.Errorf("token expired") } - // Mark as consumed — one-time use + // Mark as consumed. One-time use _, err = s.db.Exec( `UPDATE magic_links SET used = 1 WHERE id = ?`, link.ID, ) @@ -590,8 +590,8 @@ func (s *Store) IdentifierExists(id string) bool { // ResolveIdentifierForUser resolves a slug, endpoint ID, or session ID to the // canonical identifier requests are stored/broadcast under, and checks the -// user may access it. ok=false covers both "not found" and "not yours" — -// callers must respond 404 so foreign resources are indistinguishable from +// user may access it. ok=false covers both "not found" and "not yours". +// Callers must respond 404 so foreign resources are indistinguishable from // missing ones (same convention as the secrets handler). func (s *Store) ResolveIdentifierForUser(identifier, userID string) (string, bool) { if ep, err := s.GetEndpointBySlug(identifier); err == nil && ep != nil { @@ -740,7 +740,7 @@ func (s *Store) GetSubscription(userID string) (*models.Subscription, error) { // - Auto-expiry check ───────────────────────────────────────────────── // If the user cancelled and the period has passed, treat them as free // without waiting for a Paystack webhook that may never arrive. - // This is a read-time check only — nothing is written to the database. + // This is a read-time check only. Nothing is written to the database. // The webhook will eventually persist the correct state when it fires. if sub.CancelAtPeriodEnd && sub.CurrentPeriodEnd != nil && @@ -768,7 +768,7 @@ func (s *Store) GetSubscription(userID string) (*models.Subscription, error) { const BillingEventRetention = 90 * 24 * time.Hour // ErrDuplicateBillingEvent means this exact webhook payload has been seen -// before — the provider retried a delivery we already have. +// before. The provider retried a delivery we already have. var ErrDuplicateBillingEvent = errors.New("duplicate billing event") // RecordBillingEvent stores an inbound webhook before it is processed. @@ -896,7 +896,7 @@ func (s *Store) DeleteOldBillingEvents() (int64, error) { // subscription ID. Used to recover the local user when a webhook arrives // without custom_data.user_id. // -// Unlike GetSubscription this returns (nil, nil) when there is no row — the +// Unlike GetSubscription this returns (nil, nil) when there is no row. The // caller needs to tell "not found" apart from "free plan". func (s *Store) GetSubscriptionByProviderSubID(providerSubID string) (*models.Subscription, error) { return s.subscriptionBy("provider_sub_id", providerSubID) @@ -929,7 +929,7 @@ func (s *Store) ListPaystackSubscriptionsNeedingRepair() ([]*models.Subscription WHERE provider = 'paystack' AND COALESCE(provider_sub_id,'') NOT LIKE 'SUB\_%' ESCAPE '\' -- Already reconciled. A row marked non-recurring will never gain a - -- SUB_ code, because there is no subscription to find — without this + -- SUB_ code, because there is no subscription to find. Without this -- it is reported as outstanding on every run, for ever. AND COALESCE(auto_renews,1) = 1 ORDER BY created_at`) @@ -988,7 +988,7 @@ func (s *Store) MarkSubscriptionNonRecurring(id string) error { } // subscriptionBy fetches one subscription by an indexed provider column. -// column is never caller-supplied — it comes from the two wrappers above. +// column is never caller-supplied. It comes from the two wrappers above. func (s *Store) subscriptionBy(column, value string) (*models.Subscription, error) { if value == "" { return nil, nil diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 7de5975..bc2ec3f 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -221,7 +221,7 @@ func TestLatestProcessedEventAt(t *testing.T) { t.Fatalf("record: %v", err) } if _, found, err := s.LatestProcessedEventAt("lemonsqueezy", "sub-1"); err != nil || found { - t.Fatalf("found = %t (err %v), want false — nothing is processed yet", found, err) + t.Fatalf("found = %t (err %v), want false. Nothing is processed yet", found, err) } if err := s.RecordBillingEvent(billingEvent("key-old", "sub-1", older)); err != nil { @@ -266,7 +266,7 @@ func TestDeleteOldBillingEvents(t *testing.T) { t.Fatalf("delete: %v", err) } if n != 1 { - t.Fatalf("deleted %d, want 1 — only the out-of-window row", n) + t.Fatalf("deleted %d, want 1. Only the out-of-window row", n) } // The fresh row survives, so its key still collides. diff --git a/internal/verify/verify.go b/internal/verify/verify.go index 24cbd1c..d29681b 100644 --- a/internal/verify/verify.go +++ b/internal/verify/verify.go @@ -80,7 +80,7 @@ func verifyStripe(req *models.CapturedRequest, secret string) Result { return Result{Status: "failed", Provider: provider, Reason: "malformed Stripe-Signature header"} } - // Reject timestamps older than 5 minutes — replay attack protection + // Reject timestamps older than 5 minutes. Replay attack protection ts, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return Result{Status: "failed", Provider: provider, Reason: "invalid timestamp"} @@ -89,7 +89,7 @@ func verifyStripe(req *models.CapturedRequest, secret string) Result { return Result{ Status: "failed", Provider: provider, - Reason: fmt.Sprintf("timestamp too old (%ds) — possible replay attack", time.Now().Unix()-ts), + Reason: fmt.Sprintf("timestamp too old (%ds). Possible replay attack", time.Now().Unix()-ts), } } @@ -117,7 +117,7 @@ func verifyPaystack(req *models.CapturedRequest, secret string) Result { expected := computeHMAC(sha512.New, string(req.Body), secret) match := hmac.Equal([]byte(expected), []byte(sigHeader)) - // TEMP DEBUG — remove once the Paystack verification-failure bug is resolved. + // TEMP DEBUG. Remove once the Paystack verification-failure bug is resolved. // Never logs the secret itself, only its length. log.Printf("[paystack-verify-debug] secret_len=%d received_sig=%s computed_sig=%s match=%t", len(secret), sigHeader, expected, match) diff --git a/main.go b/main.go index b605488..5e583c8 100644 --- a/main.go +++ b/main.go @@ -88,7 +88,7 @@ func main() { "LEMONSQUEEZY_VARIANT_PRO_ANNUAL": lsVariantAnnual, }) if lsTestMode { - log.Printf("billing: LEMONSQUEEZY_TEST_MODE=true — checkouts are test mode, no real charges") + log.Printf("billing: LEMONSQUEEZY_TEST_MODE=true. Checkouts are test mode, no real charges") } // ── Paystack config (African users) @@ -178,7 +178,7 @@ func main() { mux.Handle("/health", healthHandler) authRateLimit := middleware.AuthIPRateLimit(authIPLimiter) - // Auth — public + // Auth. Public mux.HandleFunc("/auth/request", authRateLimit(authHandler.RequestLink)) mux.HandleFunc("/auth/verify", authHandler.VerifyLink) @@ -189,7 +189,7 @@ func main() { mux.Handle("/billing/verify-paystack", requireAuth(http.HandlerFunc(billingHandler.VerifyPaystack))) - // Billing — authenticated + // Billing. Authenticated mux.Handle("/billing/subscription", requireAuth(http.HandlerFunc(billingHandler.GetSubscription))) mux.Handle("/billing/checkout", @@ -199,20 +199,20 @@ func main() { mux.Handle("/billing/cancel", requireAuth(http.HandlerFunc(billingHandler.CancelSubscription))) - // Account + API tokens — authenticated (token management is JWT-only, + // Account + API tokens. Authenticated (token management is JWT-only, // enforced inside TokensHandler) mux.Handle("/me", requireAuth(&handler.MeHandler{Store: st})) mux.Handle("/tokens", requireAuth(&handler.TokensHandler{Store: st, MintLimiter: tokenMintLimiter})) mux.Handle("/tokens/", requireAuth(&handler.TokensHandler{Store: st, MintLimiter: tokenMintLimiter})) - // Core — authenticated + // Core. Authenticated mux.Handle("/sessions", requireAuth(&handler.SessionHandler{Manager: mgr})) mux.Handle("/requests/", requireAuth(&handler.RequestsHandler{Store: st})) mux.Handle("/replay", requireAuth(&handler.ReplayHandler{Store: st, Engine: replayEngine})) mux.Handle("/events/", requireAuth(&handler.SSEHandler{Broadcaster: broadcaster, Store: st})) mux.Handle("/endpoints", requireAuth(&handler.EndpointsHandler{Store: st})) - // Endpoints + secrets — authenticated, routed by path shape + // Endpoints + secrets. Authenticated, routed by path shape mux.Handle("/endpoints/", requireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.Path, "/secrets") { secretsHandler.ServeHTTP(w, r) @@ -221,7 +221,7 @@ func main() { } }))) - // Inbox — public but rate limited + // Inbox. Public but rate limited mux.Handle("/i/", inboxLimiter(&handler.InboxHandler{ Store: st, Broadcast: broadcaster.Broadcast, diff --git a/repair.go b/repair.go index e68c444..08b397d 100644 --- a/repair.go +++ b/repair.go @@ -16,7 +16,7 @@ import ( // // Those rows predate subscription.create being applied correctly. They cannot // be cancelled through the Paystack API, and their current_period_end was -// derived at signup and never refreshed by a renewal — so once access is gated +// derived at signup and never refreshed by a renewal, so once access is gated // on the period, they read as lapsed. // // Dry run by default: nothing is written unless apply is true. @@ -31,11 +31,11 @@ func repairPaystackSubscriptions( } if len(rows) == 0 { - log.Printf("repair: nothing to do — every Paystack row already holds a subscription code") + log.Printf("repair: nothing to do. Every Paystack row already holds a subscription code") return nil } - mode := "DRY RUN — nothing will be written" + mode := "DRY RUN. Nothing will be written" if apply { mode = "APPLYING changes" } @@ -101,9 +101,9 @@ func repairPaystackSubscriptions( } if apply { - log.Printf("repair: done — %d repaired, %d failed", repaired, failed) + log.Printf("repair: done. %d repaired, %d failed", repaired, failed) } else { - log.Printf("repair: dry run complete — %d would be repaired, %d could not be resolved", len(rows)-failed, failed) + log.Printf("repair: dry run complete. %d would be repaired, %d could not be resolved", len(rows)-failed, failed) log.Printf("repair: re-run with -apply to write these changes") } if failed > 0 { diff --git a/ui/src/components/billing/ManageSubscriptionPanel.tsx b/ui/src/components/billing/ManageSubscriptionPanel.tsx index d84c0d5..5048c2e 100644 --- a/ui/src/components/billing/ManageSubscriptionPanel.tsx +++ b/ui/src/components/billing/ManageSubscriptionPanel.tsx @@ -12,7 +12,7 @@ export function ManageSubscriptionPanel({ const { subscription, isTrialing, refetch } = useBilling() // A subscription paid for with a method that cannot be charged again has - // nothing to cancel — Paystack holds no subscription for it. Offering the + // nothing to cancel. Paystack holds no subscription for it. Offering the // button would mark the row cancelled for no reason and log that the // provider was never notified. const willNotRenew = subscription?.auto_renews === false @@ -111,20 +111,19 @@ export function ManageSubscriptionPanel({ {subscription?.cancel_at_period_end && (

- Cancellation scheduled — access until {renewalDate} + Cancellation scheduled. Access continues until {renewalDate}

)} {willNotRenew && (

- This is a one-off payment, so there is nothing to cancel — access - simply ends on {renewalDate}. Renew from the billing page to extend - it. + This is a one-off payment, so there is nothing to cancel. Access + ends on {renewalDate}. Renew from the billing page to extend it.

)} - {/* Cancel — hidden once cancelled, and for one-off payments */} + {/* Cancel. Hidden once cancelled, and for one-off payments */} {!subscription?.cancel_at_period_end && !willNotRenew && (

14-day free trial · Cancel anytime

diff --git a/ui/src/components/detail/BodyViewer.tsx b/ui/src/components/detail/BodyViewer.tsx index 0d7c563..8fc1f47 100644 --- a/ui/src/components/detail/BodyViewer.tsx +++ b/ui/src/components/detail/BodyViewer.tsx @@ -1,8 +1,8 @@ import { safeParseBody, tryPrettyPrint, tokenizeJson, type JsonTokenType } from '../../lib/json' import { CopyButton } from '../ui/CopyButton' -// Understated syntax colours tuned to the warm indigo/violet palette — -// distinct roles, not a generic green-on-black terminal. +// Understated syntax colours tuned to the warm indigo/violet palette. +// Distinct roles, not a generic green-on-black terminal. const TOKEN_CLASS: Record = { key: 'text-indigo-300', string: 'text-emerald-300', @@ -40,7 +40,7 @@ export function BodyViewer({ body }: { body: string }) { return (
- {/* Copy button — appears on hover, tucked in corner */} + {/* Copy button. Appears on hover, tucked in corner */}
diff --git a/ui/src/components/detail/MetaBar.tsx b/ui/src/components/detail/MetaBar.tsx index 4901580..1e99c06 100644 --- a/ui/src/components/detail/MetaBar.tsx +++ b/ui/src/components/detail/MetaBar.tsx @@ -8,7 +8,7 @@ export function MetaBar({ request }: { request: CapturedRequest }) {
- {/* Primary — dominates: method + verification */} + {/* Primary. Dominates: method + verification */}
@@ -19,7 +19,7 @@ export function MetaBar({ request }: { request: CapturedRequest }) { />
- {/* Secondary — recedes: IP, time, size, ID */} + {/* Secondary. Recedes: IP, time, size, ID */}
{request.remote_ip} · diff --git a/ui/src/components/feed/EmptyFeed.tsx b/ui/src/components/feed/EmptyFeed.tsx index 10d888d..048679a 100644 --- a/ui/src/components/feed/EmptyFeed.tsx +++ b/ui/src/components/feed/EmptyFeed.tsx @@ -5,7 +5,7 @@ export function EmptyFeed() { {/* Quick test snippet */}
diff --git a/ui/src/components/feed/FilterBar.tsx b/ui/src/components/feed/FilterBar.tsx index 10747f7..8bac517 100644 --- a/ui/src/components/feed/FilterBar.tsx +++ b/ui/src/components/feed/FilterBar.tsx @@ -52,7 +52,7 @@ export function FilterBar({ filters, onChange, resultCount, totalCount }: Props) const newFilters = { ...filters, [key]: isToggleOn ? value : '' } onChange(newFilters) if (isToggleOn) { - posthog?.capture('filter_applied', { // add — only on activation + posthog?.capture('filter_applied', { // add. Only on activation filter_type: key, value, }) @@ -123,7 +123,7 @@ export function FilterBar({ filters, onChange, resultCount, totalCount }: Props)
- {/* Active filter chips — always visible when filters applied (even when collapsed) */} + {/* Active filter chips. Always visible when filters applied (even when collapsed) */} {hasActiveChips && (
{filters.method && ( diff --git a/ui/src/components/feed/RequestItem.tsx b/ui/src/components/feed/RequestItem.tsx index c639462..b3efbbb 100644 --- a/ui/src/components/feed/RequestItem.tsx +++ b/ui/src/components/feed/RequestItem.tsx @@ -18,7 +18,7 @@ export function RequestItem({ request, selected, isNew = false, onClick }: Props selected ? 'bg-indigo-500/[0.07]' : 'hover:bg-surface' } ${isNew ? 'animate-arrive' : ''}`} > - {/* Left accent bar — always present, no layout shift */} + {/* Left accent bar. Always present, no layout shift */} - {/* Tabs — hidden on mobile where MobileTabBar takes over this role */} + {/* Tabs. Hidden on mobile where MobileTabBar takes over this role */}
{(['session', 'endpoints'] as Tab[]).map(t => (