Wintura.ai: A Production Reference Build in Next.js 16
How we shipped a B2B SaaS with multi-tenant RLS, Claude Sonnet 4.6 AI pipeline, sealed PDF audit trails, and 24 Playwright e2e tests in 6 weeks.
What "Reference Build" Means
A wintura.ai reference build is a fully deployed B2B SaaS application that demonstrates production-grade patterns: multi-tenant Row-Level Security, AI pipeline integration with Claude Sonnet 4.6, sealed PDF audit trails for compliance, and comprehensive end-to-end test coverage. Unlike prototype demos or MVP mockups, a reference build handles real users, real payments, and real security threats — the same patterns required by any AI-generated codebase transitioning from demo to production.
This post documents the complete technical implementation of wintura.ai, shipped in 6 weeks with 24 Playwright e2e test files. The same production hardening patterns are available via the Production Lift for any Bolt, Lovable, v0, or Cursor codebase.
The Business Context: Why Wintura Exists
Wintura.ai solves a specific problem in the window installation industry: proposal generation for sales teams. A typical window replacement proposal requires:
- Site assessment data (window dimensions, frame condition, installation complexity)
- Product selection from manufacturer catalogs
- Labor calculations based on installation type
- Financing options with compliance-safe disclosures
- Professional formatting for customer presentation
Before Wintura, sales teams assembled these proposals manually — copying data between spreadsheets, calculating totals in their heads, formatting documents by hand. A single proposal took 45-90 minutes. Errors were common. Formatting was inconsistent. Compliance language was often missing.
Wintura automates the entire workflow. The salesperson enters site data and product selections. Wintura generates a complete, professionally formatted proposal with AI-written customer-facing copy, accurate calculations, and compliant disclosures. Time per proposal: under 5 minutes.
The business model is B2B SaaS: window installation companies pay monthly for team access, with usage tracked per proposal generated.
The Technical Stack (Verified May 2026)
Every technology choice in Wintura was made for a specific reason. Here's the complete stack with rationale:
Core Framework
| Component | Version | Rationale |
|---|---|---|
| Next.js | 16.1.6 | App Router, RSC by default, Turbopack build. The Q2 2026 production standard. |
| React | 19.2.3 | Server Components everywhere possible; "use client" only for state/interactivity. |
| TypeScript | 5.8.3 | Strict mode. params is Promise<...> in Next 16 — always await params in dynamic routes. |
| Tailwind CSS | 4.1.7 | @tailwindcss/postcss with @theme inline block in globals.css. |
Database & Auth
| Component | Version | Rationale |
|---|---|---|
| Supabase | Latest | Postgres + Auth + Realtime + Storage in one platform. |
| Row-Level Security | Native | Multi-tenant isolation at the database layer — queries can't return wrong-tenant data. |
| NextAuth.js | 5.0.0-beta.25 | Credentials provider with Supabase as backend. Session management with secure cookies. |
AI Pipeline
| Component | Version | Rationale |
|---|---|---|
| Claude Sonnet 4.6 | Latest | Primary LLM for proposal structure and content generation. |
| Claude Haiku 3.5 | Latest | Secondary LLM for voice refinement (faster, cheaper for styling passes). |
| Anthropic SDK | 0.39.0 | Official TypeScript SDK with streaming support. |
Payments & Billing
| Component | Version | Rationale |
|---|---|---|
| Stripe | 17.7.0 | Subscriptions, usage-based billing, webhook handling. |
| Webhook verification | Native | stripe.webhooks.constructEvent with signature verification. |
| Idempotency | Custom | Event ID tracking prevents duplicate processing. |
Testing & Observability
| Component | Version | Rationale |
|---|---|---|
| Playwright | 1.52.0 | 24 e2e test files covering all critical user journeys. |
| Sentry | 9.15.0 | Error tracking with source maps. |
| Vercel Analytics | 1.5.0 | Core Web Vitals + custom events. |
Deployment
| Component | Rationale |
|---|---|
| Vercel | Edge network, automatic HTTPS, preview deployments, one-click production. |
| GitHub Actions | CI/CD pipeline: lint → type-check → test → deploy. |
Multi-Tenant Architecture: RLS in Practice
The most critical production pattern in Wintura is multi-tenant Row-Level Security. Here's how it works.
The Problem
Wintura serves multiple window installation companies. Each company has its own users, proposals, customers, and billing. Without proper isolation:
- Company A could see Company B's proposals
- A malicious query could dump the entire database
- A bug in one tenant's code could affect all tenants
The Solution: Row-Level Security
Every user-facing table in Wintura has RLS enabled:
-- Enable RLS on proposals table
ALTER TABLE proposals ENABLE ROW LEVEL SECURITY;
-- Create policy: users can only see their organization's proposals
CREATE POLICY org_isolation ON proposals
FOR ALL
USING (organization_id = auth.jwt() ->> 'org_id');
This policy runs at the Postgres level. Even if application code has a bug that omits the tenant filter, the database enforces isolation.
The Implementation Pattern
Wintura uses a three-layer isolation model:
Layer 1: Authentication
// auth.ts
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Credentials({
async authorize(credentials) {
const user = await supabase.auth.signInWithPassword({
email: credentials.email as string,
password: credentials.password as string,
});
if (!user.data.user) return null;
// Fetch organization membership
const { data: membership } = await supabase
.from('organization_members')
.select('organization_id, role')
.eq('user_id', user.data.user.id)
.single();
return {
id: user.data.user.id,
email: user.data.user.email,
orgId: membership?.organization_id,
role: membership?.role,
};
},
}),
],
callbacks: {
jwt({ token, user }) {
if (user) {
token.id = user.id;
token.orgId = user.orgId;
token.role = user.role;
}
return token;
},
session({ session, token }) {
session.user.id = token.id as string;
session.user.orgId = token.orgId as string;
session.user.role = token.role as string;
return session;
},
},
});
Layer 2: Request Context
// middleware.ts
export async function middleware(request: NextRequest) {
const session = await auth();
if (!session?.user?.orgId) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Set organization context for Supabase RLS
const response = NextResponse.next();
response.headers.set('x-org-id', session.user.orgId);
return response;
}
Layer 3: Database RLS
-- The RLS policy uses the JWT org_id claim
CREATE POLICY org_isolation ON proposals
FOR ALL
USING (organization_id = auth.jwt() ->> 'org_id');
-- Same pattern for all user-facing tables
CREATE POLICY org_isolation ON customers
FOR ALL
USING (organization_id = auth.jwt() ->> 'org_id');
CREATE POLICY org_isolation ON products
FOR ALL
USING (organization_id = auth.jwt() ->> 'org_id');
Why This Matters
With RLS enabled, even this intentionally broken query is safe:
// BUG: Missing organization filter
const { data: proposals } = await supabase
.from('proposals')
.select('*');
// RLS automatically filters to current user's organization
The database layer enforces what the application layer forgot. This is defense in depth.
The AI Pipeline: Two-Model Architecture
Wintura's proposal generation uses a two-model architecture with Claude Sonnet 4.6 and Claude Haiku 3.5.
Why Two Models?
Different parts of proposal generation have different requirements:
| Task | Requirements | Best Model |
|---|---|---|
| Structure generation | Complex reasoning, data synthesis, calculation verification | Claude Sonnet 4.6 |
| Voice refinement | Fast iteration, style consistency, lower cost | Claude Haiku 3.5 |
Using Sonnet for everything works but costs 3-4× more per proposal. Using Haiku for everything produces lower-quality structural reasoning. The two-model approach optimizes for both quality and cost.
The Pipeline
Step 1: Data Assembly
// Gather all inputs for the proposal
const proposalContext = {
siteData: await getSiteAssessment(proposalId),
products: await getSelectedProducts(proposalId),
customer: await getCustomerInfo(proposalId),
organization: await getOrgSettings(session.user.orgId),
complianceRules: await getComplianceRules(organization.state),
};
Step 2: Structure Generation (Sonnet 4.6)
const structurePrompt = `
Generate a window replacement proposal with the following structure:
1. Executive summary (3-4 sentences)
2. Site assessment summary
3. Product recommendations with pricing
4. Installation timeline
5. Financing options
6. Compliance disclosures for ${organization.state}
Site data: ${JSON.stringify(proposalContext.siteData)}
Products: ${JSON.stringify(proposalContext.products)}
Customer: ${proposalContext.customer.name}
Output as JSON matching this schema:
${JSON.stringify(proposalSchema)}
`;
const structure = await anthropic.messages.create({
model: 'claude-sonnet-4-6-20250514',
max_tokens: 4096,
messages: [{ role: 'user', content: structurePrompt }],
});
Step 3: Voice Refinement (Haiku 3.5)
const voicePrompt = `
Refine this proposal text for customer presentation.
Maintain all facts and numbers exactly.
Adjust tone to be professional but warm.
Use ${organization.brandVoice || 'professional'} voice.
Original: ${structure.content}
`;
const refined = await anthropic.messages.create({
model: 'claude-3-5-haiku-20241022',
max_tokens: 4096,
messages: [{ role: 'user', content: voicePrompt }],
});
Step 4: Validation
// Verify calculations match source data
const validation = validateProposalCalculations(
refined.content,
proposalContext.products,
proposalContext.siteData
);
if (!validation.valid) {
// Regenerate with explicit calculation instructions
return regenerateWithCorrections(validation.errors);
}
Cost Analysis
Per proposal generation (Q2 2026 pricing):
| Step | Model | Tokens (avg) | Cost |
|---|---|---|---|
| Structure | Sonnet 4.6 | ~2,500 input + ~1,500 output | ~$0.045 |
| Refinement | Haiku 3.5 | ~2,000 input + ~1,000 output | ~$0.003 |
| Total | ~$0.048 |
At $0.05 per proposal, a company generating 500 proposals/month pays ~$25 in AI costs. The subscription price is $199/month — 87% gross margin on AI costs alone.
Sealed PDF Audit Trail
Wintura generates legally compliant proposals with a sealed PDF audit trail. Here's what that means and how it's implemented.
The Compliance Requirement
Window replacement financing involves consumer credit regulations. Lenders require:
- Immutable record — The proposal as presented to the customer cannot be modified after signing
- Timestamp verification — Proof of when the proposal was generated and signed
- Content hash — Cryptographic verification that the document hasn't been altered
The Implementation
Step 1: PDF Generation
// Generate PDF from proposal data
import { renderToBuffer } from '@react-pdf/renderer';
import { ProposalDocument } from '@/components/pdf/ProposalDocument';
const pdfBuffer = await renderToBuffer(
<ProposalDocument
proposal={proposal}
organization={organization}
customer={customer}
generatedAt={new Date()}
/>
);
Step 2: Hash and Seal
import { createHash } from 'crypto';
// Generate SHA-256 hash of PDF content
const contentHash = createHash('sha256')
.update(pdfBuffer)
.digest('hex');
// Store sealed metadata
await supabase.from('proposal_seals').insert({
proposal_id: proposal.id,
content_hash: contentHash,
sealed_at: new Date().toISOString(),
pdf_url: await uploadToStorage(pdfBuffer, `proposals/${proposal.id}.pdf`),
});
Step 3: Verification Endpoint
// API route for auditors to verify proposal integrity
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const { data: seal } = await supabase
.from('proposal_seals')
.select('*')
.eq('proposal_id', id)
.single();
if (!seal) {
return Response.json({ error: 'Proposal not found' }, { status: 404 });
}
// Download current PDF and verify hash
const currentPdf = await downloadFromStorage(seal.pdf_url);
const currentHash = createHash('sha256')
.update(currentPdf)
.digest('hex');
return Response.json({
proposal_id: id,
sealed_at: seal.sealed_at,
integrity_verified: currentHash === seal.content_hash,
original_hash: seal.content_hash,
current_hash: currentHash,
});
}
Why This Matters
Without the sealed audit trail:
- Proposals could be modified after customer signature
- Compliance audits would fail
- The business couldn't serve financing partners
The sealed PDF pattern is required for any B2B SaaS touching regulated industries (finance, healthcare, legal, insurance).
The Test Suite: 24 Playwright E2E Files
Wintura ships with 24 Playwright end-to-end test files covering all critical user journeys. Here's the structure:
Test File Organization
tests/
├── auth/
│ ├── login.spec.ts
│ ├── logout.spec.ts
│ ├── password-reset.spec.ts
│ └── registration.spec.ts
├── proposals/
│ ├── create-proposal.spec.ts
│ ├── edit-proposal.spec.ts
│ ├── delete-proposal.spec.ts
│ ├── duplicate-proposal.spec.ts
│ ├── generate-pdf.spec.ts
│ └── seal-proposal.spec.ts
├── customers/
│ ├── create-customer.spec.ts
│ ├── edit-customer.spec.ts
│ ├── delete-customer.spec.ts
│ └── customer-history.spec.ts
├── products/
│ ├── product-catalog.spec.ts
│ ├── product-selection.spec.ts
│ └── pricing-calculation.spec.ts
├── billing/
│ ├── subscription-upgrade.spec.ts
│ ├── subscription-cancel.spec.ts
│ ├── invoice-history.spec.ts
│ └── payment-method.spec.ts
├── admin/
│ ├── team-management.spec.ts
│ ├── organization-settings.spec.ts
│ └── usage-dashboard.spec.ts
└── accessibility/
└── wcag-audit.spec.ts
Example: Proposal Creation Test
// tests/proposals/create-proposal.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Proposal Creation', () => {
test.beforeEach(async ({ page }) => {
// Login as test user
await page.goto('/login');
await page.fill('[name="email"]', 'test@wintura.ai');
await page.fill('[name="password"]', process.env.TEST_PASSWORD!);
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
test('creates proposal with valid data', async ({ page }) => {
await page.goto('/proposals/new');
// Step 1: Customer selection
await page.click('[data-testid="customer-select"]');
await page.click('[data-testid="customer-option-1"]');
// Step 2: Site assessment
await page.fill('[name="windowCount"]', '8');
await page.selectOption('[name="frameCondition"]', 'good');
await page.selectOption('[name="installationType"]', 'replacement');
// Step 3: Product selection
await page.click('[data-testid="product-category-vinyl"]');
await page.click('[data-testid="product-option-premium"]');
// Step 4: Generate proposal
await page.click('[data-testid="generate-proposal"]');
// Wait for AI generation (with timeout)
await expect(page.locator('[data-testid="proposal-preview"]'))
.toBeVisible({ timeout: 30000 });
// Verify proposal content
await expect(page.locator('[data-testid="proposal-total"]'))
.toContainText('$');
await expect(page.locator('[data-testid="proposal-customer-name"]'))
.toContainText('Test Customer');
// Save proposal
await page.click('[data-testid="save-proposal"]');
await expect(page).toHaveURL(/\/proposals\/[a-z0-9-]+$/);
});
test('validates required fields', async ({ page }) => {
await page.goto('/proposals/new');
// Try to generate without customer
await page.click('[data-testid="generate-proposal"]');
// Should show validation error
await expect(page.locator('[data-testid="error-customer-required"]'))
.toBeVisible();
});
test('handles AI generation failure gracefully', async ({ page }) => {
// Mock AI endpoint to fail
await page.route('**/api/ai/generate', route => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Service unavailable' }),
});
});
await page.goto('/proposals/new');
await page.click('[data-testid="customer-select"]');
await page.click('[data-testid="customer-option-1"]');
await page.fill('[name="windowCount"]', '8');
await page.click('[data-testid="generate-proposal"]');
// Should show error message with retry option
await expect(page.locator('[data-testid="error-generation-failed"]'))
.toBeVisible();
await expect(page.locator('[data-testid="retry-generation"]'))
.toBeVisible();
});
});
Test Coverage Philosophy
The 24 test files follow this coverage priority:
- Authentication flows — If login breaks, nothing works
- Core feature happy paths — The main thing the product does
- Payment flows — Revenue-critical paths
- Error handling — Graceful degradation under failure
- Accessibility — WCAG 2.1 AA compliance
This is the same test coverage philosophy applied in the Production Lift — 15 spec files minimum covering all critical user journeys.
Webhook Security: Stripe Integration
Wintura processes Stripe webhooks for subscription management. Here's the production-grade implementation:
The Problem
Webhook endpoints are public URLs that receive POST requests from external services. Without proper security:
- Attackers can forge webhook events
- Duplicate events can charge customers twice
- Missing events can leave subscriptions in limbo
The Solution
Step 1: Signature Verification
// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(req: Request) {
const sig = req.headers.get('stripe-signature');
const body = await req.text();
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, sig!, webhookSecret);
} catch (err) {
console.error('Webhook signature verification failed:', err);
return Response.json({ error: 'Invalid signature' }, { status: 400 });
}
// Process verified event
await handleStripeEvent(event);
return Response.json({ received: true });
}
Step 2: Idempotency
async function handleStripeEvent(event: Stripe.Event) {
// Check if we've already processed this event
const { data: existing } = await supabase
.from('processed_webhooks')
.select('id')
.eq('event_id', event.id)
.single();
if (existing) {
console.log(`Event ${event.id} already processed, skipping`);
return;
}
// Process the event
switch (event.type) {
case 'customer.subscription.created':
await handleSubscriptionCreated(event.data.object as Stripe.Subscription);
break;
case 'customer.subscription.updated':
await handleSubscriptionUpdated(event.data.object as Stripe.Subscription);
break;
case 'customer.subscription.deleted':
await handleSubscriptionDeleted(event.data.object as Stripe.Subscription);
break;
case 'invoice.payment_succeeded':
await handlePaymentSucceeded(event.data.object as Stripe.Invoice);
break;
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object as Stripe.Invoice);
break;
}
// Mark event as processed
await supabase.from('processed_webhooks').insert({
event_id: event.id,
event_type: event.type,
processed_at: new Date().toISOString(),
});
}
Step 3: Error Handling with Retry
async function handleSubscriptionCreated(subscription: Stripe.Subscription) {
try {
// Update organization subscription status
const { error } = await supabase
.from('organizations')
.update({
subscription_status: subscription.status,
subscription_id: subscription.id,
current_period_end: new Date(subscription.current_period_end * 1000).toISOString(),
})
.eq('stripe_customer_id', subscription.customer);
if (error) throw error;
} catch (err) {
// Log error but don't throw — Stripe will retry
console.error('Failed to process subscription.created:', err);
// Could also send to Sentry here
throw err; // Re-throw to trigger Stripe retry
}
}
Performance: What the Numbers Show
Wintura has been in production since March 2026. Here are the verified performance metrics:
Core Web Vitals (May 2026)
| Metric | Target | Actual |
|---|---|---|
| LCP | < 2.5s | 1.8s |
| INP | < 200ms | 85ms |
| CLS | < 0.1 | 0.02 |
API Response Times
| Endpoint | P50 | P95 | P99 |
|---|---|---|---|
/api/proposals (list) | 45ms | 120ms | 280ms |
/api/proposals (create) | 8.2s | 12.5s | 18.3s |
/api/proposals/[id]/pdf | 1.2s | 2.8s | 4.5s |
/api/auth/session | 12ms | 35ms | 85ms |
Note: Proposal creation is slow because it includes AI generation. The 8.2s P50 is acceptable for a "generate document" action — users expect AI operations to take time.
Error Rate
| Period | Requests | Errors | Error Rate |
|---|---|---|---|
| March 2026 | 12,847 | 23 | 0.18% |
| April 2026 | 28,392 | 41 | 0.14% |
| May 2026 (to date) | 45,128 | 52 | 0.12% |
Error rate trending down as edge cases are discovered and fixed.
The Production Lift Pattern Applied
Wintura implements all five patterns from the Production Lift:
| Pattern | Wintura Implementation |
|---|---|
| Multi-tenant RLS | Postgres RLS on all user-facing tables with org_id isolation |
| Auth hardening | NextAuth v5 + rate limiting + password reset + session management |
| Webhook verification | Stripe signature verification + idempotency tracking |
| Structured errors | Typed error responses with Sentry integration |
| E2E test coverage | 24 Playwright spec files covering all critical journeys |
The same five patterns are what transform any Bolt, Lovable, v0, or Cursor prototype into production-ready code.
What Wintura Cost to Build
Wintura was built internally as a reference implementation. If it were a client project, it would fall into the MVP Sprint Standard tier:
- 6 core flows (proposals, customers, products, billing, team admin, organization settings)
- Custom design system (not off-the-shelf Tailwind components)
- Full admin dashboard
- Analytics integration (Vercel + Sentry)
- AI pipeline integration
MVP Sprint Standard: €12,900 fixed, 6 weeks — which is exactly what Wintura took to build.
Frequently Asked Questions
Can I see Wintura in production?
Yes. Visit wintura.ai. The demo account lets you explore the interface without creating proposals.
Is this the only project Soatech has shipped?
Wintura is the latest shipped project and the primary reference build. It demonstrates the full production hardening stack. Additional client projects follow the same patterns.
Can you build something similar for my industry?
Yes. The Wintura patterns (multi-tenant RLS, AI pipeline, sealed audit trail, e2e tests) apply to any B2B SaaS. The Technical Blueprint (€2,500, 5 days) scopes your specific requirements before committing to a full build.
What if I already have a prototype?
If you built in Bolt, Lovable, v0, or Cursor and need production hardening, the Production Lift (€3,500, 1 week) applies the same five patterns to your existing codebase.
How do I verify the claims in this post?
- Visit wintura.ai and use the demo
- Check the case study for additional technical details
- Book a scoping call to discuss your specific requirements
Ready to build your own reference implementation? The MVP Sprint Standard (€12,900 fixed, 6 weeks) ships 6 core flows with the same production hardening as Wintura. Or start with the Technical Blueprint (€2,500, 5 days) to scope your requirements first.
Related Articles
The Wintura Playbook — Exactly How I Ship Production MVPs at Soatech
Day-by-day MVP Sprint walkthrough: €8.5K-€22K fixed, 4-8 weeks. Derived from wintura.ai (Next.js 16, Claude Sonnet 4.6, multi-tenant RLS, 24 e2e tests). Q2 2026 methodology.
Bolt to Production: The €3,500 Fixed-Price Playbook
Ship your Bolt.new prototype to production in 1 week. Auth, multi-tenant RLS, Stripe webhooks, e2e tests — €3,500 fixed price.
Lovable to Production: The 18-Point Security Checklist
CVE-2025-48757 exposed 10% of Lovable apps. This 18-point checklist covers RLS, auth, webhooks, and deployment — what Lovable doesn't generate.
Ready to build something great?
Architect-led, AI-accelerated. Let's turn your idea into a shipped product.