DEV Community

Avery Lin
Avery Lin

Posted on

One Click, One Row

A solo founder opens the inbox before the first coffee.
Two welcome notes name the same new address.
The public waitlist form accepted a double click.

The second database row is not a mystery.
The button fired again before the first write finished.
A bigger cloud invoice will not remove that race.

The cloud bill must stay at zero today.
The patch must land before the next signup wave.
A rented lab machine is the wrong shape of help.

The generated form was a fast win on the surface.
Fast generation skipped the second-click story in full.
The harness puts that story back into the repo.

The first request starts a write and yields the thread.
The second request arrives before that write commits.
Both reads see an empty slot and both insert.

A coat check, not a second coat

Picture a coat check at a crowded door.
The first click parks the coat and takes a stub.
The second click must show that stub and leave.

A free draft lane can still earn its place.
MonkeyCode is presented as an open-source coding assistant here.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode offers free model access for a narrow coding pass.
It also offers a free server option for that pass.
The free lane drafts the patch and nothing more.

The local harness decides whether that patch is real.
Current terms live on the project page, not in memory.
Quotas, model names, and uptime are not assumed here.

The operator points to a free token allowance as well.
The live figure must be read on that same page.
This draft will not freeze a number that can move.

One folder, one bug

The founder copies one module into a scratch folder.
That folder holds the signup claim and its proof.
No customer export rides along with the prompt.

The prompt names the bug in plain words.
Two requests with one normalized email must yield one row.
The model may suggest code, not production credentials.

A secret key in the prompt is a leaked key.
A real email list is not a safe fixture.
Synthetic names keep the free server boring and safe.

These files are an unexecuted sketch for a scratch repo.
They are not a log from a live customer system.
A local run must happen before any trust is assigned.

Bug: two posts of the same email create two rows.
Constraint: one normalized email may claim only one row.
Do not add network calls. Do not read environment secrets.
Files in scope: waitlist.py only.
Enter fullscreen mode Exit fullscreen mode

The prompt above is a boundary, not a spell.
It keeps the free server on one file and one rule.
A wider paste invites a wider and quieter mistake.

The claim that must win twice

The claim function is short enough to read aloud.
It returns the old row when the email already exists.
It writes once, then reports that the write happened.

def normalize_email(email):
    """Fold case and space so one person keeps one stub."""
    cleaned = email.strip().lower()
    if "@" not in cleaned or cleaned.startswith("@") or cleaned.endswith("@"):
        raise ValueError("email required")
    return cleaned


def claim_signup(email, store):
    """Return the existing row when the email was already claimed."""
    key = normalize_email(email)
    prior = store.get(key)
    if prior is not None:
        return prior, False
    row = {"email": key}
    store[key] = row
    return row, True


def maybe_send_welcome(row, created, outbox):
    """Append one welcome note only for a new claim."""
    if not created:
        return False
    outbox.append({"to": row["email"], "kind": "welcome"})
    return True
Enter fullscreen mode Exit fullscreen mode

The store in this sketch is a plain dict.
A real app should use a unique database constraint.
The dict still teaches the branch the bug forgot.

Lowercasing the mail stops a case-only twin.
The strip call drops a pasted trailing space.
Those two cleanups belong inside the claim itself.

A blank email is a broken ticket, not a guest.
The function raises before a nameless row can land.
That failure is safer than a silent duplicate.

Proof that never dials out

The harness must run on a laptop without network.
A green model reply is not evidence of a fix.
The script below is the only evidence that counts.

from waitlist import claim_signup, maybe_send_welcome


def prove():
    store = {}
    first, created = claim_signup("Ada@Example.com ", store)
    second, again = claim_signup("ada@example.com", store)
    assert created is True
    assert again is False
    assert first["email"] == "ada@example.com"
    assert second is first
    assert len(store) == 1
    outbox = []
    assert maybe_send_welcome(first, created, outbox) is True
    assert maybe_send_welcome(second, again, outbox) is False
    assert len(outbox) == 1
    other, other_created = claim_signup("lin@example.com", store)
    assert other_created is True
    assert other is not first
    assert len(store) == 2
    try:
        claim_signup("not-an-email", store)
    except ValueError:
        bad_rejected = True
    else:
        bad_rejected = False
    assert bad_rejected is True
    print("waitlist claim held")


if __name__ == "__main__":
    prove()
Enter fullscreen mode Exit fullscreen mode

A missing import fails before any hosted draft runs.
A red assert fails closed and blocks the merge.
A quiet print line means the double click lost.

The shell steps are dull on purpose today.
The compile step runs, then the proof, then a stop.
Nothing in that chain should open a socket.

