Skip to content

Roadmap: LEO 1.0 as a React alternative #30

Open

Description

@basiclines

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 createElement compatible 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-roadmap PR at a time (tests first, mutation-verified), addresses review feedback (max 3 rounds).

  • Reviewer automation (after CI on leo-roadmap PRs, plus Mondays 09:00 UTC): verdict per PR head, labels ready-to-merge and 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

  1. pinned this issue on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions