Repository navigation
Description
End goal (LEO 1.0)
A small, typed UI library where a React developer can port most components with mechanical changes, and where state lives in plain models instead of hooks. Components only render. No new jargon: you change data, the view updates.
Release gate: 1.0 ships only when LEO is a better alternative, not just a different one:
- Measured DX win: ported components need fewer concepts and less code than the React originals (M3 corpus).
- Measured performance: at or better than React on the standard benchmark (M2), with results published, including where we lose.
Principles
- Keep the three primitives and their API shape; models own state and logic, components read models and render.
- Plain words over invented terms:
setState,on,render, not hooks or dependency arrays. - JSX and
createElementcompatible so React markup ports unchanged. - Learn from the whole field, not only React (see below).
- Every claim (compatibility, speed, size) is measured before it is advertised.
Rename (decided)
The LEO prefix is redundant once imported from the package. Decision, based on the principles above (plain words, familiar to React users, names that state the data/component split):
| Today | Proposed | Notes |
|---|---|---|
LEOObject |
Model |
Says what it is: your state and logic |
LEOList |
List |
Ordered collection of models/values |
LEOElement |
Component |
Familiar to React users |
import { Model, List, Component } from "leo". Old names stay as deprecated aliases until 1.0, so nothing breaks. Lands in M1 together with the TS move.
Rejected: Store/View (Store implies one global store, View is less familiar to React users than Component); Signal/Atom style names (jargon, conflicts with the plain-words principle).
Learning from other frameworks
| Framework | Idea worth taking | How it maps to LEO |
|---|---|---|
| Solid, Preact Signals, Vue | Fine-grained reactivity: track what a render reads | Model Proxy records reads, only affected components re-render, no dependency arrays |
| Svelte 5 | Plain-language state, compiler optional | Keep the API words simple; evaluate a compile step only if benchmarks demand it |
| MobX | Observable models with actions and computed values | Computed fields and batched actions on Model |
| Vue | Approachable docs, progressive adoption | Docs structure, drop-in usage without a build step |
| Lit | Web components, small runtime | We already sit on custom elements; compare size and interop |
| Elm / Redux | Predictable state transitions | Optional action log / devtools on models |
Each idea gets a short write-up in docs/ before adoption: what problem it solves, cost, decision.
Stack decisions (proposed, 2018 stack is outdated)
| Today | Proposed | Why |
|---|---|---|
| Babel 6, webpack 4, gulp | TypeScript + tsup (lib) + Vite (examples) | Supported tooling, types for users |
| Custom elements polyfills | Drop them | Native in all evergreen browsers |
| Classic JSX pragma | Automatic JSX runtime (jsxImportSource: "leo") |
Same setup as React projects |
| mocha + jsdom | Vitest (+ browser mode with Playwright) | Real DOM for renderer tests |
Committed dist/ |
Built in CI, published to npm | No generated diffs in PRs |
| Node 8 era | Node 22+, ESM first | Current LTS |
Benchmarks
- Use js-framework-benchmark (the de facto standard): create/replace/update/swap/remove rows, startup, memory.
- Compare against React, Preact, Solid, Svelte, Vue, Lit and vanilla JS as a baseline.
- Also track bundle size (min+gzip) and a model-heavy scenario (shared state across many components) where LEO's design should help.
- Run in CI on a fixed setup; publish raw results and trends, regressions block merges.
Milestones
- M0 Foundation (LEO foundation: model-owned state, keyed rendering and familiar JSX #29): keyed reconciler, JSX/createElement, model
setState, lifecycle fixes, examples, CI. - M1 Stack + naming: TS, tsup/Vite, Vitest, automatic JSX, drop polyfills and committed dist, rename with aliases. Exit: same behavior, tests green, types emitted.
- M2 Benchmarks first: benchmark harness vs React, Preact, Solid, Svelte, Vue, Lit; record today's baseline. Exit: published baseline numbers.
- M3 Fine-grained updates: auto-tracked reads, batching, computed fields, guided by M2 data. Exit: measured improvement vs baseline, at or better than React.
- M4 Migration corpus: port real React components, measure lines and concepts changed. Exit: measured compatibility number and migration guide.
- M5 Model operations: async/loading/error state, list ops, forms. Exit: examples for each pattern.
- M6 Release: only if the release gate is met. npm package (owner action), docs site, versioning policy.
How this is run
-
Builder automation (weekdays 08:00 UTC): takes the next unchecked item here, opens one small
leo-roadmapPR at a time (tests first, mutation-verified), addresses review feedback (max 3 rounds). -
Reviewer automation (after CI on
leo-roadmapPRs, plus Mondays 09:00 UTC): verdict per PR head, labelsready-to-mergeand squash-merges ready PRs (green CI, nothing release-related), weekly evidence-based status comment here. It can suggest builder prompt changes, which only apply when the owner clicks Apply. -
Manager automation (daily 11:00 and 17:00 UTC): checks both automations' runs and PRs, nudges stalls, pauses an automation only on repeated failures or loops, keeps one status comment here updated, and pings the owner (
@basiclines needs you:) only for real decisions. -
Releases and removal of deprecated APIs go to the owner first.
-
The 22 old Dependabot PRs get closed once M1 replaces the lockfile.
-
Details:
docs/react-direction.md.
Activity