Experimental Framework · Open Source · v0.1

A new
frontend
philosophy.

Flow-Arch/Vanilla is an exploration of what frontend development looks like when you take pure functions, declarative style, and Web Components seriously — with zero dependencies and zero compromise.

Why does this exist?

Modern frontend is drowning in abstraction. Frameworks grow larger, dependency trees deeper, and the distance between a developer's intent and the actual browser grows wider every year.

Flow-Arch/Vanilla asks a simple question: what if we stripped everything back? No virtual DOM. No build step. No framework runtime. Just the platform — and a set of disciplined ideas about how to organise it.

Flow-Arch turns complex engineering into standardized assembly. No magic, just logic. If it breaks, unscrew and replace.

This project is a long-term public exploration. Every demo, every experiment, and every failure is documented. The goal is not a production framework — it is a record of thinking.

PRINCIPLE_01

Pure Functions First

Every piece of logic that can be pure, must be pure. Side effects are real — but they belong at the edges, not the core.

PRINCIPLE_02

Declare, Don't Instruct

Describe what the UI should look like given a state. Don't write step-by-step DOM surgery. View = f(State).

PRINCIPLE_03

Zero Dependencies

Every dependency is a liability. The Web Platform is the framework. Use it directly and deliberately.

PRINCIPLE_04

One Direction, Always

Data flows one way. State changes only through explicit actions. No two-way binding. No hidden channels.

PRINCIPLE_05

Encapsulate with Shadow DOM

Components own their styles and structure. No CSS leaks. No global conflicts. Real isolation.

PRINCIPLE_06

SSR & SEO Ready

Web Components render on the server. Declarative Shadow DOM means no flash of unstyled content. The web should still be crawlable.

The Rules

These are not suggestions. They are the constraints that make Flow-Arch what it is. Constraints breed clarity.

R-01
State is a plain object

All component state lives in one plain JS object. No classes, no proxies, no observables. Just data.

R-02
State only changes through a Reducer

The reducer is a pure function: (state, action) → newState. No exceptions. No shortcuts.

R-03
View is a pure function of State

The view function takes state and returns HTML. It has no memory, no side effects, no access to the outside world.

R-04
Side effects live in the Component shell only

DOM manipulation, timers, fetch calls — all of this belongs in the Web Component lifecycle methods, never inside reducers or view functions.

R-05
Never mutate. Always return new.

Use spread operators and immutable patterns. The original state object is never modified — a new one is always returned.

R-06
No framework, no build step

Flow-Arch runs directly in the browser. No Webpack, no Vite, no transpilation required. ES Modules only.

R-07
Document every limitation honestly

This is an exploration, not a product. Known problems are recorded openly. Failure is data.

The Loop

Every Flow-Arch component follows the same loop. This is the entire mental model.

flow-counter.js — minimal example
// ─── 1. STATE MODEL (pure data) ───────────────────────
const createInitialState = () => ({ count: 0 })

// ─── 2. REDUCER (pure function) ────────────────────────
//   (state, action) → newState
//   Never mutates. Always returns new object.
const reducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT": return { ...state, count: state.count + 1 }
    case "DECREMENT": return { ...state, count: state.count - 1 }
    default:         return state
  }
}

// ─── 3. VIEW (pure function) ───────────────────────────
//   View = f(State)
//   State in → HTML string out. Nothing else.
const view = (state) => `
  <div class="count">${state.count}</div>
  <button data-action="DECREMENT">−</button>
  <button data-action="INCREMENT">+</button>
`

// ─── 4. WEB COMPONENT (side-effect boundary) ──────────
//   The only place where DOM mutation is allowed.
class FlowCounter extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: "open" })
    this.state = createInitialState()
    this.dispatch = (action) => {
      this.state = reducer(this.state, action)
      this.render()
    }
  }
  connectedCallback() {
    this.render()
    this.shadowRoot.addEventListener("click", (e) => {
      const type = e.target.dataset.action
      if (type) this.dispatch({ type })
    })
  }
  render() { this.shadowRoot.innerHTML = view(this.state) }
}

customElements.define("flow-counter", FlowCounter)

What Flow-Arch is built on

No invented abstractions. Every layer maps directly to a web standard or a mathematical concept.

Frontend · Structure
Web Components

Native custom elements. No polyfill. No runtime overhead. Just the platform.

Frontend · Isolation
Shadow DOM

True style and DOM encapsulation. Components are islands. No leaks possible.

Frontend · Logic
Pure Functions

Reducer + View are stateless, testable, deterministic. Inspired by Elm and Haskell.

Frontend · Data
Unidirectional Flow

State → View → Action → State. One loop. No exceptions. Predictable by design.

Backend · Runtime
Serverless / Edge

No server to maintain. No compatibility debt. Business logic lives in pure functions.

Backend · Core
Data Model First

The data schema is the real backend. Infrastructure is just plumbing around it.

Where this is going

This is a learning project. The roadmap is a record of intent, not a promise.

✓ Complete
Demo 00 — Counter

Minimal complete example. State + Reducer + View + Web Component. The loop in its simplest form.

✓ Complete
Demo 01 — Timer

Introduces async side effects (setInterval) and component cleanup lifecycle.

→ In Progress
Demo 02 — Props System

Exploring observedAttributes to pass initial state into components from HTML attributes.

Planned
Demo 03 — Component Communication

Custom events as the messaging layer between sibling components.

Planned
Demo 04 — Todo App

First complex state example. Lists, filtering, persistence.

Exploring
DOM Diff Strategy

Replacing innerHTML with a minimal diffing approach to avoid full re-renders.

Exploring
Serverless Backend Demos

Pure function APIs on Cloudflare Workers. Data model design without server complexity.

What this doesn't solve (yet)

Intellectual honesty is part of the methodology.
These are real problems, openly recorded.

More About
L-01
Full innerHTML replacement on every render

Currently the entire Shadow DOM is rebuilt on each state change. This is expensive for complex components and loses input focus. A diffing strategy is needed.

L-02
No inter-component state management

Components are currently isolated islands. There is no global store or event bus yet.

L-03
No async action handling

Reducers are synchronous. There is no middleware or effect system for async operations like fetch calls.

L-04
No enforced purity

Unlike Haskell or Elm, JS cannot enforce that reducers and view functions are truly pure. Discipline is required from the developer.