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
943,828
Web platform — TypeScript, React, Prisma, CSS
iOS
79,607
Swift + SwiftUI lines
Android
3,117
Kotlin + Jetpack Compose lines
All platforms
1,026,552
Web + iOS + Android combined (51.0× the King James Bible)
Characters written
40.17M
40,168,559 total characters
Updates pushed
2,265
exact commit count on main
Current version
v1.22.65
build 2265 · d89cabc
Database models
420
across 47 schema files — most SaaS platforms have 20–50
API endpoints
1,052
individually routed — Stripe's public API has ~400
Translated strings
90,960
every string, in 24 languages
System permutations
10^316
2^1,052 endpoint combinations — more than atoms in the observable universe (10^80)
Project age
9mo 16d
since Dec 14, 2025
Pre-AI dev hours
34.2K hrs
1,026,552 lines ÷ 30 LOC/hr — equivalent to 16.5 years (senior engineer, no AI)
With-AI dev hours
8.6K hrs
4× AI productivity multiplier (2024–2026 studies) — equivalent to 1,069 days
Equivalent firm cost
$8,963,404
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 2 of 40 · 51–100 of 2,000 fetched
Wednesday, September 23, 2026
15 updates pushed
Updateaccount1:25 PM · ZRosserMcIntosh
deletion removes saved addresses and strips PII from orders
Account deletion anonymized the User but a user update never cascades, so
saved Address rows survived, and orders kept email, phone and full
shipping/billing addresses. It now deletes the addresses and, on every order
of the user, sets email to the same anonymized address, clears phone, gift
message and customer notes, and reduces addresses to country, state and a
3-character postal prefix (tax/accounting). Amounts, items, dates and status
are kept. Repeat calls reuse the anonymized email (idempotent); the audit
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.
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
2 updates 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.
Translated captions render word-by-word with stable keys; revisions cross-fade in place instead of erasing and retyping. Incremental translator no longer shrinks/duplicates at chunk seams; finalized lines are pre-seeded from the live caption (admin + guest rooms).
Guest join stores the invite email on the participant; Virgil gets a participant roster in context and resolves first names to invited addresses before asking. Send-email failures surface the provider's reason (Brevo 401 IP allowlist) instead of a bare HTTP 500.
Single invoice currency; line-item inputs show the matching symbol. Removed the client-only USD->BRL overwrite (its rate fetch was CSP blocked and always fell back to 5.75); replaced with a reference-only converter via /api/exchange-rates.
"Pay now" or "Email me a reminder in 1/3/7 days" on the public invoice page and email; sent by the existing daily invoice-reminders cron. Hand-applied migration adds three nullable SalesInvoice columns; reads tolerate the columns being absent until it is applied.
Approve/Send action no longer rendered twice in the Virgil panel.
Brevo auth/IP-allowlist errors are classified as a system outage: the prospect stays queued, the tick stops, a red banner shows the outage, and an internal alert fires after two consecutive failures.
Unsubscribe, hard bounce and spam complaint now suppress the prospect and cancel queued follow-ups; suppression is checked before every send.
Inbox-poll heartbeat shown in the page header; opens/clicks recorded per prospect behind PROSPECTING_TRACK_OPENS.
QR intake for identity-safe agreements, in 24 languages
The intake form's words moved out of two-language maps hardcoded in intake-form.tsx and intake.ts and into messages/*.json under Signing.intake, the same catalog the rest of the signing surface already used. Translated into all 24 locales; the completeness test now covers them.
Stored intakefields labels no longer win over the catalog. A persisted label is frozen in the language it was typed in, so the admin was saving Portuguese labels that every scanner saw regardless of their language. IntakeFieldSpec .label is now an optional operator OVERRIDE, and the admin persists structure only — key, order, type, requiredness.
formatSignedAt asks Intl for the time-zone name instead of printing a hardcoded "horário de Brasília", which was right in one language of 24.
Enabled for the bilateral NDA (/sign/nda), contractor W-9 (/sign/w9), and employee W-4 (/sign/w4), alongside the existing image release.
Deliberately NOT enabled for custom-order-deposit, engagement-ring-order, lab-grown-diamond-order, wholesale-agreement, growth-services-retainer, natural-stone-release, bench-jeweler and casting-house. The intake form asks for every placeholder that is not an operator constant, and on those the remaining placeholders are prices, deposit percentages and payment terms — a QR link would let the customer set their own deposit and sign it.
The NDA drops {effective_date} (nobody is there to type it, and a counterparty dating their own NDA can backdate it) and fixes {purpose} as a constant, since a counterparty writing their own purpose narrows the confidentiality obligation they are signing.
Intake now always collects an email, even when the wording never prints one. The completion email, the signer's copy, and the duplicate-submission guard all read signeremail, and deriving fields purely from placeholders left it null for most templates — the signer walked away with nothing.
Friday, August 28, 2026
6 updates pushed
Perf10:32 PM · ZRosserMcIntosh
run functions in pdx1, next to the Supabase us-west-2 database
Every function was executing in gru1 (project default) while Postgres
lives in Oregon: a single SELECT 1 cost ~0.5 s, POST /api/cart (~14
sequential queries) ~7.6 s, /api/products 3.5 s, /api/search 2 s.
Route-level preferredRegion exports (translate routes, iad1) still win.
Meta signal health, GA4 read side, email UTMs, internal-traffic guard
Meta Marketing API client + /admin/marketing/signals page and API (dataset quality, audience sync); scripts meta:signals, meta:audiences
GA4 Data API client + pnpm ga:report; setup docs for both
src/lib/marketing/utm: withUtm / addUtmToEmailHtml / toUtmCampaign, applied to campaign tests, system-email previews/tests, welcome gift, abandoned cart, wedding, credit, gift-card and winback emails; /go short links forward incoming UTMs
layout: GTM / gtag / Meta / X pixels and PageViewTracker never load on internal routes (server + inline early-return guard)
.env.example: GA4 + Meta Marketing vars; drop the unused AIza key
Thursday, August 27, 2026
1 update pushed
Refactorprospecting12:03 AM · ZRosserMcIntosh
split the console into tabs, badge the standard
The board stacked six things on one page: autopilot, the build standard, a
7-day send schedule, the revenue goal, the AI cost panel, and the prospect
list. The list is the only one touched daily, and it sat below several
screens of machinery that gets adjusted weekly at most.
Four tabs now, with the mode banner deliberately left ABOVE them — whether
the board is live or in test mode must never be one click away:
Pipeline the book (default — the only surface used daily)
Autopilot the runner + everything the cron will send in 7 days
Standard which generation made each lead's artifacts
Numbers the goal, and what the AI is costing
Nothing was deleted or rewritten; the existing blocks are wrapped in tab
panels. The per-prospect tabs inside the list (prospect-tabs.tsx) are
untouched — those bucket by next action and are a different axis.
StandardPanel now reports its rollup upward so the Standard tab carries a
count of what is below the current bar, and carries nothing when the answer
is zero. It stays mounted (hidden) rather than being unmounted with its tab,
so the badge is accurate on page load instead of only after you visit the
tab it exists to send you to.
On removing the re-analyze / process-again buttons: they already disappear
on their own. Every one is guarded — the urgent block on
wouldSendBelowStandard > 0, the defect table on byIssue.length > 0, the lead
list on prospects.length > 0 — and a clean book renders "Every lead in play
is at the current standard" instead. They are visible right now because the
book is not clean (167 with no deck, 12 needing fresh research).
Worth keeping them for the same reason: OUTREACH_GENERATION is 4 and the
ladder has already moved 1 → 2 → 3 → 4, with generation.ts explicitly
Wednesday, August 26, 2026
6 updates pushed
Featureprospecting8:36 PM · ZRosserMcIntosh
named offer URLs + a full-record page per jeweler
Apostrophes are stripped, not separated. "Grissom's" -> "grissoms", not "grissom-s", which looks broken on the one word that is theirs.
Ligatures and stroked letters are mapped before NFD, which does not decompose them: "ætla" was becoming "tla". A URL carrying the wrong name is worse than one carrying none. Names that yield nothing usable (e.g. purely non-Latin) keep slug NULL and resolve by token — correct, not a gap.
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.
Seed a bilateral (mutual) NDA template, modeled on the Riva Jewelry Mfg. agreement, rewritten for October Skies Benediction LLC DBA KATURA 1999 and governed by South Carolina law like every other Katura agreement.
Seeded as a migration, not through prisma/seed-signature-templates.ts, so it lands on deploy instead of waiting for a hand-run script; ON CONFLICT DO NOTHING keeps it idempotent and never overwrites operator edits.
requirescountersign is true — a bilateral NDA is not executed until Katura signs back, and the PDF chrome already renders the countersignature block.
intakeenabled is false: this is an operator-filled document, not a walk-up QR flow. company_address is the state-level address; the mailing address of record is residential and must not ship to suppliers.
Hand-written titles and descriptions across ~70 marketing and education routes, replacing hero headings reused as <title> and 200-char blurbs.
Strip the trailing "| Katura" that translated seo.title strings carried, so the layout suffix is the only brand mention instead of doubling it.
Blog SEO copy rules + article author module, with tests; noindex helper and structured-data/config cleanups; supporting docs and scripts under docs/seo/ and scripts/seo/.
STT survives socket drops; remote captions persist once
host STT died at 20:37 and stayed dead 17 min; because the remote-caption handler is gated on subtitlesOn, one Deepgram disconnect blacked out everyone's captions, translation and Virgil
a guest's Deepgram socket died silently at 21:04 — the guest client had no reconnect, no watchdog and no UI reset
every admin viewer persisted every guest line → 2–4 copies each
KeepAlive only fired while muted, so a starved-but-unmuted pipeline (swapped mic track, suspended AudioContext) hit Deepgram's 1011
both clients: KeepAlive whenever no audio frame was sent in 4 s; unexpected close → cleanup + reconnect with backoff (3 tries) before flipping subtitles off; re-entrancy guard around startStt
guest client: 30 s watchdog + visibilitychange reconnect (mirrors admin); sends browser locale as preferredLanguage on join
admin client: persist remote finals before the subtitlesOn gate; after auto-start retries fail, arm a one-shot gesture listener so the next tap starts STT (browsers block mic/AudioContext without one)
transcript route: INSERT … WHERE NOT EXISTS same speaker+text in the last 15 s
guest join route: honour body.preferredLanguage instead of 'en'
the goal panel was overstating recurring effort ~20x
Follow-ups were inverted AND double-counted: the per-PROSPECT reply lift was applied to the per-EMAIL rate, then divided by touches again, so enabling them made total sends go DOWN. They cut prospects 1,916->1,064 and RAISE sends 1,916->3,724. The multiplier is now clamped at expectedTouches, making that invariant structural rather than lucky.
sendingDays ignored the follow-up sub-lane. DAILY_SEND_CAP is a TOTAL cap (send.ts:225 gates on an unfiltered sendsToday()), so config.ts:77 is wrong about its own enforcement; FOLLOWUP_DAILY_CAP is a second, tighter budget. With follow-ups on that lane binds: 333 days, not 47.
"4 retainers at a blended $2,500" was arithmetically false ($10,000, not $8,400) and $2,500 was a JSX literal. The blend is now derived from the live proposal mix — measured $2,165.98, since 161 proposals pitch $2,000/mo and 80 pitch $2,500/mo, and none pitch the tiers the old comment named.
AI spend divided by DECKS (345) and multiplied by PROSPECTS (241).
22 of the 52 displayed hours were approvals AUTO_APPROVE_OUTREACH already performs. Real: ~28h, of which 19h is calls.
$5,000 of onboarding fees was omitted; the waiver needs a form submission cold outreach never creates.
NaN leaked into nine fields whenever a funnel rate was zero (Infinity-Infinity, Infinity*0) and would have rendered as the literal text "NaN". Rates clamp to [0,1]; products go through mul(). A test now walks every numeric leaf across 12 degenerate inputs x both toggles.
provenance stamped "measured, n=241" onto whatever it was handed, including hand-typed overrides. Sample sizes now travel with the values; absent them it reports guess, n=0.
deliveryRate read "90% — measured, n=24" beside a note saying 23 of 24 delivered, which is 95.8%. It is a deliberate haircut and now says so.
The feasibility line quoted the total cap even when the follow-up lane produced the ceiling.
PROSPECTING_MONTHLY_GOAL_CENTS="$8400" evaluated to NaN and poisoned everything downstream; || only catches unset, not malformed.
pin templates, and a present mode for holding the phone up
Fullscreen so the browser chrome goes too, and a wake lock so the screen does not dim mid-scan. Both best-effort — neither failing should stop the code being shown.
Sized to the SHORTER viewport edge, so it fills a portrait phone and still fits when someone turns it sideways mid-scan. Measured in the state initialiser rather than an effect, so the code does not visibly resize under a camera that is already aiming at it.
A white plate under the code regardless of theme. Scanners read dark-on-light and an inverted QR fails on a meaningful share of older cameras — this is the one place the dark aesthetic gives way.
The URL in text underneath, because a camera that will not focus is a real failure at a counter and someone has to be able to type it.
Rendered through a portal, so no ancestor's transform or overflow can clip a fullscreen overlay.
a Katura shell, and a wordmark that survives dark mode
It inherits `color`, so one prefers-color-scheme rule makes it near-black on paper and warm white on dark. An image cannot adapt — you need two files and a <picture> half the clients ignore.
Most email clients block remote images by default, so a wordmark that is an image is a wordmark most recipients never see. The first thing in the message would be a broken-image box.
Crisp at every density, with no @2x asset to maintain.
the signed PDF never rendered — Playfair has no italic
The style drops italic. Playfair at 24pt upright still reads as a signature, and only registered variants may be used.
A test renders REAL PDFs — typed, drawn, countersigned, and an English one with a payment block — and asserts on the %PDF- bytes. A unit test over the style object could not have caught this: the font resolves during layout, inside the render.
Fail-soft no longer means invisible. A PDF failure is recorded on the document's audit trail, which is the one place its whole story is already read. That needed a new enum value — the event column is a Postgres ENUM, so writing an unknown value is rejected, and the insert is itself inside a .catch(). Without the migration the new visibility would have been swallowed by the same silence it exists to break.
clear the migration backlog — 13 unrecorded down to zero
11 had been applied by hand months ago and simply never recorded. Marked applied, never re-run.
20260825_add_cart_abandonment had genuinely never run. Applied. Its table is load-bearing rather than analytical: the recovery cron derives its frequency cap, reminder ceiling and interval spacing from those rows, and every query is wrapped in a catch returning empty — so while the table was absent all three guards failed OPEN.
20260820_audit_hardening was PARTIALLY applied, which object existence alone cannot show. Its CREATE OR REPLACE FUNCTION had been hand-applied with the escape hatch spelled app.audit_purge, while the migration file and all three modes of scripts/backfill-audit-log.ts use app.audit_maintenance. The append-only trigger was therefore rejecting every backfill: geo had run on 0 of 1720 rows, and the tamper-evidence hash chain was sealed on 25 of 1720. The escape hatch had been inert since the day it was hand-applied. Production's function body now matches this repo; enforcement was never off, and no rows were touched.
26 MeetingParticipant rows have isHost = true with role = 'guest'. The participant-creation path has been writing the legacy flag without the engine role since 2026-08-13. Note this is also why 20260811_add_meeting_admission_roles must be resolved and never applied — its backfill UPDATE would promote all 26 to host.
Address carries a second, undeclared BEFORE UPDATE trigger from scripts/db-migrations/applied-sql/user-fields/add-address-table.sql, so every update runs the same function twice. Harmless, but owned by nothing.
migrations never applied themselves — make the drift loud instead of silent
vercel.json sets `buildCommand: "pnpm build"`, which OVERRIDES the `vercel-build` script in package.json. `prisma migrate deploy` lives in that script, so it has never run on a Vercel build. The build log confirms it: prisma generate runs via postinstall, migrate deploy does not appear at all.
The workflow's push job targeted the `staging` environment, whose secrets have never been set, so every run failed with "You must provide a nonempty direct URL" — a Prisma validation error, before it ever reached the database. Nobody was notified.
Its production job only ran on manual workflow_dispatch, and had not been dispatched.