bartblast

bartblast

Creator of Hologram

Designing Local-First features for Hologram - what's your dream DX?

Hi everyone!

I’m working on designing Local-First features for Hologram and I’d love to hear your thoughts before I dive into implementation.

What do we mean by “Local-First”?

The term gets used in different ways, so let me clarify what I mean here. Local-First is an architecture pattern where the UI isn’t blocked by the network. In practice this means:

  • Instant UI - reads come from a local store, so there’s no spinner waiting for a server round-trip

  • Optimistic updates - writes apply immediately to the local state and sync to the server in the background

  • Offline resilience - the app keeps working when connectivity drops

  • The server still matters - it handles auth, conflict resolution, shared state, and remains the source of truth

It’s not about forcing all data to live on the client - it’s about making the user experience fast and resilient regardless of network conditions. How much data you keep locally is a design decision, not a requirement.

What I’m looking for

I’m in the early design phase and I’m genuinely open to ideas. I want to hear about your ideal developer experience - not what you think is technically feasible, but what you’d want it to look like if there were no constraints.

Some things I’ve been thinking about:

1. Declarative sync

How would you ideally declare which data should be available locally and how it syncs?

2. Conflict resolution

When two users (or a user and the server) make conflicting changes, what should that look like from the developer’s perspective? Should it be automatic, configurable, or something else?

3. Offline experience

What should happen when the app goes offline and comes back? How much of this should the framework handle transparently vs. giving the developer control?

4. Inspiration from other tools

Have you used any local-first or sync engine solutions (in any ecosystem - Zero, Electric SQL, PowerSync, Automerge, LiveStore, etc.) that had a great DX? What made it great?


Don’t hold back - the bolder the idea, the better. Even if something seems unrealistic, it might spark a direction I haven’t considered.

Looking forward to the discussion! :slight_smile:

First 10 of 32 Posts Switch mode

LostKobrakai

LostKobrakai

I’d suggest you to look at tanstack db. They’re kinda building what you’re describing for the javascript ecosystem.

bartblast

bartblast OP

Creator of Hologram

TanStack DB is definitely on my radar - they’re doing some very interesting work there!

derek-zhou

derek-zhou

I’d say neither the framework nor the application developer should handle offline events, because “offline” is hard to define. Websocket broke? No heartbeat received in last XX seconds? All have their caveats, and in the case of mobile internet, the server can be flooded with such events. Because of this, I’d advocate against of relying on websocket, but that’s probably not what you want to hear. You did say don’t hold back though.

jam

jam

In general, I think the dream DX is just write queries and mutations and Hologram makes sure the data stays in sync. Ideally these would be no different than how you’d usually write queries and mutations in Elixir and you’d opt out of optimistic updates if needed. Hologram would also handle prefetching queries to help everything feel instant.

I think Zero, Instant, and Convex have the philosophy of “it’s just queries and mutations”. They all have their own query language to achieve it though, and for the latter two, I think they own the db as well. Meteor.js also had a similar philosophy and allowed you to write Mongo queries on server and client with some small differences. Zero improves upon this by letting you write the query once even though I don’t love their custom syntax. For Hologram, I think a similar DX to Zero but “it’s just standard Elixir” could be something to aspire to. I’m not sure that their SQLite cache that sits between Postgres and the client would be required to pull it off. The issues I see with the other attempts are either:

  1. They require hoop jumping. For example, TanstackDB paired with ElectricSQL requires defining things in certain ways on the server, then optionally filtering in a middle layer, to get the data onto the client. From there, you need to write queries again in TanstackDB’s query language. Not to mention a pretty convoluted mental model for the write path. I’m glad it exists but I don’t think it offers a DX to aspire to. I also wonder if their differential dataflow complexity can be avoided if using signals instead of assuming a re-render loop a la React. Then again if the data set is small enough it doesn’t matter and you’ll fit well within 16ms with any change regardless.
  2. They require opting into a completely different paradigm, e.g. Automerge, jazz.tools lean on CRDTs heavily which imo are not needed in a server reconciliation model and only become critical if dealing with collaborative document editing (or maybe not). In LiveStore’s case it uses event sourcing which sounds great in theory but seems overkill for most apps. There is something nice conceptually about event sourcing and deriving views but the complexity of making it all work seems not worth it. Maybe there’s a way to make it “just work” and not have the typical downsides come back to bite you.

