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,966
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 12 of 40 Β· 551β600 of 2,000 fetched
Friday, August 21, 2026
18 updates pushed
9:40 PM Β· ZRosserMcIntosh
proposal pdf: layout budgets β the six-page contract survives any payload
A real proposal (Hamra Jewelers) rendered 9 pages with three stranded
fragments: two audit tiles alone on a page, the method box alone on a
page, and the plan header divorced from its phase columns β because
verbose writer output overflowed the fixed one-section-per-page design
and react-pdf pushes whole keep-together blocks forward. Every
variable-length field is now clamped at sentence/word boundaries by
LAYOUT_BUDGET in pdf-data.ts, the plan gets height-aware fitting
(estimated column lines, tail items dropped only in the worst case),
and a maximum-verbosity regression test pins the render at exactly six
pages. The writer prompt now demands tight plan bullets so future
generations avoid truncation at the source. Stored proposals re-render
correctly through the same builder β no regeneration needed.
DELETE /api/admin/users/[id] deleted any account, ran auditLog.deleteMany({ userId }) first, and logged nothing. Anyone could erase a colleague's entire history and leave no trace of having done it.
PUT /api/admin/permissions granted admin page permissions. Its role check only constrained MANAGER, so an EMPLOYEE fell straight through and could grant themselves anything.
category counts came from a raw query with no tenant predicate, so the summary cards counted every tenant on the platform
"Previous" reused a forward cursor against a skip:1 read and landed one row off from page three onward; paging is bidirectional now, with id as a tiebreaker so same-millisecond entries cannot repeat or vanish
every keystroke refetched, running an unbounded GROUP BY over the whole table; search is debounced and filter options moved to a cached endpoint
geolocation resolved on every read, with up to 3s of ip-api.com rate-limit sleeps on the request path; it resolves once at write time into a persistent IpGeoCache now, and reads never leave the process
indexes now match the query shape (tenant + newest-first)
fail-open rate limiting + full i18n of the checkout flow
Production has no Upstash Redis, and rate-limit.ts failed CLOSED, so every
endpoint using it 429'd on first touch: contact/register/newsletter since
2026-02-22, and checkout since 2026-08-18 when create-payment-intent gained
Wednesday, August 19, 2026
13 updates pushed
Security6:10 AM Β· ZRosserMcIntosh
site-wide remediation β reviewed and verified
P0 fixes verified in code: client-controlled mobile-checkout shipping/ gift-balance removed (server flat-rate table; zero-value orders rejected), tenant API/MCP routes bound to the authenticated tenant, fail-closed auth (isActive + tenant membership, session invalidation), centralized SaaS builder authorization, Mailgun/EasyPost/ShipEngine/ Brevo/Slack/Telegram/log-drain webhook verification, inbound-email HTML sanitization (parser-based allowlist + sandboxed iframe), edge-level cron gate with header-only credentials, upload MIME/magic-byte hardening, production rate limiting fails closed without Redis.
Focused suites 50/50 green; scoped tsc over all 72 changed/new TS files (the gate the original run couldn't finish): every error except one maps to PRE-EXISTING debt in unchanged lines/imported files; the one genuine new-code error (JWK typing in shipengine-signature.ts) fixed here, verifier tests still 2/2.
Founder/platform accounts confirmed compatible with fail-closed auth (tenant_katura, active, platform role intact). The single would-be locked-out staff account (CAD manager) was backfilled to tenant_katura in production per the ledger's own manual action.
Remaining dangerouslySetInnerHTML in compose-dialog renders the admin's own signature (self-authored), not untrusted inbound content.
contract-grade in-house e-sign β the PandaDoc replacement
Tuesday, August 18, 2026
1 update pushed
11:54 PM Β· ZRosserMcIntosh
honest stats, real search, atomic money math, working cache busts
The "Processing" stat card read stats.processing β a key the API never sends (it sends in_production) β so the card rendered blank forever. The client state now mirrors the API payload and shows In Production.
"Shopify Orders" / "Katura Website" counted the current PAGE's 20 rows while labelled as totals. Now a real groupBy over the table.
The search box filtered the 20 already-loaded rows client-side while the API's search went unused β a search that looked global missed everything not on the current page. Now debounced to the server; filters and search reset to page 1 (staying on page 3 of a one-page filter rendered empty).
total = subtotal + shipping + tax β discount is now enforced whenever any component changes. Before: patching shipping/tax/discount alone never recomputed total (silent drift), and patching WITH items rebuilt total treating omitted shipping/tax as zero (wiping real values).
Item replacement ran deleteMany then a separate update β a failure between them left the order stripped of every line item. Now one transaction (verified: the extended tenant client supports $transaction on Prisma 6).
Invalid status values now 400 instead of 500ing inside Prisma.
invalidateCache is exact-key DEL, but list cache keys include the full filter combination β so PATCH's hardcoded trio only ever busted the unfiltered page-1 view, and cancel/mark-paid/ship/refund/DELETE busted nothing: every action served the old list for the TTL ("the button didn't work"). New SCAN-based invalidateCachePrefix, called from all six writers.
file every ad-hoc migration by domain and date-stamp them
scripts/db-migrations/applied-sql/<domain>/ is the single home for hand-run SQL: 222 files across 32 domain folders, with a generated README index listing every file by date
scripts/db-migrations/runners/ holds the 21 one-off Node/TS/shell runners (zero references anywhere, safe to relocate)
supabase/storage-policies/ holds the 3 bucket-policy files that were loose at the supabase/ root
every archived .sql now opens with its authoring date as a comment (-- 9 MAY 2026), taken from the filename timestamp where one exists, otherwise from the commit that introduced the file
prisma/migrations/<ts>_<name>/migration.sql β Prisma checksums these in _prisma_migrations; editing one breaks migrate deploy on Vercel. Added a README there explaining that, and why loose .sql must not go back in.
supabase/migrations/*.sql β the Supabase CLI owns that flat layout and every filename already carries its date.
docs/**/*.sql β documentation artifacts, referenced by name from many docs.
web dev section, colour press, $100 CAD credit, admin queue
New Websites & Web Development block, given its own full-width layout with the stack named (Next.js/React/TypeScript, Postgres/Prisma/Supabase, Stripe and Shopify/WooCommerce migrations, Vercel, Core Web Vitals, structured data, 24 locales). It's the one service where the buyer β or the buyer's nephew who "does computers" β wants the actual stack before they'll believe it. Added at page level rather than to SERVICES, which is organised around jewelry craft and also feeds the homepage and /services grids.
Press strip moved above the services grid and rendered in full colour.
Every service CTA ("Audit My AI Visibility", "Build My Brand", β¦) now links to #assessment instead of a service detail page. They were the only dead ends on the page β someone clicking those wants us to do it, not to read.
$100 new-client credit toward a first CAD design, under the price grid.
Removed the "Apply for a wholesale account" CTA and the whole "people who'd actually do the work" section.
Renders gallery moved to sit directly above "What it costs" (still hidden while its folder holds no images).
The assessment intro now makes the follow-on explicit: the report already says what to work on, so there's no discovery phase to pay for and we can start immediately.
Explicit data promise under the form: never sold, rented, or shared, used only to prepare the assessment, deleted on request.
admin/collections: fix per-keystroke focus loss; make the page campaign-ready
Real <form>: Enter submits, spinner while saving, no double-submit, disabled without a name
Client slug generator now mirrors the server's ("Silver & Pearls" -> silver-pearls, not silver--pearls), with a live URL preview and blur-time normalization
Featured is exclusive server-side: featuring one collection un-features the rest, so the /collections banner has one owner
revalidatePath on every mutation route: admin edits and curation go live immediately instead of waiting out the 30-min ISR window
Manage-products search matches metal/gemstone/collection name; 'Select all shown' bulk-select; 'N will move here / N will leave' summary before saving
Delete uses a styled confirmation dialog with piece-count-aware copy instead of the native confirm()
Featured badge overlays the card image; mosaic list gets a loading spinner; create dialog scrolls; deprecated onKeyPress replaced
narrow to services sellable today; add renders gallery + press
Removed the whole "approve your piece on a video call" section and the project-workspace early access section. The in-meeting tooling and the client workspace are how we WORK with clients, not something on sale, so they belong on the page only once they are. demos.tsx is kept, unrendered, for when that changes; the accuracy contract at the top of pitch-client says why.
Casting & Production dropped from the service grid, and Bridal Growth System from the price list. Both are filtered at the page level rather than deleted from SERVICES / OFFERS, because those catalogues also drive the homepage grid, /services, and the outbound proposal generator, where they still belong.
"Katura Wholesale β 7.77 Collection" shows as plain "Wholesale" with the 7.77 Collection called out in the body copy. Display-only override, so proposals keep the formal name.
Identity is now explicitly K99 Jeweler Services throughout β hero eyebrow, metadata, footer.
Service cards render from SERVICES in lib/k99/capabilities, which declares itself the single source of truth and forbids present-tense promises for anything "rolling_out". The status chip renders next to every card, so the page cannot drift from it. K99 Business Platform correctly shows "Rolling out"; the six sellable services show "Available now".
Prices render from OFFERS in lib/prospecting/config β the same productized catalogue already used in proposals sent to prospects, with real figures, scope and boundaries. Do not hand-write prices on the page; change them there and both stay in step.
The assessment deliverables are derived from the actual generator: evidence categories and sources from JewelerEvidence, findings / quickWins / plan90Day / independentFactCheck from GeneratedProposalV2, and the copy-ready asset + implementation + verify-after steps from ComplimentaryAction, which is gated so no brief ships without at least one verified free action. If the generator changes, change that list.
The four animated demos recreate software that ships today (live meeting translation, CAD shared in-call, in-call invoice + e-signature, and the project workspace using its real status values). Each is labelled an illustration rather than a live session.
piece previews in admin + campaign-grade public collection pages
every card now previews its actual pieces β first 8 live products as thumbnails (hover = name/price, click-through to the product) with an honest live count (the old count included drafts/archived)
assignment sheet: each product row shows metal Β· gemstone and, when the piece lives in another collection, an amber 'in <Collection> β will move here' warning (collectionId is a single FK; moves were silent before)
product search now matches metalType and gemstone ('pearl' finds pearls)
create/edit form exposes what the API always accepted: Featured toggle (labeled as the campaign-target switch) + SEO title/description
renders a real hero: collection name, curator's description, piece count, hero image with overlay when set (before: NO copy at all β an h1 over a bare grid, unfit for paid traffic)
Sterling Silver β the material collection, optimized to the max
queried by Product.metalType (NOT a DB Collection row β collectionId is a single FK, so a Collection would pull the silver Chasing Ring / Starfish / Pearl pieces OUT of their home collections; a material query lets every sterling piece live in both places)
localized metadata in all 24 locales (CATEGORY_SEO 'sterling-silver') + nav/footer/category copy in all 24 message files
ItemList + BreadcrumbList (via CategoryPage) PLUS CollectionPage schema and the first visible FAQ + FAQPage schema on any shop page
CategoryPage extended with a metalType filter mode (material categories span every jewelryType; take raised to 60 for them)
header nav, footer shop column, legacy siteConfig nav all link it
sitemap: /creations/sterling-silver at 0.8 weekly; STATIC_CONTENT_DATE bumped to 2026-08-20
/collections index: Sterling Silver mosaic tile (live images + count) linking to the landing page, included in the ItemList schema
llms.txt Shop section: Sterling Silver line, worded to disambiguate retail silver jewelry from the /precious-metals buying service
llms-full.txt: new '## Collections' section (all DB collections + the material collection) so models can recommend browsable pages, not just individual pieces
one Country control β full Central + South America currency coverage
Product/Collection JSON-LD priceCurrency is now truthfully USD β it was labeling the unconverted USD price with the locale's currency ($5,000 ring marked β¬5,000 for German visitors; Merchant Center mismatch)
additive state-wide discovery + physical mail as a first-class channel
discovery response now returns before/after prospect totals β the console proves every run is purely additive (discovery has never deleted, but the operator couldn't see that)
Entire-state mode: pick a state, the console sweeps its metros one search at a time (all 50 states + DC curated in metros.ts), each new prospect auto-assigned to the metro it was found under; per-metro progress log, partial-failure tolerant, board refreshes as results land
JewelerMailPiece model + SQL migration (supabase/migrations/20260819)
print edition PDF = the v2 proposal + a navy closing card page with the piece's unique QR ("We found something.")
landing page /proposal/[token] β "Prepared exclusively for <business>", call CTA + digital proposal; QR scans, views, and CTA clicks all recorded as ProspectingEvents (offline mail becomes an attributable channel)
lifecycle: DRAFT β submit (test_sk_ dry-runs free; live needs POSTGRID_LIVE_APPROVED="true", fails closed) β webhook tracks printing/ mailed/delivered; webhook treats payloads as hints and re-fetches every letter from the API before acting
delivery writes the timed follow-up call onto the prospect's nextStep ("delivered 47 minutes ago" call), exactly once; manual mark-mailed/ mark-delivered escape hatches for hand-assembled PRESENTATION/EXECUTIVE kits
same integrity gates as the stage-2 proposal email (v2 + fact-check + validity window); unlike email, physical may be a first touch by design
Send actually sends: the invite email fires on Send (fail-soft to the manual link), audited as EMAIL_SENT.
Real sealed PDF: @react-pdf/renderer document (body, typed/drawn signature, countersignature, E-SIGN consent recital, full audit trail) uploaded to the PRIVATE signatures bucket, with tamper-evidence: pdfsha256 of the exact bytes + HMAC seal (SIGNATURE_SEAL_SECRET or NEXTAUTH_SECRET) + a KS-XXXX-XXXX verification code printed in the footer. Public GET /api/signatures/verify/[code] lets anyone holding a copy authenticate it byte-for-byte β the thing "genius sign" can't do.
Completion emails to both parties, the signer's with the executed PDF attached and the paper-copy-on-request notice.
Template variables: the create form renders one input per {{placeholder}} and the server refuses any document with an unfilled blank (variablessnapshot recorded).
Countersigning end-to-end: signer signs -> COUNTERSIGN_REQUIRED -> admin notified -> countersign box on the detail page -> both-signature PDF regenerates -> both parties emailed. Re-signing blocked while pending.
E-SIGN consent: strengthened disclosure on the signing page; consent is a required server-side field recorded in the audit trail.
New template seeded (repo + prod): "Growth Services Agreement β 90-Day Retainer" with countersign required β matches the prospecting proposal's page-5 terms, ready for the first marketing client.
Dead 0-byte branding.ts removed; status/audit type unions aligned with the live DB enums; seed script ON CONFLICT fixed (id, not slug).
Feeds the previously-dead 14-signal seo-audit.ts: assessSeo() now runs on real fetched signals β the "free SEO audit" door-opener is finally backed by data, and its quick wins quote what was actually observed.
Absences are dated + scoped ("We did not identify X in our fetch on <date>"), never absolute; commerce-reality evidence deliberately feeds the "lacks e-commerce is contradicted" Luxor rule.
Refetch supersedes prior machine-captured rows (marked STALE, never deleted β historical proposals keep their sources); human-entered evidence untouched.
Optional PageSpeed Insights (mobile score + LCP) when PAGESPEED_API_KEY / GOOGLE_PAGESPEED_API_KEY is set; fail-soft.
3 new tests (fixture HTML through stubbed fetch): rich-store persona, bare-brochure absence phrasing, unreachable-site fail-soft. Full prospecting suite: 78/78.
/admin/analytics/conversions|traffic|orders rendered TWO stacked layouts: the (marketing) route group's layout was a near-verbatim copy of the outer one β its own context, its own module caches, its own KPI card grid, header, and timeframe picker, plus a second tab bar overlapping the outer eight. Every visit double-fetched the 21-query conversions endpoint and showed two "days" pickers that could disagree about the period on one screen. The inner layout is now a pass-through that re-exports useMarketingContext from the real provider; the three pages needed zero changes.
The hub nav is now grouped: Overview Β· Acquisition (conversions, traffic, campaigns, hero, affiliates) Β· Sales (orders, products, customers, reviews) Β· Team (team, time-tracking), rendered as a primary row plus the active group's sub-views. Every existing URL is preserved; hero, affiliates and time-tracking β pages that existed but appeared in no navigation at all β finally have a home.
The hub's period selector now lives in the URL (?days=) and every nav link carries it, so the window follows you across tabs, survives refresh, and is shareable. Previously each surface defaulted independently.
/admin/analytics/campaigns: smart links and QR codes in one sortable table (7d / 30d / all-time), each row clicking through to its operational page; cross-links to email campaigns and media performance. Comparing campaign assets previously meant flipping between two separate trees.
The Marketing sidebar entry "Conversions" was a duplicate pointer into the hub β removed; its keywords merged into the single Analytics entry.
Two dead /admin/stella/* links fixed (analytics layout, CRM marketing stub), same rot family as the Under Development drift.
Stella Marketing Hub dropped from Under Development: it is launched.
paper-archive: client matching, historical orders on the books, honest dates
Client matching: each card is checked against User accounts, CRM contacts, and other already-linked cards (email near-certain, phone strong, name token overlap). The review panel shows "Linked to customer Β· N orders" or states plainly "as far as we know, this is not currently a Katura client" with one-tap Create client record (CRM contact, source=PAPER_ARCHIVE). Linked clients flow into invoice promotion (customerType USER/CRM_CONTACT instead of ANONYMOUS).
Record as historical order: a CONFIRMED receipt becomes a real Order β the entity every revenue dashboard and the cap-table valuation compute from β dated to the CARD via customOrderDate, tagged source=PAPER_ARCHIVE (filterable like Shopify imports), created as DELIVERED history with no fulfillment/email side effects. Line items as written; deposit noted in internalNotes; synthetic archive+ email only when the card has none (and flagged).
Honest dates: recordDatePrecision (exact/~month/~year/unknown) set in review; order promotion requires at least a year so revenue periods stay defensible.
auth/profile: session-aware logins, self-service profile, es locale saves
Both /employee-login and /login showed the password form to people who were already signed in β while any direct /admin link sailed straight through β so signing in twice felt mandatory. Both pages now check the session server-side and redirect to the callbackUrl (or /admin, /account).
Settings β Profile rendered a self photo-upload widget for every employee, but the API it posts to was MANAGER/OWNER-only: a control the UI hands you that always 403s. Self-upload is now allowed; managing OTHERS' photos still requires manager/owner.
Even after an upload, the JWT pinned name/image from the moment of login ("reload to refresh session" does nothing for a JWT). The existing 5-minute role re-check now refreshes name/image too.
Nobody could change their OWN password while signed in β the PUT handled password only in the manage-others branch, so managers could set employee passwords but self-service meant the public forgot-password email flow. Own-profile password change added (min 8 chars) plus a Change Password section in Settings β Profile (en/pt/es).
Choosing EspaΓ±ol saved the cookie but the server preference 400'd: every adminLocale allowlist said ['en','pt'] while the admin ships es. All three allowlists now accept es, so the language survives a new device.
The provider declared `settings` TWICE (interface, en, and pt objects) β a small orphaned block shadowed the real one in the type, which is why every t.settings.* on the settings page has been a type error masked by ignoreBuildErrors. Duplicates removed (es had the same problem); ~10 pre-existing type errors gone.