I like your current direction but I think it can be even simpler by using datastar. I feel like if people want Liveview they’ll just use that. The benefits of a framework like this would be:
- Minimalism
- No websocket needed
- Stateless or stateful, mix and match
I think with a few tweaks it could have a more declarative feel. You could even run everything through Nex.Store so you have that same “change state, the UI updates”. Imagine something like this:
defmodule MyApp.Pages.Todos do
use Nex.Page
def mount(_params) do
%{todos: Todos.all()} # this could automatically init the Store or it could be made more explicit
end
def render(assigns) do
~H"""
<h1>My Todos</h1>
<form data-on:submit="@post('/add_todo')" >
<input type="text" name="title" required />
<button>Add</button>
</form>
<ul id="todos">
<li :for={todo <- @todos}>{todo.title}</li>
</ul>
"""
end
def add_todo(%{"title" => title}) do
todo = Todos.create(title)
Store.update(:todos, &[todo | &1])
end
end
When the store is updated, it would handle sending the html via datastar’s patch_elements. The framework could use fat morph by default or maybe automatically use the append option if it’s a list. The convention would be that the store key should match the element id.
If someone didn’t want to use the store, they could still use Nex.patch_elements("#todos", …) directly though maybe that too could be simplified / renamed if you didn’t want to expose the implementation details.
A few of other ideas:
- Take it to the next level by making true optimistic UI easy (something Liveview lacks)
- Might be nice to be able to break free from file-based routing by using a
routemacro along the lines ofHologram. - Might be cool to be able to use
templefor an all-elixir feel (maybe this is already possible).






















