Product design

Lovable Drafts: explore different pages before deciding what goes live

Lovable builds web apps through conversation. Drafts give creators and collaborators separate previews for trying pages, copy and layouts before bringing a chosen direction into the project and publishing it.

Break down the problem and try your own design and implementation first. Then explore the reasoning to check states, edge cases, trade-offs, and ways to test your approach. The guide offers a reasoning path, not a complete implementation.

01
CODE EXERCISEAdvanced

How can accepting a draft stay atomic under races and retries?

Scenario

In a simplified site-building service, accepting a draft replaces the main project document with that draft; it does not publish to visitors. Two members may accept different drafts concurrently, and a timeout may prompt a retry. Design the commit protocol to preserve version consistency and replay results. baseVersion is the main version the draft started from, expectedVersion is the version required by this request, and operationId identifies one acceptance operation.

Given conditions and requirements

Design the accept operation for a simplified draft service. This is your service, not an account of Lovable's internal implementation.

  1. Each draft stores an immutable baseVersion and a candidate document. The main project has a monotonically increasing version. Acceptance replaces the complete document without automatic merging. Changes to the application’s business data and external-service actions are out of scope; the document, version, draft status and operation result still need to be stored together as required in condition 6.
  2. A request contains draftId, expectedVersion and a unique operationId. Retries with that operationId must use identical parameters; reject mismatches.
  3. Accept only an open draft whose baseVersion equals expectedVersion while the main version also equals expectedVersion. Success replaces the main document, increments the version, marks the draft accepted and saves a replayable result.
  4. Two drafts are both based on version 7. If their requests arrive together, at most one succeeds. The other receives an explicit conflict rather than silently overwriting.
  5. A failed commit must not leave partial state. The server can stop after a successful commit but before delivering its response. A client timeout cannot establish that acceptance failed.
  6. Assume storage supports a transaction or a single atomic compare-and-swap that covers the document, version, draft state and idempotency record. Results are retained throughout this exercise.
  7. Describe the key states, atomic boundary, retry order and minimal counterexamples. Distinguish competing drafts from retries of the same operation. Do not provide a complete implementation.

Work through this case

A and B both originate from main version 7. A commits but its client receives no response; B then requests acceptance and A retries with its original operationId. Explain the three outcomes, the final main version and why replaying A is not a second acceptance.

What to deliver

Deliver a diagram of state and stored records, the atomic commit boundary, separate sequences for retries and competing drafts, and failure-window traces. Atomic means the listed persisted changes all happen together or none happens. Complete service code is unnecessary.

Explore the reasoning
  1. Check operation identity before new-request preconditions. If operationId already exists, compare all parameters. Replay a matching result instead of rejecting it because the draft is now accepted. Reject mismatched parameters. Only a missing record can enter the new-acceptance path.
  2. Make the decision and commit atomic. Idempotency lookup and parameter matching need the same transaction, compare-and-swap or unique-constraint protection. An existing result takes precedence over draft and version checks. For a genuinely new operation, atomically check open status and both version equalities, then commit the document, incremented version, accepted state and result together. Reading a version at the start and writing unconditionally later permits overwrites. A same-ID contender that loses must reread the winner's complete parameters and result: replay if they match, reject otherwise. It must not treat this as a new operation's version conflict.
  3. Make a concurrent conflict explainable. A and B both start from 7. After A commits version 8, B fails its check and A's document remains. Fetch the main version again and create or adjust the proposal. Merely changing B's expectedVersion to 8 must not bypass its immutable baseVersion.
  4. Treat a timeout as awaiting confirmation. If commit succeeds but its response is lost, retry or query with the original operationId and parameters. Replaying success must not increment version 8 to 9. Do not send a fresh operationId as speculative compensation.
  5. Trace failure windows. Before commit, all four state groups stay old; after commit but before response, all are new and retry returns success. Reject mismatched reuse of an ID. B still cannot overwrite after A succeeds. If identical X1 and X2 both initially see no record, X1 commits version 8; X2 finds and replays that result within the atomic decision. Both responses agree and the version increments once. Also test missing drafts, rejected drafts and version mismatches. The exercise excludes external side effects, so do not extend its atomic guarantee to email or payments.