Inspiration

Over half of U.S. public high schools lack physical lab space for students, constituting a large gap in access to chemistry and other lab-based science education experiences, yet studies show that participation in physical labs increases student motivation and achievement in the subject. Existing lab simulations are 2D and nonimmersive, restricting free student inquiry and exploration. Furthermore, even at schools with lab spaces, students often enter a chemistry lab having memorized a procedure without understanding what their actions will do. Physical laboratory time is limited, mistakes can waste materials, and teachers usually discover misconceptions only after the experiment begins.

Most virtual labs solve this with fixed animations or step-by-step worksheets. I wanted something more active: a rehearsal space where students manipulate equipment, make meaningful mistakes, ask contextual questions, and build confidence before touching the real apparatus.

At the same time, teachers should not need to become simulation developers. They should be able to describe a learning goal in normal language and receive a lesson assembled from capabilities that the platform can genuinely support.

That led to Litmus: an AI-native browser laboratory where GPT-5.6 adapts pedagogy, but deterministic software remains the authority on chemistry.

What it does

Litmus provides two connected experiences for students and teachers.

Students can perform browser-based chemistry labs with interactive 3D equipment. Supported activities include acid/base titrations, quantitative precipitation, solution preparation, and mass-based dissolution calorimetry. Students can tare a balance, weigh solids, transfer liquids, read apparatus, observe changes, and see technique errors propagate into their results. Their actions are backed by the same deterministic experiment runtime based off real chemical calculations.

Meanwhile, a GPT-5.6 Student Coach—reminiscent of a helpful TA—is available to help guide the student through confusing patches and flag major student mistakes or safety concerns. It receives only bounded context derived from the scientific state of the lab, and is not able to mutate the lab environment or make up chemistry.

On the other side, teachers use Lab Composer to:

  • Describe a desired lesson.
  • Receive a GPT-5.6-authored structured proposal.
  • Edit objectives, equipment, actions, workflow rules, and assessments.
  • Inspect deterministic validation and runtime traces.
  • Review a separate advisory GPT-5.6 Judge critique.
  • Preview only workflows that pass the hard validation gate.

This way, teachers can customize the pre-lab or lab replacement assignments most informative to their own curriculum, and students can practice these labs whenever they want, however they want.

How we built it

Litmus uses Next.js, React, TypeScript, React Three Fiber, Zustand, Zod, Supabase, and the OpenAI Responses API.

┌── STUDENT'S BROWSER (Chromebook-class hardware) ─────────────────────────┐
│                                                                          │
│   React 19 + Next.js UI  ◄──projects state──┐   React Three Fiber        │
│   panels, readings, a11y text               │   3D bench (three.js),     │
│           │ typed actions                   │   on-demand render loop    │
│           ▼                                 │            ▲               │
│   Zustand session store ────────────────────┘            │               │
│           │                                              │               │
│           ▼                                              │               │
│   ┌── DETERMINISTIC LAB RUNTIME (TypeScript) ────────────────────────┐   │
│   │  registries  → closed catalog: equipment, actions, materials,    │   │
│   │                units, observables, placements                    │   │
│   │  mechanics   → reusable technique: pour, dispense, tare, weigh   │   │
│   │  chemistry   → acid–base, precipitation/Ksp, thermal/enthalpy    │   │
│   │  ledger      → integer-unit mass & volume conservation           │   │
│   │  replay      → bit-identical re-execution from the action trace  │   │
│   └──────────────────────────┬───────────────────────────────────────┘   │
│                              │ semantic events + observables             │
└──────────────────────────────┼───────────────────────────────────────────┘
                               │ HTTPS (batched checkpoints)
                               ▼
┌── VERCEL SERVERLESS FUNCTIONS (Next.js route handlers, Node) ────────────┐
│   auth + per-user rate-limit guard on every model-reaching route         │
│   /api/sessions/checkpoint · /api/coach · /api/evaluate                  │
│   /api/lab-composer/author/capability · /api/lab-composer/judge          │
└──────────┬──────────────────────────────────────────┬────────────────────┘
           ▼                                          ▼
┌── SUPABASE ────────────────────────┐   ┌── OPENAI RESPONSES API ────────┐
│  Postgres + Auth + Row Level       │   │  GPT-5.6                       │
│  Security                          │   │  Coach · Evaluator             │
│  sessions, events, skill_estimates │   │  Author Agent · Judge Agent    │
│  classes, assignments, definitions │   │  (structured outputs, typed    │
│                                    │   │   tools, no chemistry math)    │
└────────────────────────────────────┘   └────────────────────────────────┘

What each part of the stack does

Layer Technology Responsibility
Framework Next.js 16 / React 19 Static lab pages served from CDN; dynamic routes and API handlers as serverless functions
3D bench React Three Fiber + three.js Interactive glassware and instruments. Renders on demand rather than every frame, with a reduced-graphics tier for low-spec hardware
Client state Zustand Holds the session and projects engine state into React. Stores no chemistry of its own
Contracts TypeScript + Zod Typed boundaries everywhere; Zod validates API payloads, workflow definitions, and every model response before it is trusted
Engine Plain TypeScript The chemistry, conservation, evidence, and replay authority. No framework, no I/O, no clock, no randomness — which is what makes it replayable
Persistence Supabase (Postgres, Auth, RLS) Accounts, classes, assignments, and recorded evidence. Row Level Security scopes every read to its owner or class teacher
AI OpenAI Responses API (GPT-5.6) Language, pedagogy, authoring proposals, and advisory critique — via structured outputs and typed tools
Verification Vitest + Playwright 915 unit/integration tests plus real-browser flows

Responsibilities are split so that no model output can become scientific truth:

  • Deterministic chemistry models own pH, equilibrium, precipitation, conservation, temperature, and enthalpy.
  • Equipment adapters own reusable mechanics such as pouring, dispensing, taring, and weighing.
  • Workflow rules own procedure, evidence, and assessment requirements.
  • GPT-5.6 owns bounded pedagogy, structured authoring proposals, coaching language, and advisory critiques.
  • Teachers retain approval authority.

Codex accelerated the engineering workflow from the get-go, helping me build the entire app from scratch:

  • Convert architecture decisions into scoped implementation tickets.
  • Implement strict TypeScript contracts and deterministic solvers, building the chemistry and general lab workflow engine from the ground up.
  • Scope and build the Lab Composer feature: GPT-5.6 helped flesh out the idea, while Sol took care of almost all of implementation and migrating the initial hard-coded titration engine into a fluid and flexible setup.
  • Build registry, runtime, accessibility, and browser integration tests.
  • Diagnose a strict JSON Schema problem that prevented the Author Agent request from ever reaching OpenAI.
  • Trace full student workflows through the real browser UI.
  • Refactor large changes into reviewable commits.
  • Maintain documentation and test coverage as the architecture evolved.

GPT-5.6 Sol High was my most reliable partner throughout the process, and I sent it on long goal stretches often—sometimes reaching up to four hours long. And all the way in the browser, in the ideation phase, GPT-5.6 was incredibly helpful in scoping the project and giving ideas to actually execute the generic chemistry engine.

Challenges we ran into

The hardest part was rebuilding our chemistry engine so it could power more than one experiment. Our first lab was a hard-coded titration engine—the burette, flask, indicator, and pH curve were wired directly into each other. Every new experiment would have meant starting over, and teachers could never author their own labs, because there was nothing general to build with, directly contradicting the flexibility I hoped the app would have.

So, we pulled out what every lab shares—tracking materials, conserving mass and volume, mixing solutions—and rebuilt each area of chemistry on top of that base. Acid–base, precipitation, heat, and dilution became separate pieces that snap together. We did the same for lab technique, turning pouring, measuring, taring, and weighing into shared building blocks.

The risk was breaking the original already-working titration engine, so we kept the old engine running beside the new one and recorded exactly what it produced at every step. The new engine wasn’t marked finished until it reproduced that record exactly.

That comparison surfaced another challenge, which was the nature of tiny rounding errors in computation. The old engine reported an impossible pH at the moment a titration finishes. Our tests had missed it because they used neat round volumes, while the real app adds liquid drop by drop—simulating a real titration—and accumulates tiny rounding errors. We rewrote the calculation and started testing the messy path the app actually takes.

After all that, four experiments now run on one engine, and teachers have a verified set of building blocks for the AI to work with. That is what made AI authoring possible at all.

Deciding what the AI should not control came next. Letting a model invent chemistry would have been flexible but unsafe and impossible to replay. Because chemistry was now made of registered building blocks, we could let GPT-5.6 combine them while deterministic code checks and runs create the result.

Finally, making a 3D workspace accessible was harder than expected. Every drag and every visual change needed a keyboard path, a text equivalent, and an action underneath it.

Accomplishments that we're proud of

I’m incredibly proud of having executed the vision I initially arrived with. I would’ve loved Litmus in high school chemistry—a platform with an airy feel that almost resembles a videogame, but grounded in scientific truth; a platform that grants students the freedom to author their own lab experiences without having to be right in front of a real lab bench.

Also, the technical side was genuinely hard. A chemistry workflow general enough to describe many experiments, but exact enough to stay scientifically honest, turned out to be a much harder design problem than I expected. Every component needed precise rules about what it is, what it connects to, and what it may do—far denser logic than anything I had built before. Taking a problem that open-ended and turning it into something that actually runs, and is tested, is what I'm proudest of technically.

What we learned

It was my first time using a core aspect of the stack—the 3D rendering and state tracking with Zustand—and I learned that the hard part is not drawing the glassware, it is deciding what the scene is allowed to know. My instinct was to let the 3D components own their state: fill levels, colors, whether a stopcock is open. That falls apart quickly, because a render loop runs sixty times a second and React was never meant to be driven that way. What worked was treating the scene as a read-only projection—the engine owns every value, Zustand holds the session outside React, and the 3D components either read from it or move three.js objects directly. The scene ended up holding almost no state of its own, and it redraws only when something actually changes rather than every frame, which is also what keeps it usable on a school Chromebook.

I also learned that fallback behavior meant to make the app flow smoother can obscure integration failures. One hidden bug basically invalidated an entire feature: some of my structured JSON schema was rejected by GPT's strict mode before the request ever reached the model, and a silent fallback concealed the fact that no agent was running at all when the Lab Composer Agent was invoked.

I also learned a lot about using Codex well. It was most effective when I gave it clear architectural invariants, scoped tickets, and executable acceptance criteria. It dramatically accelerated implementation while the deterministic test suite kept decisions reviewable.

What's next for Litmus: AI-Native Browser Lab

Next, I want to expand the range of experiences available to users.

That includes:

  • More high-school chemistry procedures and equipment.
  • Richer teacher-controlled 3D lab bench configurations.
  • A shared library of teacher-authored and school-approved labs.
  • Assignment and classroom analytics workflows.
  • More accessible measurement and voice interactions.
  • Collaborative teacher revision workflows.
  • Performance improvements for low-cost Chromebooks.
  • Additional languages and curriculum mappings.

The long-term goal is not to replace physical laboratories, but to help improve access to valuable chemistry experiences for students and teachers that simply don’t have the means. I hope to scale this to an actual platform available for high schools in the US and beyond.

Built With

  • codex
  • gpt-5.6
  • next.js
  • openai-api
  • react
  • react-three-fiber
  • supabase
  • typescript
  • zod
  • zustand
Share this project:

Updates