Argument

Why friction (accounts, setup, dashboards) kills the maker loop for hobby projects

A hobby project is a thin loop between an idea and the thing existing in some form you can poke at. Three small context switches kill that loop more often than any technical limit does: an account form, an env file, and a project dashboard. The interesting design move is to treat the absence of all three as the spec, and to ask what concretely fills the gap.

M
Matthew Diakonov
6 min
Direct answer

Friction kills the maker loop because every decision between having an idea and seeing the artifact is a context switch, and most hobby ideas do not survive three context switches before the maker forgets why they were excited. Tools that actually ship hobby projects treat the absence of the three classic friction stops (account form, env setup, project dashboard) as a hard constraint, then build the rest of the architecture around that absence. On mk0r the replacements are concrete and small: one localStorage UUID, one fire-and-forget POST that warms a sandbox before the first keystroke, one Firestore transaction that hands you a ready VM on the first prompt.

The maker loop is the unit of work

Most posts about hobby builds talk about motivation, discipline, or weekend cadence. Those frame the problem at the wrong scale. The unit that actually matters is one iteration of the maker loop: an idea, an artifact, a reaction. A working version on the screen is a forcing function for the next idea, because seeing the thing changes what you want it to be next. A blank textarea or a half-set up environment is the opposite. It is a forcing function for giving up.

That is why the cost of friction is non-linear. A single account form is annoying. Two stops in a row (account, then env setup) is a noticeable hesitation, and you can feel the maker reading the screen and asking themselves whether this is worth it. Three stops in a row (account, env setup, project picker) is where most hobby ideas quietly die. There is no single moment of giving up, just a thinning sense that the original idea was not that interesting and tomorrow would be a better day to try.

Pro work survives three context switches because the motivation is external (a deadline, a paycheck, a reviewer). Hobby work has none of those. The only motive is the maker’s curiosity about the next iteration, and curiosity is the cheapest fuel to lose.

The three classic friction stops

Pick any tool aimed at builders and you can usually find one of three friction stops between landing and first artifact:

  1. Account form. Email, password, sometimes a verification round trip, sometimes a card. Justified for serious work, fatal for hobby work because it forces a commitment before the maker has decided whether the tool can do the thing.
  2. Env setup or install step. A CLI to install, an env file to fill, a Docker daemon to start, a port to free up. Each of these is a small yak-shave that quietly resets the maker’s mental state from “I want to build X” to “I am debugging the tool now.”
  3. Project dashboard or picker. Land on a list of past projects. Pick one or start a new one with a name and a template. Often the most quietly lethal of the three, because it presents a list of choices exactly when the maker wants to be making one specific thing, and choosing among unrelated alternatives is a tax on attention.

You can survive any one of these in isolation. You usually cannot survive all three back to back, especially on a Saturday at 11pm with a half-formed idea.

What replaces each friction stop

The same three rows, framed as either-or. Most builders pick the left column. mk0r is built around the right column.

FeatureMost buildersmk0r
AccountSignup form before any preview. Email, password, often a credit card.One localStorage key (mk0r_session_key) set to crypto.randomUUID() on first paint. No email, no password.
Environment setupA setup wizard, env file, or installed CLI before the first build./api/vm/prewarm fires on page mount. The sandbox is warming before the user has typed a character.
Project picker / dashboardLand on /dashboard. Pick a project. Or start a new one with a name and a template.Land on a textarea. The first prompt is the project. The textarea is the dashboard.
First-prompt pathAuth -> dashboard -> new project -> template -> wait for build -> first preview.Type prompt -> Firestore transaction pops a warm VM -> first preview. One screen, one round trip.
Coming back tomorrowFind the project in a list of past projects. Reauthenticate if the session expired.The same UUID is in localStorage. Same URL. The session resumes if the VM is alive, or rebuilds from the per-turn git history if not.

The replacement, in three pieces

The right column above is not a marketing summary. It is the actual mechanism, and it is small enough to read in a single sitting. Three pieces:

1. The account is a UUID in localStorage

In src/app/(landing)/page.tsx around line 48, the landing page does this on first paint:

let key = localStorage.getItem("mk0r_session_key");
if (!key) {
  key = crypto.randomUUID();
  localStorage.setItem("mk0r_session_key", key);
}

