Every change we ship to katura1999.com — features, fixes, security patches, the lot. Pulled straight from our private GitHub repository so you can see exactly what was built and when.
By the numbers
Lines of code
997,181
Web platform — TypeScript, React, Prisma, CSS
iOS
79,607
Swift + SwiftUI lines
Android
3,117
Kotlin + Jetpack Compose lines
All platforms
1,079,905
Web + iOS + Android combined (54.2× the King James Bible)
Characters written
42.68M
42,680,782 total characters
Updates pushed
2,265
exact commit count on main
Current version
v1.22.65
build 2265 · b9d76c9
Database models
423
across 47 schema files — most SaaS platforms have 20–50
API endpoints
1,074
individually routed — Stripe's public API has ~400
Translated strings
106,176
every string, in 24 languages
System permutations
10^323
2^1,074 endpoint combinations — more than atoms in the observable universe (10^80)
Project age
11mo 1d
since Dec 14, 2025
Pre-AI dev hours
36.0K hrs
1,079,905 lines ÷ 30 LOC/hr — equivalent to 17.3 years (senior engineer, no AI)
With-AI dev hours
9.0K hrs
4× AI productivity multiplier (2024–2026 studies) — equivalent to 1,125 days
Equivalent firm cost
$9,472,807
live ticker · Katura rate: $60 USD/hr
Hours estimated from source line count at 30 LOC/hr (industry benchmark for production-quality TypeScript/React without AI assistance), with a 4× multiplier for AI-assisted development per published 2024–2026 enterprise studies. Equivalent Firm Cost uses a $250/hr loaded billable rate reflecting a premium engineering firm building enterprise-grade SaaS — and ticks up live, because the project is still being actively built.
Commit history
2,265 updates pushed
Showing page 9 of 40 · 401–450 of 2,000 fetched
Thursday, September 24, 2026
13 updates pushed
3:57 PM · ZRosserMcIntosh
feat/app-controls into feat/app-swarm
Integrates the feat/app-controls builder branch from the app swarm (iOS Controls,
postal-code-first addresses, Pix/BNPL/Apple Pay checkout) so the three
/admin/ios (listed under Core): hero slide editor with real photos or video per piece/collection, caption, sub-caption and autoplay; Featured picker with editable title; per-piece video mode (autoplay, tap to play, photo only); per-collection cover picker; shelf order and visibility; phone-frame preview; published timestamp. en/pt/es copy.
Publishing is a server action on the page itself, so the feature adds only two routes (the page and the public endpoint) against Vercel's route cap. Managers and owners publish (requireKaturaAdmin admits EMPLOYEE by default); employees with Products access can view. Saves are audit-logged with old and new values, and a save over someone else's newer publish is refused instead of silently overwriting it.
GET /api/app/home resolves the stored ids against the live catalog on every request: only ACTIVE public pieces (no custom-ring orders, no Trunk Show specials), photos/videos must still belong to their item, deleted collections drop out, new collections join the shelf order. Edge-cached for a minute; returns configured:false on failure so the app keeps its built-in Home. The resolver is pure and unit-tested.
Pix for the iOS app, BNPL for US orders, Apple Pay quotes
PATCH /api/create-payment-intent confirms an existing checkout PaymentIntent with Pix server-side (CPF/CNPJ validated, client secret as the credential) and returns the QR / copia-e-cola code. Same order, same webhook. Codes expire after 30 min, inside the reservation sweeper's in-flight grace, so the sweeper never cancels a code still being paid.
POST now returns the intent's resolved payment_method_types, so the app offers Pix only when Stripe really made it available (dashboard setting, BRL, under the 3,000 USD Pix cap).
POST { quoteOnly: true } prices the bag for a partial address with no side effects (no customer, order, hold or superseding). The iOS Apple Pay sheet uses it to show the same shipping/tax/total the charge will use. Own rate-limit bucket so re-quotes never eat the checkout allowance.
House allowlist: opt-in BNPL (affirm, klarna, afterpay_clearpay) for USD product orders. They settle to the house in full on success, so they pass the settled-funds rule; gift-card purchases do not opt in (providers prohibit stored value). Stripe enforces each provider's amount window.
Webhook records payment_intent.processing as Payment PROCESSING.
Web Payment Element lists Apple Pay / Google Pay first, adds Klarna to the USD order.
Sign in with Apple and Google for customers, web + iOS
One resolver (src/lib/oauth/identity.ts, rules in identity-decision.ts) for web and native: existing identity -> sign in; signed-in Connect -> link; verified email match -> auto-link; else create a CUSTOMER with emailVerified and the register side effects (no newsletter, no WELCOME100). Staff / platform / non-zero-tenant / SaaS-owner / support-IT accounts are never signed in, created or linked.
NextAuth: Google and Apple providers register only when their env vars exist; the signIn callback runs the resolver before Auth.js's second getUserByAccount, and the adapter's createUser/linkAccount now refuse to run. Apple client secret is an ES256 JWT signed from the .p8. state/nonce/PKCE (and callback-url) cookies are SameSite=None in production for Apple's form_post; the session cookie stays Lax.
POST /api/auth/mobile/oauth: verifies Apple/Google ID tokens (jose, remote JWKS, iss/aud/nonce), exchanges Apple's authorization code for a refresh token, and mints the same NextAuth session cookie a browser sign-in gets.
/api/account/sign-in-methods (GET, DELETE /{provider}, POST link-intent); change-password lets passwordless accounts set a first password; account deletion and unlinking revoke Apple tokens.
/api/app/storefront gains auth flags; login/register get provider buttons and settings a Sign-in methods card (en + pt strings).
Customer OAuth errors bounce from /employee-login back to the page the customer started on.
ring sizes start at 5; stop defaulting and re-flagging one-of-one
Customer size pickers (PDP, design-your-ring, engagement design, ring builder, app config) start at US 5. Staff preference records keep the full range because they capture real measurements.
'One of one' meant 'one in stock', not 'cannot be made again'. New products default to false (form, admin API, schema default + migration), and the admin update route no longer sets it to true whenever an edit omits the field — that would have re-flagged every product on save.
Add src/lib/promotions/resolve-promo-code.ts, the single resolver used by both the public preview (/api/promotions/validate) and the charge path, so the previewed discount is the charged discount. Enforces active window, usage/per-customer limits, new-customer, tags, product/collection targeting, exclusions, min purchase, max discount.
Checkout tries promo codes first and falls through to gift cards on 404; shows the applied code with Remove and a discount line; sends promoCode to payment.
create-payment-intent re-resolves server-side, subtracts the discount, taxes the discounted amount, zeroes shipping for FREE_SHIPPING, caps gift balance against the discounted total, records it on the order and in Stripe metadata. Ineligible codes are a hard 400.
Stripe webhook records PromotionUsage and bumps currentUsage only on payment_intent.succeeded (idempotent per order).
Gift-card redeem checks existence before the auth gate so guests who mistype see "Invalid code", not "Sign in"; both endpoints rate-limited.
WELCOME100 is a config row for the signup welcome gift (already paid as gift balance) and is excluded from the coupon path.
New checkoutPage i18n keys added to all 24 locale files (shallow merge).
stop the block rewinding on fragment merge; iOS save; reachable settings
Both rooms: capped at 70vh (desktop min(34rem, 100vh-8rem)) and scrolling internally.
Host: the three tallest sections — Preferred spoken language, Font, Recording — are now collapsed <details> disclosures. Native, so no state and no re-render path to get wrong.
Translation is now one switch instead of a language list. ON means smart mode: everyone else's speech in YOUR own language, which is what almost everyone wants. Choosing a different target still exists, demoted to a link inside Settings — a language list is the wrong thing to put in front of someone mid-call.
direct in-call file sharing; make captions print without reprinting or jumping
IncrementalTranslator re-appended the PRE-COMMIT tail the instant a chunk landed, so the chunk's own words printed twice and the duplicate was deleted a beat later. Fired every ~4 stable words, both rooms, every language pair. lastTailTrans is now cleared at the commit that takes ownership of those words. Reproduced by a regression test whose tails resolve out of order — the existing fixture resolved them synchronously, which forced the one interleaving where the bug cannot appear.
Friday, September 4, 2026
1 update pushed
Updatejewel-vox2:50 PM · ZRosserMcIntosh
unbreak article saving; add image sizing and a live preview
Saving has been impossible for any article without a category. The form sends
categoryId: "" for "No category" (its default), the route passed that straight
to Prisma, and Postgres rejected the row on Article_categoryId_fkey — 23503,
verified against the live database. The writer saw only "Failed to create
article". Both routes now normalise "" to null for categoryId and publishedAt,
and answer failures with the actual reason: a P2003 names the field, a P2002
names the clash, and zod issues come back per-field instead of a bare
"Validation failed". Every one of the 275 existing articles was written by the
cron through a different code path, which is why this went unnoticed.
Image sizing: @tiptap/extension-image has no size control, and the public
renderer forced prose-img:w-full over anything an author set — so images could
only ever be full width. Images now carry a width attribute written as an
INLINE style, which outranks the class, and a size bar (Small/Medium/Large/Full)
appears whenever an image is selected. w-full remains the default, so existing
articles are untouched; block+mx-auto centres the ones deliberately narrowed.
Both `style` and `width` are already on the sanitizer allowlist, so the size
survives publishing.
Preview: a Write/Preview toggle renders the draft through ArticleContent — the
same component the live article page uses — so images, spacing and typography
are checked as they will ship. The editor stays mounted behind `hidden` so
toggling does not discard undo history. Word count added alongside characters.
use Stripe's decimal rules for Stripe amounts, not the display flag
STRIPE_ZERO_DECIMAL_CURRENCIES: bif clp djf gnf jpy kmf krw mga pyg rwf vnd vuv xaf xof xpf (UGX, though on Stripe's list, is represented two-decimal with "00" for backwards compatibility).
STRIPE_WHOLE_UNIT_CURRENCIES: huf twd isk ugx — ×100, whole units only (amount divisible by 100).
Everything else (incl. IDR) ×100. toSmallestCurrencyUnit / fromSmallestCurrencyUnit delegate to it; `zeroDecimal` in config stays for display and rounding only.
create-payment-intent (via toSmallestCurrencyUnit) and the webhook inverse.
/api/checkout/order/[orderId]: also charged the USD Order.total under the order's foreign currency code; now charges chargedAmount in that currency, else the USD total in USD.
SalesInvoice public payment, quick-invoice, signatures (PI + Checkout), whose "cents" are major × 100 in every currency: converted at the Stripe boundary (internalCentsToStripeUnits), and Stripe amounts recorded back into SalesInvoice (webhook invoice.paid / sales_invoice_public, sync-stripe) via stripeUnitsToInternalCents.
Casting order pay + refund, and order refunds (a USD refund on a non-USD order is now the same proportion of chargedAmount, in that currency). USD-only paths (Checkout Session, mobile PaymentIntent, gift cards, deep diagnostic, SaaS billing) are unchanged. The iOS app never computes Stripe units (PaymentSheet uses the server's PaymentIntent).
stop advertising a smart-link CAPI ViewContent that no longer fires
"CAPI ViewContent on click" badges removed (list + detail)
the detail page's ON/OFF toggle for fireViewContent is replaced by a note saying what the link does now (click cookies + click log) and that ViewContent comes once from the landing product page
"CAPI fire rate" KPI removed (it would read 0% by design); the list view shows avg time on page instead, now that durationMs is recorded
recent clicks no longer flag new clicks "no CAPI"; old clicks that did send one show "server VC (legacy)" The fireViewContent column and API field are kept, just no longer surfaced.
social-proof line reads the real ProductView columns, never blocks
one query for the 30-day and 7-day distinct non-bot viewer counts, scoped to tenant zero, excluding rows written by local dev servers (pageUrl on localhost) so test traffic cannot inflate the number
2 s timeout; any failure or malformed id returns the empty result with 200 (the component renders nothing) and logs a safeLog.warn
CDN-cacheable (s-maxage 300 with a line, 60 without)
copy rules moved to buildSocialProof (unit-tested); every number shown is a real distinct-visitor count, "100+" is a true lower bound, and nothing is shown when there are no real viewers
identify server events and persist the funnel first-party
the client now sends its first-party visitor id (katura_visitor_id), referrer channel and first-touch UTMs with every /api/tracking/* call and with POST /api/cart (getTrackingContext)
ViewContent / AddToCart / InitiateCheckout / Purchase CAPI calls add the signed-in user's email and id via auth(), plus the visitor id, as em / external_id
view-content → PRODUCT_VIEW
POST /api/cart and add-to-cart (quantity increases) → CART_ADD; the two paths are disjoint, so nothing is counted twice
purchase → CHECKOUT_COMPLETE, built from the Order (USD total, paid status), at most one per order
initiate-checkout writes nothing: /api/analytics/checkout-start already records the deduplicated CHECKOUT_START the admin funnel reads; it now also stores visitor id, channel and UTMs No migration: the enum already has PRODUCT_VIEW, CART_ADD, CHECKOUT_START and CHECKOUT_COMPLETE, so the Meta-style names map onto those.
drop GTM and send pixels, gtag and CAPI from the storefront only
layout: tags render only for a tracked request host, and each inline bootstrap re-checks location.hostname
sendCapiEvent drops any event whose sourceHost (request host) or event_source_url host is not tracked — covers the /api/tracking/* routes, the cart route, the marketing Lead and the Stripe webhook
handle canceled intents, keep Order.total in USD, zero-decimal amounts
payment_intent.canceled was subscribed but unhandled, so Payment rows stayed PENDING. It now marks the Payment CANCELLED, cancels the order if it is still PENDING and releases its stock hold exactly once.
payment_intent.succeeded overwrote Order.total (USD) with the charged foreign-currency amount (KAT-MT38YEVO: 192.17 BRL as the USD total). A non-USD charge now goes to chargedAmount/currency, matching what create-payment-intent writes; Payment.amount stays USD (metadata.amountUSD). Receipt/ops emails, Brevo LAST_ORDER_TOTAL and User.totalSpent use the USD total. The Purchase CAPI event (value + charged currency, event_source_url) is unchanged. Historical rows are not rewritten.
centsToDecimal divided zero-decimal currencies (JPY/KRW/CLP/PYG/HUF/IDR/VND) by 100; it now uses the currency config via src/lib/currency/stripe-amount.ts, the exact inverse of toSmallestCurrencyUnit.
succeeded is idempotent on Stripe retries (no second gift-balance deduction, stock decrement or email).
A payment that succeeds after its hold was released re-deducts stock; if that oversells, the order is noted, ops is alerted, and it is not auto-exported to ShipStation.
payment_failed only cancels/releases a still-PENDING order.
expire one-of-one stock holds after 20 minutes without payment
A PaymentIntent-backed checkout hold lives 20 min from order creation.
PDP, cart add, Meta/Google feeds, bundles and wholesale templates treat expired holds as released (read-only groupBy, only when reserved > 0).
The checkout routes release expired holds on the cart's products, and the customer's own previous PENDING checkout (same email or user), before the stock check, so stacking is gone.
Releasing cancels the Stripe PaymentIntent FIRST (a stale form can't be paid against released stock); processing / succeeded intents, and requires_action (3DS, Pix QR) under 60 min, keep their hold.
PENDING -> CANCELLED is a conditional update and stock is released only by the winner, never below zero, on the same inventory row reserve uses.
cleanup-orders now runs every 15 min (never */5) as the backstop; the legacy 4 h / 30 min path is unchanged for orders without a PaymentIntent and no longer "releases" stock for manual orders that never reserved it.
The mobile PaymentIntent and Checkout Session routes reserved on EVERY inventory row of a product; they now reserve on one.
put name, price and a working Add to Bag on a phone's first screen
adds to bag through useAddToCart (same action, analytics, redirect);
reads "Select size" when a ring size is required and unchosen (or ring variants are still loading); a tap scrolls to and opens the picker, which gets a highlight and a "requires a size" line. The main button does the same instead of a toast;
links to the consultation for INQUIRY_ONLY/SHOWCASE as well as price-on-request (it used to offer Add to Bag for those);
stays disabled when out of stock. It still hides while the in-page CTA is visible (now tracked on the button row, not the whole actions block) and marks <html data-sticky-buy-bar> so the WhatsApp widget steps aside; ProductActions shows an in-flow WhatsApp link on phones instead.
stop the WhatsApp widget covering buy buttons on phones
Tooltip is closed by default; it appears at most once per session, on desktop only, after 30 s and 600 px of scrolling, and hides itself.
On phones the button steps aside on any page that mounts a sticky buy bar (html[data-sticky-buy-bar], globals.css) and below lg on /cart and /checkout, where the pay action is sticky.
New WhatsAppInlineLink for pages to keep an in-flow chat entry.
The guest hard-coded `translated: undefined` on EVERY remote interim (~4/sec) while the replacement was throttled to 700ms, so the translated line was erased and retyped several times a second.
The host committed its final but held the live interim for a 150ms merge window, printing the tail twice and then chopping it in one frame.
sentenceCase() ran on the confirmed half and the live half separately, capitalising the tail's first word — which flipped back to lowercase the moment the final merged it. Deepgram's 150ms endpointing splits most utterances without terminal punctuation, so this fired on nearly every final. It now cases the concatenation once.
Smart-format punctuation made stable words new tokens to the morph diff. wordKey() folds case and edge punctuation, so a word that only gained a comma swaps glyphs instead of blur-crossfading.
instantRevisions was applied to a string that is half confirmed and half live; the two halves need opposite policies, so they are now two spans.
A single mis-detected language re-parented the live text between blocks and back. Block continuity now needs 3 agreeing interims; segLang still drives translation and the persisted row.
The guest's `slice(-20)` could evict the head of a speaker group, changing its React key and re-fading every word of unchanged text.