<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="373749" data-post-id="373749">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Eiji" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Eiji/120/36743_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Eiji
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="bartblast" data-post="10" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>All fascinating approaches, but I should clarify - I’m specifically looking for Pub/Sub (Publish/Subscribe) patterns. Think broadcasting messages across channels/topics like Phoenix Channels and Phoenix PubSub, but reimagined for ideal DX.</p>
</blockquote>
</aside>
<p>The <code>PubSub</code> works on <code>self()</code> process, so let’s say the <code>holo_subscribe</code> would be a short for sending a special message that instead of be send to action or command would call <code>PubSub.subscribe/3</code>. Also the <code>Registry</code> would need to be fully implemented in <code>Hologram</code>’s JS.</p>
<p>Anyway in very short <code>PubSub</code> would work rather easily based on my proposal. I have mentioned something more generic to give a easy way for non-<code>PubSub</code> implementations. There are lots of stuff working around <code>Elixir</code> processes, so sending and sending after are the absolute must haves here …</p>
<aside class="quote no-group" data-username="bartblast" data-post="10" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>We could broadcast to “all users in room X,” “session Y,” or “component Z on any device.”.</p>
</blockquote>
</aside>
<p>Oh well, very interesting. First would be a simple <code>broadcast/4</code> equivalent, the second would be something like a <code>local_broadcast/4</code> limited to current user and the third would be again a <code>broadcast/4</code> equivalent limited to current user and component.</p>
<p>Those specific targets are interesting, but I would also add <code>broadcast "on any device", so </code>broadcast/4` equivalent limited to current user. I would add 4 different function names with same prefix to make those calls clear and explicit, something like:</p>
<ol>
<li><code>broadcast</code> (all users in room X on all nodes)</li>
<li><code>broadcast_session</code> (session Y)</li>
<li><code>broadcast_user</code> (on any device)</li>
<li><code>broadcast_user_component</code> (component Z on any device)</li>
</ol> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373749" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-373749" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373749"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="373760" data-post-id="373760">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="LostKobrakai" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/LostKobrakai/120/3072_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  LostKobrakai
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="garrison" data-post="11" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/g/3bc359/48.png" class="avatar"> garrison:</div>
<blockquote>
<p>I have experimented quite deeply with building “realtime” apps using Phoenix PubSub in the standard way and I have come to the conclusion that it’s just not good.</p>
</blockquote>
</aside>
<p>I think the problem here is level of abstraction.</p>
<p>PubSub is not a means of “when provided you have all you need for realtime UI”. PubSub is a low level message passing primitive, which can be used for lots of things. Realtime UI can use PubSub, but it likely won’t be great unless you layer more architecture or tooling on top of it. There’s also quite a bit of making tradeoffs involved. E.g. phoenix presence was layered on top of phoenix presence, but it’s quite explicit about being eventually consistent and just being able to hold reasonable amounts of metadata per listed user.</p>
<p>Incremental view maintenance (IVM) on the other hand is a much higher level tool for a much more narrow usecase. It’s awesome to get incrementially updating UI and I’d argue it’s a way of building UI with less footguns, but it’s also not really a holy grail either. It’s quite useful for what it does, but e.g. electric sql only uses it on the client between it’s local db and live queries. Syncing the db state to the client happens by syncing transaction events (kind of a WAL).</p>
<p>So I guess my first question here would really be what hologram understands as “realtime UI”. At least when coming from a POV of what’s going on in JS land, there’s currently a lot of work going into CRDT based systems, where the server becomes a dumb sync server, the whole data transfer becomes completely abstracted away and you essentially just trust the system to give you the correct data client side. That’s not at all the level where something like PubSub would sit.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373760" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-373760" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373760"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="373797" data-post-id="373797">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="jam" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jam/120/36266_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  jam
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I tend to agree with garrison’s take but maybe someone can change my mind. I’ve been working with phoenix pubsub and channels to wire up a svelte frontend. And while it works, the DX leaves a lot to be desired. I’ve created my own abstractions to make it nicer to work with and I think Hologram could do something similar / take it to the next level which is why I suggested live queries as a potential path instead of working with lower-level <code>subscribe</code> and <code>broadcast</code>. Of course it would be a lot more work and maybe it doesn’t belong in Hologram core but it would make Hologram even more compelling if it were imo.</p>
<p>If there is a compelling reason to surface lower-level pubsub, then I think all you’d need are:</p>
<ul>
<li><code>subscribe</code></li>
<li><code>broadcast</code></li>
<li><code>broadcast_from</code></li>
<li><code>broadcast_to</code> - can be used to send to the current user only or something else specifically</li>
</ul>
<p>Maybe subscribe should include some way to authorize. I’m not sure if you’re thinking authorization would be elsewhere.</p>
<aside class="quote no-group quote-modified" data-username="LostKobrakai" data-post="13" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lostkobrakai/48/3072_2.png" class="avatar"> LostKobrakai:</div>
<blockquote>
<p>in JS land, there’s currently a lot of work going into CRDT based systems, where the server becomes a dump sync server, the whole data transfer becomes completely abstracted away</p>
</blockquote>
</aside>
<p>I’m not sure I buy into the use CRDT for all the things. I think using server reconciliation (used by Zero and apps like Linear) makes more sense for most apps. CRDTs are no doubt useful for collaborative text editing. But again maybe someone can enlighten me.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373797" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-373797" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373797"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="373798" data-post-id="373798">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="LostKobrakai" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/LostKobrakai/120/3072_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  LostKobrakai
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="jam" data-post="14" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jam/48/36266_2.png" class="avatar"> jam:</div>
<blockquote>
<p>I’m not sure I buy into the use CRDT for all the things. I think using server reconciliation (used by Zero and apps like Linear) makes more sense for most apps. CRDTs are no doubt useful for collaborative text editing. But again maybe someone can enlighten me.</p>
</blockquote>
</aside>
<p>I’d argue that it doesn’t matter in the end. All a user cares for it that somehow and with some guarantees data becomes available. The hard parts are handled by the system. But that also means the system becomes a harder thing to implement.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373798" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-373798" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373798"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="373800" data-post-id="373800">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="jam" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jam/120/36266_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  jam
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Sure if someone can say here’s some magic that “just works”, that sounds wonderful in theory. However, I think there is still a lot to be determined, e.g. permissions. There’s also the issue of CRDTs will converge, but the result may not be what you’d hoped. I think for a wide range of apps, you’re going to need a central server authority, at which point server reconciliation is the easier and more flexible path imo. But happy to be proven wrong so that I can offload complexity <img src="https://forum.elixirforum.com/images/emoji/apple/slightly_smiling_face.png?v=15" title=":slightly_smiling_face:" class="emoji" alt=":slightly_smiling_face:" loading="lazy" width="20" height="20">.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373800" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-373800" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373800"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="373802" data-post-id="373802">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="bartblast" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/120/17647_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  bartblast
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Hologram</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I might be misunderstanding something here, or maybe there’s just a disconnect. For now, my focus is on a simple pub/sub primitive that enables sending and broadcasting messages.</p>
<p>It feels like the conversation is jumping straight into high-level application patterns such as data synchronization. While pub/sub can certainly play a role there, it’s not the same thing. I suspect part of the disconnect comes from past experiences with Phoenix PubSub, likely when trying to implement custom data sync solutions.</p>
<p>For proper data sync, we’ll almost certainly need more than just pub/sub. Pub/sub might be one building block, but it won’t be sufficient on its own - it could even lead us toward building something like a lightweight client-side database on top of OPFS. I’m not sure yet. I still need to explore the broader local-first space. But what is clear is that raw pub/sub alone can’t cover data synchronization.</p>
<p>That said, Hologram should still have pub/sub. It’s broadly useful. For instance, it wouldn’t surprise me if Electric SQL used pub/sub under the hood to sync transaction logs. Differential Dataflow’s operator coordination also looks a lot like pub/sub in concept. And even Zero/IVM systems rely on some messaging layer to propagate changes.</p>
<p>The likely endgame is a layered architecture:</p>
<ul>
<li><strong>Pub/Sub Layer</strong> (what I’m working on now): reliable message delivery, topic management, subscription handling</li>
<li><strong>Coordination Layer</strong>: consistency, watermarks, transaction boundaries</li>
<li><strong>Application Layer</strong>: live queries, state sync, real-time UI updates</li>
</ul>
<p>Even if pub/sub isn’t the backbone of the eventual data sync design, it will still serve plenty of other purposes - component-to-component messaging across devices, system events, escape hatches, and more.</p>
<p>Some cases where plain pub/sub shines:</p>
<ul>
<li>Event notifications where consistency isn’t critical (user joins, deployment complete, toast notifications)</li>
<li>Real-time updates that don’t require data integrity (progress ticks, typing indicators, mouse tracking in multiplayer)</li>
<li>Anything idempotent or ephemeral, where lost or duplicated messages don’t cause problems</li>
</ul>
<p>So for now, I’d like to explore possible API/DSL patterns for this, including auth as well.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373802" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-373802" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373802"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #16"></div>
  </section>
</div>
    <div class="postbit" id="373805" data-post-id="373805">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="ken-kost" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ken-kost/120/40209_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  ken-kost
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I trust your ideas will be great, since the DX for hologram in general feels great. My use case would be your example of svg drawing from 0.5.0 release - I want to broadcast changes to other players in the room. I’m making a scribble game in your framework - it’s fun! This is my missing piece. <img src="https://forum.elixirforum.com/images/emoji/apple/ogre.png?v=15" title=":ogre:" class="emoji" alt=":ogre:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373805" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-373805" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373805"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #17"></div>
  </section>
</div>
    <div class="postbit" id="373808" data-post-id="373808">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="garrison" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  garrison
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="bartblast" data-post="17" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>That said, Hologram should still have pub/sub.</p>
</blockquote>
</aside>
<p>Actually, why should <em>Hologram</em> have PubSub? Come to think of it, why does <em>Phoenix</em> have PubSub? Why don’t we just have “Elixir PubSub”?</p>
<aside class="quote no-group" data-username="bartblast" data-post="17" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>The likely endgame is a layered architecture</p>
</blockquote>
</aside>
<p>I like your layers, but I think they’re the wrong way around. Transactions and consistency are the foundation, and the application layer which consumes them is on top of that.</p>
<p>PubSub is fanout, and is an (optional) final layer that one might use to scale out for a particular use-case.</p>
<p>In the common case, a client would subscribe directly to the part of the database they are interested in consuming (their own tenant space). This is <em>not</em> a “topic” subscription, this interaction <em>must</em> be with the database because only the database understands how its keyspace is sharded.</p>
<p>In the less common “fanout” case, an (internal) client would subscribe to a popular keyspace and then fan that out to PubSub subscribers on some sort of topic. But this is <em>not the foundational API</em>, because this is not the common case. Most of the time most users will subscribe only to things <em>they</em> are interested in, and again PubSub is a poor primitive for this behavior because it has no interaction with the sharding mechanism and would become a scaling bottleneck.</p>
<p>This is analogous to streaming a particular show vs viewing a live sports game. I think most apps are the former, though I’m sure some constitute the latter.</p>
<aside class="quote no-group" data-username="bartblast" data-post="17" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>Event notifications where consistency isn’t critical (user joins, deployment complete, toast notifications)</p>
</blockquote>
</aside>
<p>I think consistency is critical for all of these things. It’s critical for <em>nearly</em> everything, but we’re all so used to the extraordinary negligence of mainstream databases that we’ve come to accept living without it.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373808" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/19">Post #18</a>
	                </div>
	            </div>
              <div id="likers-container-373808" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373808"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #18"></div>
  </section>
</div>
    <div class="postbit" id="373809" data-post-id="373809">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="garrison" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  garrison
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="bartblast" data-post="17" data-topic="72519">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bartblast/48/17647_2.png" class="avatar"> bartblast:</div>
<blockquote>
<p>Even if pub/sub isn’t the backbone of the eventual data sync design, it will still serve plenty of other purposes - component-to-component messaging across devices</p>
</blockquote>
</aside>
<p>I missed this line and I want to respond to it specifically because it highlights why the points I’m trying to make here are actually on topic (I swear!).</p>
<p>If you are sending messages between components like “user created a post!” then you have already lost. This is actually exactly the same problem that came up in that thread we had on two-way binding, except in this case it’s over the network.</p>
<p>What you want is top-down, one-way dataflow. In some cases this may be local (like React controlled components), and in some cases it may be flowing from your central database into the client’s components. But it <em>must be one-way</em>. A component should update the database, and the database should update the other component. If you start going down this path of “I’ll just send a notification for this one little thing” you will get bugs and spaghetti. I have felt this pain. I think we all have.</p>
<p>Now CRDTs and local-first do fit into this discussion (the database becomes “local”) but I don’t want to descend into that whole thing. I’m only trying to make this one point <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373809" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/20">Post #19</a>
	                </div>
	            </div>
              <div id="likers-container-373809" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373809"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #19"></div>
  </section>
</div>
    <div class="postbit" id="373957" data-post-id="373957">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="PaulOlukayode" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/PaulOlukayode/120/38640_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  PaulOlukayode
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Well, Pub/Sub is interesting. Check out <strong>my Stanza for the messaging protocol</strong> I am working on—you can refer to my documentation. Pub/Sub <strong>cannot be used for one-to-one chats</strong>, but it can be used for <strong>typing notifications</strong> and <strong>recording events</strong>. One-to-one chat requires a <strong>registry and an offline queue system</strong> to detect if a user is online.</p>
<p><strong>BIMip</strong> is the messaging protocol we are developing. It leverages <strong>Cowboy</strong> and <strong>:gen_tcp</strong> for both native and socket connections. We implemented a custom <strong>adaptive ping-pong mechanism</strong> to maintain long-lived connections. You can reach me via WhatsApp at <strong>+234 902 455 0724</strong>.</p>
<p>The truth is, <strong>Elixir gives you the power to build almost anything</strong>. <a href="https://github.com/olukayodepaul/BIMip.git" class="inline-onebox" rel="noopener nofollow ugc">GitHub - olukayodepaul/bimips: Documentation for Binary Interface for Messaging &amp; Internet Protocol · GitHub</a></p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Util.Network.AdaptivePingPong do
  @moduledoc """
  Handles network-level PingPong for child GenServers.

  Features:
    - Tracks missed pongs and RTT per device
    - Dynamically adjusts ping frequency and max missed pongs
    - Schedules next ping automatically
    - Integrates with DeviceStateChange to trigger online/offline state updates
    - Terminates after exceeding max allowed delay with no pong
  """

  require Logger
  alias Settings.AdaptiveNetwork
  alias App.RegistryHub
  alias Util.DeviceState

  @max_pong_counter AdaptiveNetwork.max_pong_retries()
  @default_ping_interval AdaptiveNetwork.default_ping_interval_ms()
  @max_allowed_delay AdaptiveNetwork.max_allowed_delay_seconds()

  # -------------------------
  # Handle periodic ping
  # -------------------------
  def handle_ping(state) when is_map(state) do
    missed = Map.get(state, :missed_pongs, 0)
    counter = Map.get(state, :pong_counter, 0)
    last_ping = Map.get(state, :timer, DateTime.utc_now())
    eid = Map.get(state, :eid)
    device_id = Map.get(state, :device_id)
    ws_pid = Map.get(state, :ws_pid)
    last_rtt = Map.get(state, :last_rtt, nil)
    max_missed = Map.get(state, :max_missed_pongs_adaptive, AdaptiveNetwork.initial_max_missed_pings())
    now = DateTime.utc_now()
    delta = DateTime.diff(now, last_ping)
    last_state_change = Map.get(state, :last_state_change, DateTime.utc_now())

    cond do
      # Ping delayed → terminate
      delta &gt; @max_allowed_delay -&gt;
        Logger.error(
          "[#{device_id}] Ping delayed by #{delta}s (&gt; #{@max_allowed_delay}), terminating GenServer"
        )
        {:stop, :normal, state}

      # Too many missed pongs → mark offline
      missed &gt;= max_missed -&gt;
        Logger.error(
          "[#{device_id}] Device OFFLINE: missed #{missed} pings in a row (limit=#{max_missed})"
        )

        case state_change(device_id, eid, "OFFLINE", last_state_change, state) do
          {:chr, new_device_state} -&gt;
            Logger.warning("[#{device_id}] OFFLINE state change emitted to RegistryHub")
            send(ws_pid, :send_ping)
            schedule_ping(device_id, last_rtt)

            {:noreply,
             %{
               state
               | missed_pongs: max_missed,
                 pong_counter: counter,
                 last_rtt: nil,
                 last_send_ping: nil,
                 last_state_change: now,
                 device_state: new_device_state
             }}

          {:unchr, same_device_state} -&gt;
            Logger.debug("[#{device_id}] Still OFFLINE (no new state change)")
            send(ws_pid, :send_ping)
            schedule_ping(device_id, last_rtt)

            {:noreply,
             %{
               state
               | missed_pongs: max_missed,
                 pong_counter: counter,
                 last_rtt: nil,
                 last_send_ping: nil,
                 device_state: same_device_state
             }}
        end

      # Normal ping
      true -&gt;
        Logger.info(
          "[#{device_id}] Sending ping (missed=#{missed}/#{max_missed}, " &lt;&gt;
            "remaining=#{max_missed - missed}, counter=#{counter})"
        )

        send(ws_pid, :send_ping)
        schedule_ping(device_id, last_rtt)
        handle_increment_counter(state, counter, missed, last_rtt, now, device_id, eid, last_state_change)
    end
  end

  # -------------------------
  # Increment pong counter
  # -------------------------
  defp handle_increment_counter(state, counter, missed, last_rtt, now, device_id, eid, last_state_change) do
    case increment_counter(counter, device_id, eid, last_state_change, state) do
      {:ok, counter, cur_new_state} -&gt;
        update_state_after_increment(state, counter, missed, last_rtt, now, cur_new_state)

      {:er, counter} -&gt;
        Logger.debug(
          "[#{device_id}] Missed pong incremented → #{missed + 1} (limit=#{Map.get(state, :max_missed_pongs_adaptive)})"
        )

        {:noreply,
         %{
           state
           | missed_pongs: missed + 1,
             pong_counter: counter,
             timer: now,
             last_rtt: last_rtt,
             last_send_ping: now
         }}
    end
  end

  defp update_state_after_increment(state, counter, missed, last_rtt, now, {:chr, chr_device_state}) do
    Logger.info("[#{state.device_id}] Device ONLINE state refreshed via ping counter reset")

    {:noreply,
     %{
       state
       | missed_pongs: missed + 1,
         pong_counter: counter,
         timer: now,
         last_rtt: last_rtt,
         last_send_ping: now,
         last_state_change: DateTime.utc_now(),
         device_state: chr_device_state
     }}
  end

  defp update_state_after_increment(state, counter, missed, last_rtt, now, {:unchr, unchr_device_state}) do
    Logger.debug("[#{state.device_id}] Device state unchanged (ONLINE) after ping counter increment")

    {:noreply,
     %{
       state
       | missed_pongs: missed + 1,
         pong_counter: counter,
         timer: now,
         last_rtt: last_rtt,
         last_send_ping: now,
         device_state: unchr_device_state
     }}
  end

  defp increment_counter(counter, device_id, eid, last_state_change, state) do
    if counter + 1 &gt;= @max_pong_counter do
      Logger.debug("[#{device_id}] Ping counter limit reached → ONLINE transition for #{eid}")
      new_state = state_change(device_id, eid, "ONLINE", last_state_change, state)
      {:ok, 0, new_state}
    else
      {:er, counter + 1}
    end
  end

  # -------------------------
  # Device state change
  # -------------------------
  def state_change(device_id, eid, status, last_state_change, state, awareness_intention \\ 2) do
    attrs = %{
      status: status,
      last_seen: DateTime.utc_now(),
      awareness_intention: awareness_intention,
      last_activity: last_state_change
    }

    device_state = Map.get(state, :device_state)

    case DeviceState.track_state_change(attrs, device_state) do
      {:changed, prev_status, new_state} -&gt;
        Logger.info("[#{device_id}] State changed #{prev_status} → #{status}")
        RegistryHub.send_pong_to_bimip_server_master(device_id, eid, prev_status)
        {:chr, new_state}

      {:refresh, prev_status, new_state} -&gt;
        Logger.debug("[#{device_id}] State refresh #{prev_status} → #{status}")
        RegistryHub.send_pong_to_bimip_server_master(device_id, eid, prev_status)
        {:chr, new_state}

      {:unchanged, prev_status, new_state} -&gt;
        Logger.debug("[#{device_id}] State unchanged (#{prev_status})")
        {:unchr, new_state}
    end
  end

  # -------------------------
  # Adaptive ping interval
  # -------------------------
  defp calculate_adaptive_interval(rtt) when is_integer(rtt) do
    thresholds = AdaptiveNetwork.rtt_thresholds()
    intervals = AdaptiveNetwork.ping_intervals()

    cond do
      rtt &gt; thresholds.high -&gt; intervals.high_rtt
      rtt &lt; thresholds.low -&gt; intervals.default
      true -&gt; intervals.medium_rtt
    end
  end

  defp maybe_adaptive_interval(nil), do: @default_ping_interval
  defp maybe_adaptive_interval(rtt) when is_integer(rtt), do: calculate_adaptive_interval(rtt)

  # -------------------------
  # Adaptive max missed pongs
  # -------------------------
  # defp maybe_adaptive_max_missed(nil), do: AdaptiveNetwork.max_missed_pongs().default
  defp maybe_adaptive_max_missed(rtt) when is_integer(rtt) do
    thresholds = AdaptiveNetwork.rtt_thresholds()
    max_missed = AdaptiveNetwork.max_missed_pongs()

    cond do
      rtt &gt; thresholds.high -&gt; max_missed.high
      rtt &lt; thresholds.low -&gt; max_missed.low
      true -&gt; max_missed.default
    end
  end

  # -------------------------
  # Schedule next ping
  # -------------------------
  @doc "Schedule next ping with adaptive interval"
  def schedule_ping(device_id, last_rtt \\ nil) do
    interval = maybe_adaptive_interval(last_rtt)
    Logger.debug("[#{device_id}] Scheduling next ping in #{interval}ms")
    RegistryHub.schedule_ping_registry(device_id, interval)
    :ok
  end

  # -------------------------
  # Pong received from client
  # -------------------------
  def pongs_received(device_id, receive_time, state) when is_map(state) do
    last_send_ping = Map.get(state, :last_send_ping)
    rtt = if last_send_ping, do: DateTime.diff(receive_time, last_send_ping, :millisecond), else: 0

    Logger.info(
      "[#{device_id}] Pong received (RTT=#{rtt}ms). Resetting missed_pongs=0 (was #{state.missed_pongs})"
    )

    adaptive_max_missed = maybe_adaptive_max_missed(rtt)

    new_counter =
      if Map.get(state, :pong_counter, 0) + 1 &gt;= @max_pong_counter do
        Logger.debug("[#{device_id}] Pong counter limit reached → ONLINE transition")
        cur_device_state =
          state_change(device_id, Map.get(state, :eid), "ONLINE", Map.get(state, :last_state_change), state)
        {:pr_count, 0, cur_device_state}
      else
        {:unpr_count, Map.get(state, :pong_counter, 0) + 1}
      end

    case new_counter do
      {:pr_count, counter, cur_device_state} -&gt;
        update_state_after_increment(state, counter, 0, rtt, receive_time, cur_device_state)

      {:unpr_count, counter} -&gt;
        {:noreply,
         %{
           state
           | missed_pongs: 0,
             pong_counter: counter,
             timer: receive_time,
             last_rtt: rtt,
             max_missed_pongs_adaptive: adaptive_max_missed,
             last_send_ping: receive_time
         }}
    end
  end

  # -------------------------
  # Pong received from network
  # -------------------------
  def handle_pong_from_network(device_id, sent_time) do
    Logger.debug("[#{device_id}] Handling network pong at #{sent_time}")
    RegistryHub.handle_pong_registry(device_id, sent_time)
  end
end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="373957" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/time-to-implement-pub-sub-in-hologram-looking-for-your-input/72519/21">Post #20</a>
	                </div>
	            </div>
              <div id="likers-container-373957" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="373957"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #20"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/72519/load_more?page=3">Load more posts (28 remaining)</a>
</div></template></turbo-stream>