
The JavaScript ecosystem has a magic problem. Not the fun kind. The kind where you stare at your...
The JavaScript ecosystem has a magic problem.
Not the fun kind. The kind where you stare at your code, everything looks correct, and something still breaks in a way you can't explain. The kind where you spend forty minutes debugging why your computed() stopped updating, or why an effect fired when you didn't expect it, or why destructuring a store value makes it stop being reactive.
We called it reactivity. We called it signals. We called it runes. And every new name comes with a new layer of invisible machinery running underneath your code, doing things you didn't ask for, breaking in ways you didn't anticipate. The deeper problem isn't performance or verbosity — it's locality of reasoning. You can't look at a line of code and know when or why it will execute.
I've been building complex web applications for eighteen years — interactive dashboarding systems, industrial HMI interfaces, config-driven UIs. Those projects are the reason this framework exists: not because they failed, but because I could see exactly how they would age. I got tired of the magic. So I built something without it.
This isn't the Nth framework built out of frustration. It's a deliberate synthesis of ideas that already proved themselves: Redux's three principles, the Entity Component System architecture from game engines, and lit-html's surgical DOM updates. None of these are new. What's new is putting them together and following the logic all the way through.
I really started digging React when they introduced Redux. It revamped Functional Programming concepts as good practices for large-scale systems — proving they belonged in production code, not just CS theory. Three principles made any webapp predictable, debuggable, and testable as never before:
But things went south. Devs complained that Redux was too verbose, immutable updates were painful, async logic was a hack. Those complaints were valid — the boilerplate was a genuine tax. Enter RTK, which solved real problems: simpler reducers, built-in Immer, sane async thunks. But then it kept going — createAppSlice, builder callback notation, circular dependency nightmares. The question isn't whether Redux needed fixing. It's whether the fixes took things in the right direction. Then the "Single Source Of Truth" dogma started bending entirely: local state here, Context there, Zustand, Jotai, signals. We write less code now, and it just magically works. Well — not for me.
Let me be specific, because "magic is bad" is an easy claim to make and a hard one to defend without evidence.
React re-renders are actually fast — React was right about that. The real problem is that re-renders trigger effects and lifecycle methods. useEffect fires after every matching render, subscriptions re-initialize, derived state recomputes. Invisible dependency arrays silently break when you forget something, and useEffect lists grow into things nobody on the team fully trusts. React's answer? A stable compiler that adds layers of cache automatically. Which means you can have a suboptimal component hierarchy and the compiler will compensate — which is convenient until you need to understand why something broke.
Vue 3 introduced a subtle trap with the Composition API: destructuring a reactive object silently breaks the proxy chain that powers reactivity. Your variable stops updating and you get no warning whatsoever. Vue provides toRefs() specifically to patch this — which proves the point: you now have to manage the integrity of an invisible system on top of writing your actual application. And computed() knows when to recompute by secretly tracking which reactive properties you accessed while it ran, which can produce circular dependencies that only blow up at runtime.
Svelte 5 introduced runes — $state(), $derived(), $effect(). The docs themselves define the word:
rune /ruːn/ noun — A letter or mark used as a mystical or magic symbol.
It's impressive engineering. But unlike JSX — which is a purely syntactic transformation — Svelte's compiler is semantically active: it changes what your code means, not just how it looks. $state() isn't JavaScript with nicer syntax; it's a different programming model that requires the compiler to be correct.
All three are racing in the same direction: more reactivity, more compilation, more invisible machinery. I went the other way.
Inglorious Web is built on one idea: state is data, behavior are functions, rendering is a pure function of state.
No proxies. No signals. No compiler. Just plain JavaScript objects, event handlers, and lit-html's surgical DOM updates. The mental model is a one-time cost, not a continuous tax — you learn it once, and it scales without adding new concepts.
const Counter = {
create(entity) {
entity.value = 0;
},
increment(entity) {
entity.value++;
},
render(entity, api) {
return html`
<div>
<span>Count: ${entity.value}</span>
<button @click=${() => api.notify(`#${entity.id}:increment`)}>
+1
</button>
</div>
`;
},
};
It looks like a hybrid between Vue's Options API and React's JSX. If you prefer either of those syntaxes, there are Vite plugins for both. But the key differences are in what's absent. There are no hooks, no lifecycle methods, no component-level state. create and increment are plain event handlers — closer to RTK reducers than to React methods. The templates are plain JavaScript tagged literals: no new syntax to learn, no compilation step required. Boring doesn't mean verbose — it means every line does exactly what it says.
One deliberate abstraction worth naming: state mutations inside handlers look impure but aren't. The framework wraps them in Mutative — the same structural sharing idea as Immer, but 2–6x faster — so you write entity.value++ and get back an immutable snapshot. That's the only reactive magic in the stack, it's a small and well-understood library, and it's what makes testing trivial.
When state changes, the whole tree re-renders. But lit-html only touches the DOM nodes that actually changed — the same way Redux reducers don't do anything when an action isn't their concern. Re-rendering is cheap. Effects and lifecycle surprises don't exist. The question "why did this effect fire?" is simply impossible to ask, because you can look at any handler and reason about exactly when it runs. And because every state transition is an explicit event, you can grep for every place it's fired — something you cannot do with a reactive dependency graph.
In React, testing a component with hooks means setting up a fake component tree and mocking the world around it. In Vue 3, testing a composable means testing impure functions swimming in proxy magic.
In Inglorious Web, testing state logic is this:
import { trigger } from "@inglorious/web/test";
const { entity, events } = trigger(
{ value: 10 },
Counter.increment,
5,
);
expect(entity.value).toBe(15);
And testing rendering is equally straightforward:
import { render } from "@inglorious/web/test";
const template = Counter.render(
{ value: 42 },
{ notify: vi.fn() },
);
const root = document.createElement("div");
render(template, root);
expect(root.textContent).toContain("Count: 42");
// snapshot testing works too:
expect(root.innerHTML).toMatchSnapshot();
No fake component tree. No lifecycle setup. No async ceremony. Because render is a pure function of an entity, and a pure function is just a function you call.
React, Vue, and Svelte are component-centric. The component is the unit. Logic lives in components, state is owned or lifted by them, everything is a tree.
Inglorious Web is entity-centric. Your application is a collection of entities — pieces of state with associated behaviors. Some entities happen to render. Most of the time you don't think about the tree at all.
If you've heard of the Entity Component System (ECS) architecture used in game engines, this will feel familiar — though it's not a strict implementation. Think of it as ECS meets Redux: entities hold data, types hold behavior, and the store is the single source of truth. The practical consequence is that you can add, remove, or compose behaviors at the type level without touching the UI, and you can test state logic in complete isolation from rendering. That's not just less magic — it's a different ontology.
This is the first post in a series.
In the next post, I'll go deeper into the entity-centric architecture: how types compose, how the ECS lineage maps to real web UI problems, and whether the mental model holds up at scale — from a TodoMVC to a config-driven industrial HMI. I'll also be honest about the ecosystem, the tradeoffs, and where the framework fits and where it doesn't.
In the third post, I'll show the numbers: a benchmark running 1000 rows at 100 updates per second, comparing React (naive, memoized, and with RTK), and a live chart benchmark against Recharts. Performance, bundle size, and what "dramatically smaller optimization surface area" actually looks like in practice.
The ecosystem is moving toward more magic. I'm moving the other way.
gemmaI ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...
communityHey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...
ai(yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...
aiMy laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...
githubactionsI Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...
aiI've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...
Workflows from the Neura Market marketplace related to this DeepSeek resource