Back to the building5Level 5 · Sensei

Sensei (Master Splinter)

A personal trainer for React and TypeScript, with a compiler, a bundler and a test runner in one browser tab. A lesson only counts when every hidden test passes with zero type errors.

Role
Creator · spec, design, front end
Year
2026
Stack
React, TypeScript, Monaco, Sandpack
Code
GitHub
Sensei lesson screen: lesson list on the left, the brief with an illustrated diagram, the code editor, and the test panel
15 lessons, 3 tracks84 hidden tests0 type errors to pass
I

The brief

I've spent 10+ years building production websites, and I wanted to move from front-end web developer to front-end engineer. That meant getting fluent in typed React.

I learned React from The Road to React, which ran its exercises in a live code editor, so you knew right away if your code worked. I wanted that same loop for TypeScript and React, built around how I actually learn. Every good student needs a patient sensei, so I built one and named it after the most patient one I know of.

Fluent in typed React, not just familiar with it.The goal the whole trainer is built around.
II

How a lesson works

Every lesson runs the same five steps. Here is lesson 3, unions and narrowing, run against its real hidden tests.

  1. Read the briefShort directions, a diagram, and why it matters on the job.
  2. Fill the holesA starter file with TODOs, in the editor from VS Code.
  3. Watch it runReact lessons re-render a live preview as you type.
  4. Pass the testsA hidden suite checks behaviour you can see.
  5. Zero type errorsThe compiler catches what tests can't.
exercise.ts
// idle, OR success with data, OR error with a message.
// Never two at once.
export type RequestState = // TODO
  never;

export function describeRequest(state: RequestState): string {
  switch (state.status) {
    // TODO: one case per member
    default:
      return assertNever(state);
  }
}
export type RequestState =
  | { status: "idle" }
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

export function describeRequest(state: RequestState): string {
  switch (state.status) {
    case "idle":
      return "Waiting to start";
    case "success":
      return `Loaded ${state.data.length} item${state.data.length === 1 ? "" : "s"}`;
    case "error":
      return `Failed: ${state.message}`;
    default:
      return assertNever(state);
  }
}
Tests (hidden from you)
  • describes the idle state
  • describes success with a count
  • gets the singular right
  • describes failure
TypeScriptProperty 'status' does not exist on type 'never'no errors
Not yet. Try again.Lesson complete

Hints reveal one at a time, each more specific than the last. "Show solution" exists, but a lesson you watched is not a lesson you learned: reset it and redo it a day later.

III

The belt path

Fifteen lessons in a deliberate order, with an interview grading every few lessons.

Track 1 · 5 points a lesson

TypeScript foundations

The type system on its own, before React gets involved.
123 45
  1. 1Inference and the any trap
  2. 2Object types, optional fields, readonly
  3. 3Unions, narrowing, discriminated unions
  4. ◆Interview: TypeScript basics
  5. 4Generics: functions that keep their type
  6. 5keyof, typeof and the utility types
  7. ◆Interview: generics and derived types
Track 2 · 7 points a lesson

React with TypeScript

Components, state and effects, typed properly.
6789 101112 13
  1. 6Components and typed props
  2. 7useState, and the state you shouldn't store
  3. 8Events and controlled forms
  4. 9Lists, keys, and updating without mutating
  5. ◆Interview: React fundamentals
  6. 10useEffect with an honest request state
  7. 11useReducer with a discriminated action union
  8. 12Custom hooks: behaviour, not markup
  9. ◆Interview: effects, reducers and hooks
  10. 13Context without the undefined checks
Track 3 · 10 points a lesson

Engineering judgement

Why it re-rendered, and APIs that can't be misused.
1415
  1. 14Why it re-rendered: memo, useMemo, useCallback
  2. 15A component API that can't be misused
  3. ◆Interview: engineering judgement
Lesson: hidden tests plus zero type errorsInterview checkpoint: five in all

Nothing gets retired. The discriminated union from lesson 3 comes back for request state in lesson 10, reducer actions in 11 and component props in 15, and each lesson shows what it builds on as chips you can click back to.

IV

The notebook

The spec I wrote before the first line of code, and what I added after using it.

The spec

v1, in pencil
  • progress bar
  • short directions, no essays
  • hints, optional, one at a time
  • save my progress
  • lessons build on each other
  • a real-world reason for every lesson
  • points: $1 each, paid to myself
  • added after using it:
  • pause button, preview kept rebuilding mid-thought
  • hide the lesson list (⌘B)
  • Dock app, no terminal
  • diagrams, after a full day of code

The loop

what "done" means, drawn out type it squiggles? tests green? no: again both = done

same loop as The Road to React, but the compiler gets a vote

V

The bug log

Running a bundler, a test runner and a compiler inside one browser tab broke in ways that weren't obvious. Each fix is commented at the site in the code.

01

The test runner never started

Sandpack provider stuck in "idle"

Sandpack registers its bundler clients in a mount effect, and React's StrictMode mounts twice. That left the provider idle and deaf to its clients. The app runs without StrictMode, with a comment saying why.

02

Running tests wiped the preview

preview replaced by a test report

A client in test mode stops rendering your app. The preview and the test runner each get their own Sandpack provider.

03

Test runs hung halfway

hidden iframe, throttled timers

Browsers throttle timers in a display: none iframe. Inactive panels hide with opacity instead, so tests keep running out of sight.

04

The preview flickered forever

rebuild, test, rebuild, test...

Inline objects passed to Sandpack looked new on every render, and a finished test run set state, which rebuilt the sandbox, which ran the tests again. Memoising the files, setup and options broke the loop.

05

A colon in the folder name

vite: command not found

The project folder is "React:Typescript Sensei", and PATH is colon separated, so npm's usual trick split it in two. The scripts call node ./node_modules/... directly.

06

A Dock app that behaves like one

endless bounce; quitting killed Chrome's network

A shell script in an .app never tells macOS it finished launching, so it's a stay-open AppleScript applet. Stopping the server uses lsof -sTCP:LISTEN, so it kills the listener and not every browser connected to the port.

VI

The stack

React + TypeScriptThe app is written in the thing it teaches.
ViteFast dev server; the Dock app just starts it and opens a window.
MonacoThe VS Code editor, running the real TypeScript compiler in a web worker with React's actual type files. That's where the squiggles come from.
SandpackBundles your code for the live preview and runs the Jest-style hidden tests in the browser.
One file per lessonBrief, starter, hidden tests, solution, hints and "builds on" in one typed object. Adding a lesson is copying one.
localStorageProgress, points and half-written code survive a closed tab. No accounts, no server.
AppleScript launcherA double-click Dock app with loading bars, so I never open a terminal to study.
Claude CodeA study tool I built for myself. I wrote the spec and was its first student; Claude wrote most of the code, because typed React is what I was learning.
VII

Results

A personal tool with one student: me. No usage numbers, no users to count. Here is what it holds.

15lessons across 3 tracks
84hidden tests
5interview checkpoints
0type errors allowed to pass

Each point is $1 I pay myself: $101 for finishing all 15 lessons, $50 for all five checkpoints, and $5 for every day with 25 active minutes. Along the way: 6 non-obvious bugs, each fixed and commented in the code where it happens.

VIII

Looking back

What worked

Diagrams over more code. After a full day at a screen, a drawing of a storage chest beat another code sample. I'd also worked through The Road to React on Educative, so I started out knowing what I liked and what was missing. The live editor and the interview checkpoints came straight from it.

What I'd change

I'd make it work fully offline. The editor and type checker run locally, but the preview and tests use CodeSandbox's hosted bundler, so no internet means no green ticks. I'd also add a check that runs every lesson's solution against its own hidden tests, so a broken lesson can't ship.

What's next

More belts: a testing track where I write the tests instead of passing them, an accessibility track, and Next.js with server components, the stack I ship with. All three use the same one-file-per-lesson shape.