Now to keep things a bit more manageable, I think Hologram should focus on supporting Last Write Wins to start and maybe as it’s only reconciliation technique. Otherwise, I think complexity explodes in what is already fairly complex.

For offline, queue the writes and replay when back online. The replays go through the same mutation business logic so if something has changed that would prevent their write, it’ll just get rolled back. There should also probably be a config to determine when a full sync is required, e.g. after X days offline with a default that won’t get people into too much trouble, maybe 1 day.

Like Zero, I think you could constrain the types of supported queries in a first release and potentially expand over time. For example, I think Zero still doesn’t support aggregates.

esambo

esambo

Local first would be awesome for LiveView Native, as native app users are used to instant responsiveness.

jam

jam

As an aside, it would be amazing if one day you could pair Hologram and @garrison’s Hobbes if the stars align.

esambo

esambo

Some iOS apps offer a sync feature that makes this local first concept more explicit, where the apps is first and foremost a local app, and sync is secondary. Some apps that come to mind are:

  • Day One (offers a “Reset Sync” in the app which indicates that the source of truth is the cloud version. It is also very explicit about the Sync Status. Each item also has an Entry Version History)
  • Strongbox (allows user to explicitly define what the Source Device (and then resolve any merge conflicts accordingly, which isn’t too bad, as each entry has a history versions exposed in the app). It also offers a Lazy Sync.)

And then there are web apps that offer an offline mode:

  • Google Docs, MS Office, Zoho, Proton, WPS

Git as probably also a good example of different merge conflict strategies, and 3-way-merge.

bartblast

bartblast OP

Creator of Hologram

You’re right that “offline” is hard to pin down. I think the sync engine still needs some form of connectivity awareness at the framework level though. If a sync attempt fails, the framework needs to know when to retry. Whether that’s periodic retries with backoff, reacting to the connection being re-established, or some combination - I’m not sure we can completely avoid the problem you’re describing. With a declarative data model this was always going to be the framework’s responsibility, not the developer’s - but you’re right that it’s a hard problem to solve well.

By the way, Hologram doesn’t use WebSockets - it communicates via standard HTTP requests with a keep-alive mechanism. But the challenge applies regardless of transport.

What approach would you lean toward?

derek-zhou

derek-zhou

I agree with you; the client side needs to be aware of the connectivity situation. On the other hand, the server side should refrain from doing anything special when a client is perceived “offline”.

I was under the impression that Hologram use websocket, just not Phoenix Socket. Maybe I was hallucinating.

bartblast

bartblast OP

Creator of Hologram

Thanks @jam, really appreciate the detailed thoughts!

I agree with a lot here, especially the “make it invisible” direction - that’s exactly the bar I’m aiming for. Code should work the same way on both client and server, and developers shouldn’t need to think about what’s local vs. remote.

One place I’m thinking differently though is query-based sync. Syncing queries couples the sync layer to the UI - every time a component changes what it displays, you’re renegotiating what lives on the client. I’d rather sync underlying data based on declarative authorization rules. You define once what data a user/role gets locally (you need those rules for security anyway), and then components just query freely against whatever’s available. That’s where the real “just write queries” DX comes from I think - not because queries drive the sync, but because sync is already handled underneath.

On the query side - I definitely don’t want to invent a new language. But I’d like to explore some ideas from Gel (previously EdgeDB), their composability is way ahead of SQL. Probably more of a query API than a language, something that feels natural in Elixir.

As for event sourcing - I’m not ruling it out. Maybe there’s a lightweight version that collapses to simple atomic operations in most cases, giving you the “what happened” benefits without the usual complexity. Still weighing that against going straight to LWW.

And yeah, fully agree on starting constrained. Ship a small subset of the most needed operations first, validate the DX, expand from there.

Great discussion, keep the ideas coming!

Where Next?

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 91561 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement