Honest Documentation
Known
Limitations.
This is a living record of what Flow-Arch cannot do well — yet. Every limitation is an open research question. Honesty about constraints is part of the methodology.
Each entry describes the problem, explains when it matters, shows any current workaround, and frames the open question it leaves behind. Limitations resolved through experiments will be updated here.
A limitation honestly documented is more valuable than a feature dishonestly promoted. If you find something missing from this list, that is a contribution.
| ID | Limitation | Status | Priority |
|---|---|---|---|
| L-01 | Full innerHTML replacement on every render | KNOWN | High |
| L-02 | No inter-component state management | KNOWN | High |
| L-03 | No async action handling | KNOWN | Medium |
| L-04 | No enforced purity (inherent to JS) | KNOWN | Low |
| L-05 | String-based view has no type safety | KNOWN | Medium |
| L-06 | No SSR implementation yet | KNOWN | Medium |
| L-07 | Component boilerplate repetition | KNOWN | Low |
Full innerHTML Replacement
on Every Render
What happens
Every time state changes, the entire Shadow DOM is destroyed and rebuilt from scratch.
render() { // Every render — full replacement // Old nodes destroyed. New nodes created. this.shadowRoot.innerHTML = view(this.state) }
Why it is a problem
The browser must destroy all existing nodes, parse the new HTML string, create all new nodes, recalculate styles, re-layout, and repaint — even when only one value changed.
// 1. Input loses focus on every render // <input> is destroyed and recreated — cursor position lost // 2. CSS transitions reset // transition: opacity 0.3s — resets every render because // the element is a brand new node each time // 3. Expensive for large lists // 100 todo items = 100 nodes destroyed + 100 recreated // on every single keystroke or tick
When it does not matter
Simple components updated on user click (counter, toggle, timer). DOM trees with fewer than ~20 nodes. No <input> elements inside the component.
Can we build a minimal DOM diff algorithm in pure vanilla JS, without dependencies, that still fits the Flow-Arch pure function philosophy? What is the minimum viable diffing strategy?
No Inter-Component
State Management
What happens
Each Web Component manages its own private state. There is no shared store or global state layer. Two sibling components cannot share data directly.
Current workaround
Use CustomEvent to bubble data up, and attributes to pass data down. It works but becomes verbose for complex trees.
// Child → Parent: dispatch a custom event this.dispatchEvent(new CustomEvent("state-changed", { bubbles: true, detail: { count: this.state.count } })) // Parent → Child: set attribute childEl.setAttribute("count", newCount) // This works. But with 5+ components it becomes // very hard to trace where data comes from.
Can a global store be designed as a pure function system, consistent with Flow-Arch's philosophy, without introducing a framework or complex observer pattern?
No Async Action Handling
What happens
Reducers are synchronous pure functions.
There is no built-in mechanism for async operations like fetch.
// ✗ This breaks the pure function contract const reducer = async (state, action) => { const data = await fetch("/api/data") // ✗ side effect return { ...state, data } } // ✓ Current workaround: async in the component shell async connectedCallback() { this.dispatch({ type: "LOADING" }) const res = await fetch("/api/data") const data = await res.json() // Then dispatch synchronously with the result this.dispatch({ type: "DATA_LOADED", payload: data }) }
Keeping reducers synchronous is not a bug — it is a deliberate choice. Async reducers make testing harder and blur the boundary between pure logic and side effects. The workaround above keeps the separation intact, but has no standard pattern yet.
What is the Flow-Arch equivalent of Redux-Thunk? Can a lightweight effect system be designed without middleware complexity, keeping reducers pure and async logic explicit?
No Enforced Purity
What happens
Unlike Haskell or Elm, JavaScript cannot enforce that reducers and view functions are truly pure. Side effects can be written inside them with no warning from the language.
// This compiles and runs fine — JS does not care const reducer = (state, action) => { console.log("side effect") // ✗ impure localStorage.setItem("x", "y") // ✗ impure return { ...state } // ✓ pure return } // Compare: Haskell makes this impossible at compile time // IO actions must be declared in the type signature // pure :: Int -> Int ← enforced pure // impure :: Int -> IO Int ← enforced impure
Honest assessment
This limitation is inherent to JavaScript. It cannot be fully solved without a type system or compiler. Flow-Arch accepts this and relies on discipline, documentation, and code review.
ESLint rule no-param-reassign catches mutation.
TypeScript readonly reduces mutation risk.
Neither fully solves the problem — they reduce it.
String-Based View Has
No Type Safety
What happens
The view function returns an HTML string via a template literal.
Typos in HTML structure fail silently. Typos in state properties
cause runtime errors. There is no autocomplete, no validation.
const view = (state) => ` <div class="contaner"> ← typo: silent failure <p>${state.usr.name}</p> ← typo: runtime TypeError <p>${state.userInput}</p> ← XSS if not sanitized </div> ` // Minimum protection: sanitize user input const escape = (str) => String(str) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") // Always escape before interpolating user data const view = (state) => ` <p>${escape(state.userInput)}</p> `
Could a lightweight tagged template literal function provide
automatic sanitization and basic validation,
without abandoning the pure function view pattern?
This could become part of flow-core.js.
No SSR Implementation Yet
What happens
Flow-Arch components are defined and rendered entirely in the browser. The page is blank until JavaScript runs. Search engines may not see component content.
The platform solution already exists
Declarative Shadow DOM allows Shadow DOM to be rendered as plain HTML by the server — no JavaScript required. Flow-Arch does not use this yet.
<!-- Rendered by server. Works without any JavaScript. --> <flow-counter> <template shadowrootmode="open"> <style>.count { color: #c8f542; }</style> <div class="count">0</div> <button>+</button> </template> </flow-counter> <!-- When JS loads, the component hydrates in place --> <!-- No flash of unstyled content -->
How does Flow-Arch's State → View → Reducer loop integrate
with server-side rendering and hydration?
Can the pure view() function generate both
the server HTML and the client render from the same code?
Component Boilerplate Repetition
What happens
Every Flow-Arch component repeats the same structural boilerplate. The unique logic of each component is buried inside identical scaffolding.
// This exact structure appears in every component class AnyComponent 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() } render() { this.shadowRoot.innerHTML = view(this.state) } } // Proposed: a factory that removes the boilerplate const FlowCounter = createFlowComponent(reducer, view) customElements.define("flow-counter", FlowCounter) // This is one of the most promising paths to flow-core.js
A createFlowComponent(reducer, view) factory function
could eliminate the boilerplate entirely.
This would be the first step toward a real flow-core.js.
What should its API look like?
Found a limitation not listed here?
If you tried something with Flow-Arch and it did not work well, that finding belongs in this document. A limitation honestly reported is a valid and valuable contribution.
Open a GitHub Issue with the label limitation,
describe what you tried and what failed.
No code required — a clear description is enough.