That string is the user’s identity for every API call for the rest of the session and for every return visit in the same browser. There is no email, no password, no verification round trip. The Firestore document at app_sessions/{sessionKey} holds the per-user state on the server. The browser holds the UUID and nothing else.

2. Setup happens before the first keystroke

On the same file at line 63, the page mounts and fires:

useEffect(() => {
  fetch("/api/vm/prewarm", { method: "POST" }).catch(() => {});
}, []);

Fire and forget. The response is discarded. The point is the side effect: that route calls topupPool(), which checks the vm_pool Firestore collection and, if fewer than VM_POOL_TARGET_SIZE (default 1 in src/core/e2b.ts line 2011) ready sandboxes match the current spec hash, boots one. By the time the maker has read the page and typed a sentence, a Linux VM with Chromium, a Vite dev server, and Playwright MCP is finishing its boot sequence. The setup happens, just not on their screen.

3. The dashboard is the textarea

When the first prompt is submitted, claimPrewarmedSession in src/core/e2b.ts (line 2213) runs a Firestore transaction that pops one ready VM from the pool:

await db.runTransaction(async (tx) => {
  const query = db.collection("vm_pool")
    .where("status", "==", "ready")
    .where("specHash", "==", currentSpecHash)
    .limit(1);
  const snap = await tx.get(query);
  if (snap.empty) return;
  const doc = snap.docs[0];
  tx.delete(doc.ref);
  claimed = { ...(doc.data()), poolDocId: doc.id };
});

That transaction is the entire account-creation step on this product. A database transaction that hands the maker a pre-built sandbox bound to the UUID they minted seconds earlier. There is no project picker because the prompt is the project, and there is no past-projects list because the UUID in localStorage already maps to the maker’s one live session.

From open tab to live preview

UserLanding pagePrewarm routeVM poolOpen mk0r.comuseEffect: read mk0r_session_key, mint UUID if missingPOST /api/vm/prewarm (fire and forget)topupPool() to VM_POOL_TARGET_SIZEready: 1, warming: nType first prompt, hit submitclaimPrewarmedSession via Firestore transactionSandbox + ACP sessionLive preview streaming

What removing the dashboard actually costs

No design choice is free. The cost of the textarea-as-dashboard is that there is no list of past projects to pick from. Every return visit in the same browser resumes the same session. If the maker wants a fresh start they open a new browser window or clear the storage key. There is no UI for archiving an idea, no folder of weekend projects to browse.

That is a real loss for serious work. It is also, deliberately, the wrong loss to optimize against for hobby work. The maker who lost an idea to a dashboard that demanded they pick a project before letting them type is the more common failure case than the maker who built three things and could not find them all later. Most hobby projects are one project, built in one sitting, and the optimization that matters is keeping the loop alive long enough to see the artifact.

When the project outgrows hobby, the maker connects it to a real backend with real auth and real persistence. That is the correct moment to introduce a dashboard, not the first minute of the first build.

The habit any builder can copy

Pre-do the next decision while the user is still on the current screen. The prewarm POST is the canonical example. It does not change anything visible on the page. It just moves the start of the boot sequence backward in time, so the wait the maker would otherwise have hit is already consumed by the time they reach it.

Almost every “feels instant” tool is doing some version of this. Speculative prefetches on hover. Service workers warming caches. Pool managers holding ready compute behind a queue depth of one. The pattern is old. The place hobby tooling tends to under-apply it is the very first wait, where the user is most likely to decide the tool is not worth their evening.

If you are building a tool for hobby makers and you can only do one thing on this list, do this one. Move the first wait off the screen. The other two pieces (no account, no dashboard) follow naturally once you commit to that.

Want a 15-minute walkthrough of the friction-free path?

Show me your idea. I will open the file paths in the post and we will trace the loop end to end.

Frequently asked questions

What does "the maker loop" actually mean?

It is the round trip from "I have an idea" to "the thing exists in some form I can poke at." Hobby projects live or die by how short that loop is. A real prototype on the screen is a forcing function for the next idea. An empty form, an env file you have not filled out, a dashboard with eight previous projects shouting for attention: each of those is one more decision between the idea and the artifact, and most ideas do not survive three of those before the maker forgets what they were excited about.

Why is an account form specifically expensive for hobby projects?

