Minbook
KO
The Code Was Done, But Everything Else Wasn't

The Code Was Done, But Everything Else Wasn't

MJ · · 6 min read

Analyzing the difference between 'code complete' and 'product complete' through the 3-week post-feature work (payment stability, security, legal docs, SEO, etc.) which accounted for 88% of the effort.

We Built an MVP in 3 Days

WICHI’s first version came out of the Jocoding Hackathon in just 3 days. Claude Code handled the backend API, and Lovable generated the frontend. Thanks to AI coding tools, the development cycle was completely different from what traditional timelines would suggest.

The 3-Day Tech Stack

LayerTechnologyRole
BackendFastAPI + PythonAI search engine queries, brand analysis, score calculation
FrontendReact + Vite (Lovable)UI, dashboard, report views
DBSupabase (PostgreSQL)Users, analysis results, credit storage
InfraRailway + Lovable hostingServer deployment, frontend hosting
AuthSupabase AuthSign-up, login, session management

Features That Worked at Hackathon Submission

  • Parallel queries to 3 AI search engines (ChatGPT, Perplexity, AI Overviews)
  • Brand mention analysis and GEO Score calculation
  • Analysis execution based on the 9-Bucket query framework
  • User sign-up, login, and dashboard access
  • Payment page (Lemon Squeezy integration)
  • Analysis report generation and viewing

The code was done. We had a product with working core features.

But it was not ready to launch.


”It Works” and “It’s Launchable” Are Different Things

Only after the hackathon ended and we decided to commercialize did we realize there was a massive gap between a “working MVP” and a “product people can pay for.”

graph LR
    A["Working MVP"] --> B["Launchable Product"]

    subgraph GAP["What Was Missing"]
        direction TB
        C["Legal Notices"]
        D["Payment Stability"]
        E["Security Validation"]
        F["Error Monitoring"]
        G["SEO"]
        H["Accessibility"]
    end

    A -.->|"3 days"| A
    B -.->|"+3 weeks"| B
    A -->|"Didn't know this gap existed"| GAP
    GAP --> B

The defining characteristic of this gap was that the items were not technically difficult — we simply didn’t know they existed. Here’s the breakdown by category.


What Was Missing — By Category

We were collecting user emails without disclosing our data handling practices anywhere. We were accepting payments without any terms of service.

Specifically missing:

  • Privacy Policy (/privacy) — what data we collect, why, how long we keep it, whether we share it with third parties
  • Terms of Service (/terms) — conditions of use, liability limitations, dispute resolution
  • Cookie consent banner — disclosure of third-party cookies from GA4, Supabase Auth, Lemon Squeezy, etc.
  • Refund policy — conditions and procedures for refunds on a credit-based service

The moment you accept payments, legal notices are not optional. Both Korea’s Personal Information Protection Act and GDPR require disclosure before service usage. We learned this after launch.

2. Payment Stability — Webhooks, Idempotency, Transactions

The payment page existed, but the flow after payment was incomplete.

sequenceDiagram
    participant User as User
    participant App as WICHI
    participant LS as Lemon Squeezy
    participant DB as Database

    User->>App: Payment request
    App->>LS: Create checkout session
    LS->>User: Redirect to payment page
    User->>LS: Complete card payment
    LS->>App: Send webhook

    Note over App: Problems start here

    App->>DB: Add credits
    Note over App,DB: No idempotency → duplicate charges possible
    Note over App,DB: No signature verification → forged requests possible
    Note over App,DB: No retry logic on failure

Specifically missing:

  • Webhook signature verification — no validation that incoming requests actually originated from Lemon Squeezy
  • Idempotency handling — if the same webhook arrived twice, credits would be double-charged
  • Payment failure handling — no user-facing flow for card declines or network errors
  • Credit transaction safety — no recovery logic if the server errored during an analysis run after credits were deducted

Without webhook idempotency, double-charging actually happens. This is not theoretical — it occurred during testing.

3. Security Validation — Input Sanitization, Injection Defense

The frontend had basic input length limits. The backend accepted user input almost as-is.

Specifically missing:

  • Backend input validation — no allowlist-based filtering. SQL or script injection was possible through the brand name input field
  • Prompt injection defense — user input went directly into LLM prompts. Unintended instructions could be injected
  • Rate limiting — any user could fire unlimited analysis requests
  • CORS configuration — the API was callable from any domain
# Before: using input as-is
async def analyze(brand_name: str):
    prompt = f"Analyze the brand '{brand_name}' in AI search results"
    # brand_name could contain prompt injection

# After: allowlist + pattern filtering
async def analyze(brand_name: str):
    if not re.match(r'^[a-zA-Z0-9가-힣\s\-\.]{1,100}$', brand_name):
        raise ValidationError("Invalid brand name")
    if contains_forbidden_patterns(brand_name):
        raise ValidationError("Forbidden input detected")
    prompt = f"Analyze the brand '{sanitize(brand_name)}' in AI search results"

With a free MVP, someone entering malicious input causes limited damage. The moment payments are involved, the incentive for abuse appears. Credit fraud, API abuse, and injection attacks translate directly into real costs.

4. Error Monitoring — Sentry, Alerting

There was zero monitoring. When an LLM API call failed during analysis, users saw a blank screen. And we had no idea it was happening.

Specifically missing:

  • Error tracking service (Sentry, etc.) — no server/client error collection
  • Uptime monitoring — no way to know if the server had gone down
  • Alert channels — no Slack/email/Telegram notifications on errors
  • User-facing error messages — no feedback like “An error occurred during analysis. Your credits will be restored”

We had no way to know errors existed until users reported them directly. That is not the structure of a paid service.

5. SEO — A Paid Service That Can’t Be Found

