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
751,820
across the entire repo (equivalent to 37.0 King James Bibles)
Characters written
29.13M
29,126,188 total characters
Updates pushed
1,573
exact commit count on main
Current version
v1.15.73
build 1573 · 6d58963
Database models
366
across 47 schema files — most SaaS platforms have 20–50
API endpoints
854
individually routed — Stripe's public API has ~400
Translated strings
79,872
every string, in 24 languages
System permutations
10^257
2^854 endpoint combinations — more than atoms in the observable universe (10^80)
Project age
4mo 29d
since Dec 14, 2025
Pre-AI dev hours
25.1K hrs
751,820 lines ÷ 30 LOC/hr — equivalent to 12.0 years (senior engineer, no AI)
With-AI dev hours
6.3K hrs
4× AI productivity multiplier (2024–2026 studies) — equivalent to 783 days
Equivalent firm cost
$6,479,051
live ticker · $60/hr ongoing
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.
Locks the admin viewport to 100vh so sidebar and main pane scroll independently. Composer now stays pinned to the bottom edge.
/api/admin/yen/chat now returns a helpful detail when Ollama is unreachable (e.g. on Vercel where there's no localhost:11434): 'Set YEN_PROVIDER=groq + GROQ_API_KEY (or openai + OPENAI_API_KEY)'
/admin/yen page surfaces the detail in the toast description.
Phase 1 Meetings — schedule video meetings from channel header
Zero config. Works today.
Phase 1.5 swap to Google Meet only requires editing src/lib/calendar/meet-provider.ts::createMeetingUrl — call-sites do not change.
ChatMessage now has metadata Json? and replyToId String? in the Prisma schema. These columns already existed in the DB (used by messages/route.ts raw SQL); the schema file was just out of sync. Added an idempotent migration prisma/add-chat-message-metadata.sql that ADDs the columns IF NOT EXISTS, safe to re-run.
comprehensive dark-mode pass — add dark: siblings to 62 light-only Tailwind classes across 28 admin files
Auto-fixed light-mode-only Tailwind utility classes that were causing black text on dark backgrounds in admin pages. Previous audit only matched className="..." attributes; this pass also catches strings inside cn()/clsx() ternaries and template literals.
Investigated @fsafasf (philbrewerton868@gmail.com): was an OWNER-role account created 2026-04-08 that never logged in (no sessions, no events, no chat history). Locked down: role demoted OWNER→CUSTOMER, isActive=false, sessions cleared. Only 2 OWNER accounts remain (Rosser, Lee) — both with recent valid logins.
handleSendMessage() now OPTIMISTIC: temp message appears instantly, input clears immediately, input refocuses. Server response swaps temp→real id. Failed sends marked with ⚠️ prefix for retry visibility. Kills the 3-sec send delay on /admin/messages.
Realtime INSERT subscription now skips own-user inserts (already rendered optimistically) and dedupes by id, eliminating the double-render that caused echo/delay.
Dropped the per-channel messageCount subquery from GET /channels — it was unused client-side (unreadCount was hardcoded 0) and was the main cause of the 3-sec page-load delay for users with many channels.
Channels endpoint now returns cm."lastReadAt" in each member row
MessageBubble renders ⏳ (pending) / ✓ (sent) / ✓✓ (read by someone else) on own messages. Computed client-side from channel members' lastReadAt.
Sidebar now splits channels into two sections with uppercase headers: · Direct Messages (1-on-1 DMs) · Channels (groups, projects, announcements)
Count badge per section. Empty states per section. Preserves search.
New src/lib/vera/providers.ts implements getVeraProvider() which resolves the active LLM from VERA_PROVIDER env var: ollama (default, free local) -> http://localhost:11434/v1 groq (free hosted) -> https://api.groq.com/openai/v1 openai (paid) -> https://api.openai.com/v1
chat/route.ts is now provider-agnostic. The OPENAI_API_KEY hard 503 is replaced with a per-provider validation; default Ollama needs no key.
stripImagePartsForTextOnly() converts image_url parts to text markers when the active provider is text-only (Ollama/Groq) so multimodal history doesn't 400 the request.
estimateCostCents() returns 0 for free providers; powers the upcoming cost-tracking dashboard. Persisted via existing VeraMessage.costCents column.
pingVeraProvider() health probe used by the new monitoring test.
New conversations now persist modelProvider + modelName so we can mix providers per-conversation later.
New synthetic-test 'Integration: Vera AI Provider' calls pingVeraProvider() at the configured baseUrl. Returns: pass -> provider reachable AND configured model in /models degraded -> provider reachable, model name not found in catalog fail -> provider unreachable / timed out Visible immediately on /admin/developer/health.
A full uptime-tracker (sparkline over last year) requires a new UptimeCheck Prisma model + cron + history API; documented as Phase 2 in the audit doc rather than shipping half-built.
Audited all admin/**/*.tsx — 12 offenders found, 7 real (orders page is the only high-traffic one). Fixed all 5 visible text-stone-{700,900} sites on /admin/orders with dark: siblings.
Full audit + remediation list lives in docs/ADMIN_AUDIT_2026-04-23.md.
New docs/ai/OPENCLAW_INTEGRATION_OPPORTUNITIES.md walks through all 17 experiments in /admin/openclaw and pins each to a specific file/component as the integration point. Includes:
Phase 2 (this month) — revenue briefing, health diagnose, support triage, store copywriting, content drafter
Phase 3 (this quarter)— sentry root-cause, deploy guard, outreach, trials, ads, referrals, trade-shows, seo, wholesale All zero-cost on Ollama/Groq, with hard-blocked pricing/refund decisions and write-access kept human-only.
Updatemessenger2:47 PM · ZRosserMcIntosh
instant Create Group close + colored @mentions; fix(orders): surface real sales-rep errors + optimistic UI; feat(admin): HelpTip component (EN/PT/ES)
optimistically updates the row immediately
surfaces the actual server error (status + details) in a toast so we can finally see WHY it fails when it does
re-syncs from the server on failure to undo the optimistic update
src/components/admin/HelpTip.tsx: a tiny, self-contained '?' icon popover with built-in EN / PT / ES support (auto-detects the NEXT_LOCALE cookie). Drop in next to any field, header, or stat: <HelpTip en="..." pt="..." es="..." /> Wired up first instance on the orders Sales Rep table column.
Spanish locale (#9) was already 100% wired: 'es' is in src/i18n/config.ts, messages/es.json has 5000+ translated lines, and both LanguageSelector + CurrencySelector iterate locales[] so Spanish appears in every language picker automatically.
CAD Files tab now also shows files attached via the polymorphic CadFileLink table (i.e. linked from /admin/design or the CAD library), not just direct uploads. Each linked file gets the same '3D View & Casting' button which opens the existing STLPreviewDialog with casting cost in all metals.
Tab is URL-synced (?tab=cad) so each stage is shareable and back/forward works without refetching.
Eager activityLog include reduced from 50 -> 10 (huge perf win on the initial GET; full list still available via the activity tab endpoint).
GET now returns a Supabase publicUrl alongside each linked CadFile so consumers can render previews without knowing the bucket layout.
@-mention autocomplete now matches against email handle, full email, first name, and strips a leading '@' (typing '@stella' or 'stella' both work).
Empty Add Members state explains *why* nobody appears (no users in directory vs. no match) and console-warns when /api/admin/users returns zero — the most common cause of 'cant find Stella'.
sendBrevoEmail() now captures Brevo's response messageId on success and parses + logs the structured error body on failure.
Send route forces sender.email = BREVO_FROM_EMAIL (verified Brevo sender) and routes replies back via replyTo. This eliminates Brevo's silent-drop behavior when fromAccount.email isn't a verified sender.
UI surfaces the Brevo messageId on success (or a warning if Brevo didn't return one) instead of a fake 'sent' toast.
searchable member picker, persist wizard fields across steps
Added a search input above the member list. Filters teammates by name OR email as you type. Auto-focuses when the dialog opens so you can start typing immediately. Empty-state copy distinguishes 'no matches' from 'none yet'.
The existing checkboxes were fine, but with no search and an unscrolled list, it looked like the dialog was inert. The search makes it obvious the picker is interactive and resolves the user-reported 'cannot create chat' UX.
Cleared memberSearch/newChannelName/selectedMembers when the dialog closes (Cancel, Esc, click-outside) so the next open starts fresh.
Bug: 'title required' on submit even though the user filled in step 1. Root cause: AnimatePresence with mode='wait' was unmounting each step when the user clicked Continue, so by the time they reached step 4 only step-4 inputs were in the DOM. new FormData(e.currentTarget) therefore contained budget/dates only — title (step 1), description (step 2), metals (step 3) were all lost.
Fix: keep all four <motion.section> blocks mounted at all times and use the 'hidden' attribute + animate opacity/x to show only the current one. This way every named field is in the DOM throughout the wizard and FormData captures the full payload on submit.
Switched AnimatePresence mode from 'wait' to 'sync' since nothing actually unmounts anymore. Added an inline NOTE comment so the next person doesn't accidentally re-introduce the regression by 'cleaning up' the always-mounted sections.
unwrap API responses + add @-mentions, browser notifications, photo cache-bust
Messenger crashed with 'k.filter is not a function' when opening /admin/messages. Root cause: setChannels(data) was assigning the raw {channels: [...]} envelope instead of the array, so subsequent .filter() calls threw. Now unwrap every API response (channels, messages, users, send-message, create-channel) defensively and fall back to [] on any non-array shape or non-OK response.
Wrapped users.filter(...) and channels.filter(...) in Array.isArray() guards to prevent any future regressions.
handleCreateChannel now reads data.channel.id (the API returns {channel, success}) so newly-created chats actually open after creation.
Type @ in the composer to get a 6-row dropdown of teammates filtered by handle/name. ArrowUp/ArrowDown to navigate, Enter/Tab to insert, Esc to close. Click also works. Inserts as @firstname which the backend already parses into ChatMention rows.
Added Notification.permission state + an in-line banner that asks for permission the first time a logged-in user opens Messenger.
Subscribed to ALL ChatMessage inserts (filtered client-side to channels you belong to). Notifications fire when a message arrives in a non-active channel OR when @yourhandle mentions you in any channel. Clicking the notification focuses the tab and opens that channel.
Skips notifications for messages you sent yourself.
Team page now appends a ?t=<timestamp> cache-buster to the avatar URL after upload so browsers and Supabase edge caches don't serve the previous bytes.
(If the photo still doesn't render, verify the Supabase 'employees' bucket is set to Public — see supabase/storage-policies-employees.sql)
Removed Messages button from the top header — moved into the sidebar Business Tools group as nav.messages → /admin/messages (in-house Supabase Messenger, NOT Slack)
Moved Vera AI from Overview group into Business Tools (top of group) alongside Messages, Email, Calendar, Tasks, Storage
New /api/admin/me/permissions endpoint returns role + perm rows + a derived canSeeFinancials flag for the current user
Sidebar (desktop + mobile) now hides any nav item whose pageSlug has canView=false. Default-open: missing rows mean allowed
Owners are never filtered
Admin server layout now blocks direct URL access — server-side render of NotAuthorized component (with manager-contact CTA) when the route maps to a slug that user can't view
Middleware forwards x-pathname as a *request* header so server components can route-guard via headers().get('x-pathname')
New 'dashboard-financials' permission slug added to ADMIN_PAGES (toggleable from Team → Member → Access Controls tab)
Server-side payload sanitization: revenue total + per-order $ amounts are stripped from the response before it reaches the client when the user lacks dashboard-financials.canView
Revenue stat tile is hidden entirely; recent orders show '—' instead of $
HeaderChat top-bar Messages button now opens /admin/messages (was redirecting to Slack since Feb 2026)
/admin/messages page renders the full <Messenger /> component (Supabase realtime, channels, DMs, group chats) — replaces the 'moved to Slack' interim card
Add /api/user route returning { id, name, email, image, bubbleColor } — required by the Messenger client and the iOS app
scripts/apply-messenger-migration.mjs — idempotent runner that applies scripts/db-migrations/applied-sql/messaging/add_internal_messenger.sql AND adds ChatMessage + ChatChannelMember to the supabase_realtime publication
Migration applied to production (tables present, counts 0/0/0)
FinancingDisplay shown under PDP price ('as low as $X/mo') — pure display widget; real Affirm/Klarna flow remains in unchanged Stripe Payment Element
StickyMobileAddToCart bar on PDP — mobile only, scroll-to only, NEVER submits to cart (single source of truth for cart adds stays ProductActions)
/promise — 6 promise cards + concierge CTA
/ring-sizer — free ring sizer landing + form, posts to new /api/marketing/ring-sizer (creates EngagementLead, Brevo confirm email, list sync; idempotent on email)
/custom-design — 4-step process landing with transparent pricing
/api/social-proof/recent-purchases — anonymised (first initial + city only, jittered timestamps, 5-min cache); RecentPurchasesTicker mounted off-by-default behind NEXT_PUBLIC_RECENT_PURCHASES_TICKER env flag
Polished /not-found with 3-card next-step grid
Footer 'Why Katura' service row (Promise / Custom Design / Free Ring Sizer / Financing)
Mini progress dots: larger (28px), ring-offset for current stage, shadows on completed steps, smoother transitions, stage labels exposed via title attr.
Same gradient background + hero header treatment for consistency.
Wider container (max-w-5xl) for breathing room.
Need Help card → 'Need a hand?' concierge card with amber accent.
New CustomerCadViewer client wrapper that dynamic-imports STLPreviewDialog with showCastingPanel={false} (hides internal weight/cost data from clients).
Order detail page now fetches JewelryProjectOrderLink → project → approved CAD files and renders a 'Your Custom Design' card with per-file 'View in 3D' buttons. Only files with approvedAt set are exposed to customers.
STLPreviewDialog: new showCastingPanel prop (default true for backward compat with admin call sites). When false, side panel + volume callback are skipped so the canvas takes the full width.
Filters to viewable 3D extensions (stl/obj/3dm/step/iges/gltf/glb/ply).
photoreal metals via studio HDRI + ACES tone mapping
Add @react-three/drei <Environment preset='studio'> to both the single and assembly Canvas — high-metalness materials require an env map for reflections; without one, gold/platinum/silver render as a near-black diffuse colour (the 'yellowish-black' issue from the screenshot).
Bump material colours slightly lighter (#FFE082 vs #FFD700 etc.) so the env-mapped highlights don't crush back to the diffuse base.
Set metalness to 1.0 (true conductor) — values <0.9 fight the env map.
Configure renderer with ACESFilmicToneMapping + sRGB output color space for filmic highlight rolloff that matches photography.
Drop pointLight, lower ambient/hemisphere — env map is doing the work.
envMapIntensity bumped to 1.6 for richer reflections.
High Jewelry page redesign: Remove all emoji/icon clutter, adopt Graff-inspired editorial approach. Strip deposit tier explanations for pure mystery—'if you have to ask, you can't afford it.'
13 client components in /admin lack useAdminTranslations. High-impact (payroll-dashboard, marketing, crm-appointments) vs low-impact (stella, enterprise) ranked in the audit doc.
Recommended approach: lint rule + bulk OpenClaw 'translate' skill rather than hand-translating 13 files.
Added VERA_PROVIDER / VERA_MODEL / OLLAMA_BASE_URL / GROQ_API_KEY with sensible defaults (Ollama). Existing OPENAI_API_KEY is now optional.
high-jewelry hero — show full collar/necklace, extend behind header
Negative top margin pulls hero section under the header for true full-bleed effect (image extends behind the nav bar)
Hero height now calc(100vh + header height) so the image has more vertical room — the collar/necklace no longer gets cropped
Changed object-position from object-top to object-[center_25%] so the focal point is at 25% from top (face visible) while showing significantly more of the necklace area below
Added z-20 to header so it stays above the hero when the section slides underneath it