Two reasons. First, signing up obligates the maker to a relationship with the tool before they have decided whether the tool can do the thing. That is a backwards order of operations for a hobby idea, where the whole point of building is to find out whether the thing is interesting. Second, signup forms are decision-rich: which email, which password, do I want emails, is this going to charge me. Each of those is a small context switch, and the cumulative weight of three or four small context switches is enough to lose the maker before the first build.

How does mk0r get away with no account at all?

The "account" is one line of code. In src/app/(landing)/page.tsx around line 48, the page reads localStorage.getItem("mk0r_session_key") and, if nothing is there, sets it to crypto.randomUUID(). That UUID is the user's identity for the rest of the session and across return visits in the same browser. Every API call (preview state, publish, session resume) is keyed off that string. The first time anyone arrives, that line of JavaScript runs once and the user is now "signed up."

Where does the state live, then?

On the server, keyed by the UUID. Firestore stores a document per session at app_sessions/{sessionKey} with the sandbox id, the ACP session id, the residential IP setting, and a few pieces of bookkeeping. The user's browser holds nothing but the UUID. If they clear localStorage they get a new identity. If they keep it, every return visit picks the same session up where they left off, and if the original VM has been garbage collected the per-turn git history rebuilds the project on a fresh sandbox.

What is the prewarm POST and why does it matter?

The landing page mounts and immediately fires fetch("/api/vm/prewarm", { method: "POST" }) at line 63 of (landing)/page.tsx. It is fire-and-forget, the response is ignored. The point is the side effect: that route calls topupPool() in src/core/vm.ts, which checks Firestore for ready sandboxes matching the current spec hash and, if there are fewer than VM_POOL_TARGET_SIZE (default 1), boots and initializes one. The maker is reading the textarea while a Linux VM with Chromium, Vite, and Playwright is finishing its boot sequence. By the time they type a sentence and hit submit, the sandbox is usually warm.

What replaces the project picker?

Nothing replaces it. The textarea on the landing page is the project picker. The first prompt is the new project. Coming back tomorrow with the same browser opens that same project because the UUID in localStorage maps to the same Firestore session. There is no list of past projects to pick from, because making a list of past projects would re-introduce the dashboard step that was just removed. If the maker wants to start a fresh idea they open a new browser window or clear the storage key. That is the cost of removing the dashboard, and it is a cost worth paying for hobby work.

What does the first prompt actually trigger on the server?

claimPrewarmedSession in src/core/e2b.ts (line 2213). It runs a Firestore runTransaction that queries the vm_pool collection for documents with status "ready" and a matching specHash, atomically deletes one of them, and hands the sandbox metadata back to the request. That transaction is the entire account-creation step on mk0r: a database transaction that pulls a pre-built VM out of a pool and binds it to the maker's session key. The user never typed an email and the agent now has a Linux box, a dev server, a real Chromium, and a per-turn git history.

Is this just a no-signup tool with extra steps?

The opposite. The interesting design choice is that no-signup is not a feature in a list, it is the constraint the rest of the architecture is fitted to. Sessions are UUIDs because there are no users to bind them to. State is rebuildable from a per-turn git history because there is no user account to authenticate against and trust. The VM pool exists because the user is going to start typing while their VM boots. Each piece is what you build when you treat the absence of an account form as a hard requirement, not a marketing claim.

When does the absence of accounts become a real problem?

When the project outgrows hobby. Real auth, payments, a multi-user database, an analytics dashboard, an email-based password reset: those are the points where the user does want to claim ownership of the thing they have built, and a UUID in localStorage is no longer the right primitive. mk0r's stance is that the friction-free path is the right default for the first hour of a project, and the moment you want to ship something serious you connect it to a real backend and a real auth system. The design is tuned for the part of the loop where ideas die fastest, not the part where they are ready to scale.

What is one habit a maker can copy from this even if they are not using mk0r?

Pre-do the next decision while the user is still on the current screen. The prewarm POST is the canonical example: by the time the user has finished reading the page, the next thing they need is already half built. Almost every "feels instant" tool you have used is doing some version of this, and the easiest place for hobby builders to add it is at the very top of the loop, where the first wait between idea and artifact is also the wait that loses the most makers.

mk0r.AI app builder
© 2026 mk0r. All rights reserved.