python3 -m venv .venv
. .venv/bin/activate
python3 -m py_compile waitlist.py prove_waitlist.py
python3 prove_waitlist.py
Enter fullscreen mode Exit fullscreen mode

Let the free pass draft, then sit down

The founder pastes the function and the failing trace.
The ask stays small and names the second call.
The free server runs that narrow pass, then stops.

MonkeyCode is the drafting bench in this workflow.
It is not the database, the mailer, or the judge.
If the free server is dark, the harness still runs.

A hand-written guard is still a valid ship.
Speed comes from the small scope, not from magic.
The zero bill comes from refusing a new rental.

When the draft returns, the same script runs again.
A changed signature must fail the compile step first.
Only a green local run may touch the real branch.

The index that closes the door

A unique index turns the second insert into an error.
The handler must catch that error and read the winner.
A blind retry will recreate the double welcome note.

CREATE UNIQUE INDEX waitlist_email_key
    ON waitlist_signups (email);
Enter fullscreen mode Exit fullscreen mode

The SQL beside the function is short and blunt.
It refuses a second insert with the same email.
The app then reads the row it already stored.

def on_unique_violation(email, store):
    row = store.get(normalize_email(email))
    if row is None:
        raise LookupError("winner missing after conflict")
    return row, False
Enter fullscreen mode Exit fullscreen mode

That helper is a sketch of the conflict path.
A real driver raises its own integrity error class.
The handler maps that class and leaves other errors alone.

The founder checks the index before blaming the button.
A missing index lets the app branch lie in comfort.
The database, not the button color, owns the rule.

Mail only on the first claim

Mail should send only when the created flag is true.
That rule stops the second welcome note cold.
The inbox story ends when one address gets one note.

The welcome helper lives beside the claim function.
A false created flag must leave the outbox alone.
The proof asserts a single note after both calls.

sent = maybe_send_welcome(second, False, outbox)
assert sent is False
assert len(outbox) == 1
Enter fullscreen mode Exit fullscreen mode

A failed send should not roll back a stored claim.
The row is the source of truth after the unique write.
Mail can retry later without inserting a twin.

Bad patches that look clever

Three bad patches show up often in this lane.
One patch mints a fresh client key on every click.
Another patch sleeps, then hopes the race has ended.

A third patch catches the click in the browser only.
A fresh client key fails when the second click differs.
A sleep call fails when the network slows down.

A browser lock fails when two devices race apart.
The model may invent a lock the database cannot keep.
The model may skip the unique index and smile.

The assert on store length is the smile detector.
A unique email index fails closed, which is the point.
A green chat bubble cannot overrule that length check.

A note the week can trust

A tiny runner writes the result to a local log.
The log stores the date, the file hash, and the verdict.
That note is the memory when the week gets loud.

import hashlib
import subprocess
import sys
from datetime import date
from pathlib import Path


def file_hash(path):
    data = Path(path).read_bytes()
    return hashlib.sha256(data).hexdigest()[:12]


def main():
    target = "waitlist.py"
    digest = file_hash(target)
    run = subprocess.run(
        [sys.executable, "prove_waitlist.py"],
        check=False,
    )
    verdict = "pass" if run.returncode == 0 else "fail"
    line = f"{date.today().isoformat()} {digest} {verdict}\n"
    with open("proof.log", "a", encoding="utf-8") as handle:
        handle.write(line)
    print(line.strip())
    return run.returncode


if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode
python3 -m py_compile note_proof.py
python3 note_proof.py
Enter fullscreen mode Exit fullscreen mode

The hash stops a silent edit after a green run.
If the file changes, the old pass no longer counts.
The proof runs again and a fresh line is appended.

Who should walk past this door

Some days the free lane is the wrong door.
A repo full of payment secrets should stay offline.
A regulated customer file should never board that server.

A founder who needs a support contract needs a vendor.
A team that must audit every prompt needs another path.
This sketch has no legal review and no uptime promise.

A huge monorepo will not fit a short free pass.
The cut continues until one file tells the story.
If that cut fails, the guard is written by hand.

This path fits a solo founder who can read a diff.
It fits a scratch app with synthetic fixtures only.
It does not fit a team that shares live customer data.

The limits are accepted before the first prompt leaves.
Free access can throttle, pause, or change shape.
A dark lane is a cue to type the guard by hand.

Leave the invoice asleep

The production step is still a human merge.
The unique index belongs beside the claim function.
The bill stays quiet because no new machine started.

A reader can copy the harness into a scratch repo today.
The same reader confirms the repository, license, and live terms first.
The proof runs before any generated line is trusted.

Top comments (0)