PROJECT-003Status: Still thinking

A minimalist coding playground inside NotFinalDev for solving Python, JavaScript, and SQL challenges with browser-local execution, immediate feedback, and as little IDE overhead as possible.

tech: react · tanstack-start · typescript · codemirror · pyodide · sql.js · supabase · bun · playwright · cloudflare
started: Oct 21, 2026

NotFinalCode

screenshot nfcode
screenshot nfcode

A minimalist coding playground built directly into NotFinalDev.

NotFinalCode sits somewhere between reading a programming exercise and opening a full development environment.

I wanted a place where I could open a problem, write the solution, run it, understand what went wrong, and move on.

No project setup.

No file explorer.

No fake terminal pretending to be more useful than it is.

Just the problem, the editor, and feedback.

Why I built it

Coding platforms often become small IDEs.

That makes sense for larger projects, but for a short algorithm or SQL exercise I do not necessarily want an entire development environment.

I wanted the interface to stay quiet.

The problem should be the important part.

The code should be the important part.

Everything else should exist only when it is useful.

That became the main design rule behind NotFinalCode.

What it contains

The main /code page starts intentionally empty.

A small brace-based interface exposes the settings and lets the rest of the playground appear progressively instead of immediately presenting a wall of controls.

From there, NotFinalCode currently includes:

  • programming challenges in Python and JavaScript;
  • SQL challenges;
  • a searchable Library;
  • filtering by language and difficulty;
  • a Free Run mode without challenge grading;
  • shareable Issue links;
  • local drafts and preferences;
  • execution feedback and diagnostics;
  • timers for people who apparently enjoy measuring how long they have been stuck.

The current catalogue contains 664 Issues:

  • 619 programming questions;
  • 45 SQL questions.

The editor

The editor is built with CodeMirror 6.

I briefly entertained the idea of recreating parts of the editing experience myself.

I then remembered that text editors are one of those things that look simple until you try to build one.

CodeMirror already handles the important parts:

  • cursor behaviour;
  • selection;
  • indentation;
  • undo and redo;
  • language grammars;
  • syntax highlighting.

I built the interface around it instead.

The empty editor also contains faint “ghost” guides that reproduce the writing surface I originally sketched.

They are decorative.

They are not real source characters.

This distinction became surprisingly important.

Running the code

Python, JavaScript and SQL each use a different browser runtime.

Python runs through Pyodide in a dedicated worker.

JavaScript runs inside workers that can be terminated.

SQL uses SQLite through sql.js, with a fresh database for each sample.

The objective was to keep execution local to the browser rather than create a server process every time someone presses Run.

That does not mean I consider the browser a trusted judge.

The playground provides feedback against public samples.

It does not provide secure scoring.

If I ever add rankings, points or trusted submissions, those results will need independent server-side verification.

A green result means:

Your code produced the expected output for this sample.

It does not mean:

Congratulations. Cryptography has proven you are a software engineer.

The sandbox problem

Moving user code into a Web Worker was not enough.

A worker removes computation from the UI thread, but it does not automatically create the isolation boundary I wanted.

The current execution path therefore uses a sandboxed opaque-origin iframe, a restricted Content Security Policy, a private MessageChannel, and dedicated workers.

Submitted code does not receive access to the application document or authentication context.

Execution is also bounded with timeouts and output limits.

I learned quite a lot while building this part.

Mostly that “it runs in a worker” and “it is safely isolated” are very different sentences.

The starter code was wrong

Not technically wrong.

Worse.

Confusing.

The first programming starters exposed too much of the execution protocol.

Users could see input parsing, conversion loops and output formatting before they even reached the algorithm they were supposed to implement.

For example, a simple challenge could begin with several lines of preparation that had nothing to do with solving the actual problem.

Eventually I moved that responsibility into the runner.

The current contract looks more like:

def solve(numbers):
    # Return your answer.
    return None

or:

function solve(numbers) {
  // Return your answer.
  return null;
}

The runner supplies the arguments and interprets the return value.

This was one of the most useful iterations in the project.

Less code did not automatically make the original version simpler.

Removing the wrong responsibility did.

The briefs had the same problem

When I changed the execution contract from script-style code to functions returning values, I initially tried to explain that migration inside the problem briefs.

That was a mistake.

The user does not need to know the history of my runner.

They need to know what the problem expects.

So I removed the migration explanations and rewrote the briefs around the current contract instead.

It sounds obvious now.

It was less obvious after spending several hours thinking about the implementation.

Local AI is part of the authoring workflow

AI is involved in NotFinalCode, but not where I want correctness to depend on it.

I have experimented with small local models for things such as:

  • generating or improving briefs;
  • identifying semantic input families;
  • helping annotate the question library.

Those experiments have included small Qwen models, Kev and Laya.

They are authoring tools.

They do not decide whether submitted code is correct.

Some model-generated work is also deliberately rejected or left unpublished when the review quality is not good enough.

A model producing an answer is not the same thing as me trusting it.

That distinction is becoming one of the recurring themes of this project.

Running inference on another machine

The local-AI authoring workflow does not need to run on the laptop hosting the development environment.

Some inference jobs run on my AgentHQ machine.

The authoring scripts send structured requests over SSH to a model service that stays bound to the remote machine's loopback interface.

The simplified path is:

authoring script
      ↓
     SSH
      ↓
AgentHQ loopback
      ↓
 local model
      ↓
JSON response

There is no silent cloud fallback.

If the remote model is unavailable, the operation fails.

I prefer that.

I would rather know that the intended local system is unavailable than accidentally process something somewhere else.

Keeping model output separate from truth

Working with hundreds of questions created another problem: provenance.

A generated brief is not automatically correct.

A model annotation is not automatically approved.

A record existing in a dataset is not automatically ready for publication.

NotFinalCode therefore keeps different stages separate:

  • source material;
  • generated artifacts;
  • reviewed contracts;
  • public question data;
  • reference and audit information.

This adds work.

It also makes it much easier to understand where something came from and how much confidence I should place in it.

Validation

The function-based starter migration was tested across the full programming catalogue.

The recorded verification included:

  • 197 unit tests;
  • 80 Chromium coding scenarios;
  • 619 programming questions;
  • 4,525 public sample inputs;
  • production-built worker execution for Python, JavaScript and SQL examples;
  • TypeScript, lint and production build checks.

These numbers describe verification of specific revisions.

They do not prove that every programming problem in the library is universally correct.

That would be a much more exciting test suite.

What I care about most

The project started as a coding playground.

It has gradually become an experiment in several things I care about:

  • how much interface a tool actually needs;
  • how to make feedback useful without making it noisy;
  • how to safely execute code inside a browser;
  • where local AI is useful;
  • where AI should not be trusted;
  • how to keep generated data auditable;
  • how small interactions can make technical tools feel intentional.

It is still intentionally smaller than an IDE.

I want to keep it that way.

What comes next

One area I want to explore is better SQL feedback.

“Wrong” is not especially educational.

I would rather identify which parts of a requirement a query demonstrably satisfies and which parts still need work.

The idea is to connect specific phrases in a SQL brief to deterministic checks and progressively mark them as satisfied.

An LLM may help author those requirements offline.

It will not be trusted to decide them at runtime.

That feature is still an experiment.

Which is probably the appropriate state for something living on NotFinalDev.

Playground
notfinaldev.uncreativeprojects.com/code

Connected work