No robots.txt, no sitemap, no meta tags, no Open Graph images. We hadn’t even registered with search engines.

Specifically missing:

ItemStatusImpact
robots.txtMissingCrawlers don’t know which pages to index
sitemap.xmlMissingCan’t communicate page structure to search engines
Meta tags (title, description)MissingNo meaningful info in search results
OG imagesMissingBroken link cards on social sharing
canonical URLMissingPotential duplicate page issues
Structured data (JSON-LD)MissingNo Rich Snippet opportunities
Google Search Console registrationNot doneCan’t monitor indexing status

For an early-stage SaaS with no ad budget, organic search is practically the only free acquisition channel. That channel was completely shut.

6. Everything Else — Small But Costly to Skip

  • GA4 integration — no user behavior data collection, no way to measure conversion rates
  • Error pages (404, 500) — showing default browser error pages
  • Loading states — no loading indicator for analysis runs (which take 3-5 minutes)
  • Email notifications — no email when analysis completes
  • Mobile responsiveness — layout broken on some pages

Why We Didn’t Know

The Blind Spot of AI Coding Tools

AI coding tools are fast at “build this feature.” Auth systems, payment integrations, CRUD APIs — they generate high-quality code quickly for specific feature requests.

But they don’t answer “what did you forget before launch.” This is not a deficiency of the tools. AI coding tools are designed to convert specifications into code, not to validate the completeness of the specifications themselves.

graph TB
    subgraph AI_TOOL["What AI Coding Tools Do Well"]
        A1["Feature request → code generation"]
        A2["Bug fix request → patch generation"]
        A3["Refactoring request → code improvement"]
    end

    subgraph BUILDER["What the Builder Must Know"]
        B1["What's missing for launch"]
        B2["What's legally required"]
        B3["What's a security risk"]
        B4["What to prioritize for the business"]
    end

    AI_TOOL -.->|"This area is not covered"| BUILDER

The Structural Limitation of Hackathons

A hackathon operates under the constraint: “show something working in 3 days.” Within that constraint, legal notices, security validation, and SEO naturally get deprioritized. That makes sense — “does it have a privacy policy?” is not on the judging criteria.

The problem is that those priorities linger after the hackathon ends. “We’ll handle it later” becomes a loop, until right before launch when you realize “we can’t accept payments without this.”


Time Distribution — 15% Code, 85% Everything Else

For WICHI, the actual time distribution looked like this:

Task CategoryTime SpentRatioDifficulty
Core feature code (AI analysis, scoring)3 days12%High
Payment integration + webhook stabilization3 days12%Medium
Security hardening (input validation, injection defense)2 days8%Medium
i18n (Korean/English bilingual support)3 days12%Medium
SEO (meta tags, sitemap, Search Console)1 day4%Low
Legal documents (privacy policy, terms)1 day4%Low
Monitoring (GA4, Sentry integration)1 day4%Low
Frontend migration (Lovable to Vercel)1 day4%Low
Sample system (demo without login)2 days8%Medium
Testing, bug fixes, edge cases4 days16%Medium
Documentation, README, deploy config2 days8%Low
DNS, CORS, env vars, misc config2 days8%Low
Total~25 days100%
gantt
    title WICHI: MVP → Launch Timeline
    dateFormat  YYYY-MM-DD
    axisFormat  %m/%d

    section Hackathon (3 days)
    Core feature code           :done, h1, 2026-02-27, 3d

    section Commercialization (~22 days)
    Payment stabilization       :done, c1, 2026-03-03, 3d
    Security hardening          :done, c2, 2026-03-05, 2d
    i18n                        :done, c3, 2026-03-06, 3d
    Frontend migration          :done, c4, 2026-03-04, 1d
    Sample system               :done, c5, 2026-03-09, 2d
    SEO + legal docs            :done, c6, 2026-03-07, 2d
    Monitoring integration      :done, c7, 2026-03-10, 1d
    Testing + bug fixes         :done, c8, 2026-03-11, 4d
    Docs + deploy config        :done, c9, 2026-03-15, 2d
    Misc config                 :done, c10, 2026-03-17, 2d

3 days for the code, roughly 22 days for everything else. By ratio, the code was 12% of the total work.

The interesting part is that among the remaining 88%, almost none of it was technically difficult. Most items were “things that take half a day once you know they need to be done.”


Three Lessons from This Experience

1. Code Complete ≠ Product Complete

The faster AI coding tools get, the stronger the illusion that “the code is done, so we’re almost there.” In reality, code accounts for 10-15% of the total, and the remaining 85-90% determines launch quality.

2. If You Don’t Know About It, You Won’t Do It

When someone asks “do you have a password reset flow?”, you immediately think “oh, I missed that.” But if nobody asks the question, you won’t know until the first customer forgets their password. If it doesn’t make it onto the task list, it doesn’t get done.

3. Checklists Are Knowledge Democratization

Experienced developers know these items as “things you obviously need to do.” But a first-time SaaS builder doesn’t. The most efficient way to bridge this knowledge gap isn’t coaching or mentoring — it’s a well-organized checklist.


The Beginning of a Checklist

While fixing each missing item, we started documenting them simultaneously. Items fixed, items still pending, items that could wait. Initially, it was just a bullet list on a single Notion page.

The list grew. Security, payments, legal notices, SEO, monitoring, accessibility, testing, CI/CD. Each category accumulated 20-40 items.

Some items were specific to WICHI, but the majority were things any SaaS builder should verify.

It wasn’t that we thought “it would be a waste to keep this to ourselves.” More precisely, it was: if someone had organized this for us beforehand, we wouldn’t have wasted 3 weeks.

How this memo grew into 534 items, was organized into 15 categories, became a CLI tool, and ultimately an open-source project — that’s the story of the next post.

Share

Related Posts