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


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="wycliffogembo87" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  wycliffogembo87
                    <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>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks for your feedback — a couple of your points land and a couple I’d push back on.</p>
<p><strong>On the “good operators vs rogue operators” framing.</strong> It collapses three adversaries into two in a way that loses the useful distinction. The rogue operator (actively pushing malicious JS) is one adversary and, as I conceded, the architecture cannot stop them. But there are two others the client-side crypto genuinely <em>does</em> protect against, and neither requires the operator to be rogue:</p>
<ul>
<li><strong>Passive compromise</strong> — the operator is behaving honestly; the DB just gets dumped in a breach, a backup leaks, or an opportunistic intruder runs <code>pg_dump</code>. The operator is not the attacker; their data just went somewhere it shouldn’t. This is the most common class of real-world incident.</li>
<li><strong>Future operator</strong> — the domain is sold, the business is acquired, I die, the project is handed off, I fall under legal pressure. The current operator’s good behaviour doesn’t bind the next one, but the ciphertext does.</li>
</ul>
<p>So the irony isn’t quite what it looks like — the defence <em>is</em> against good operators, in the sense that their present-tense good behaviour doesn’t retroactively protect data once a key they never possessed becomes moot to the future state of the system.</p>
<p><strong>“Do you need to persist anything at all?”</strong> — you’re right that server-side utility is low. Still: yes, but the minimum.</p>
<ul>
<li><code>rooms</code> exists so two users with the same shared phone number can find the same pigeonhole without exchanging a session ID over a side channel. The passcode-test UX (“type a number from your contacts”) requires a durable lookup target.</li>
<li><code>room_access</code> exists because the 10-attempt lockout on <code>access_hash</code> has to be server-enforced — a client can’t rate-limit itself.</li>
<li><code>messages</code> (single row) exists as a dead-drop: one user writes, the other reads asynchronously. Pure peer-to-peer would require both users online simultaneously, which breaks the medium.</li>
</ul>
<p>N=1 + encryption means the DB is effectively “a pigeonhole with a rate-limiter.” Every row is as small as I can make it and is hard-deleted on reply or TTL expiry. Zero is the wrong answer; minimum is the design.</p>
<p><strong>Firefox on Windows.</strong> Good that the restart helped. The “general feeling of slowness” is probably one of two things:</p>
<ol>
<li><strong>PBKDF2 600k iterations at login</strong> — ~1.5–2.5s on the main thread, shown behind a loader, happens once per session. Intentional cost per OWASP 2023.</li>
<li><strong>Glassmorphism <code>backdrop-filter: blur()</code></strong> — Firefox on Windows has a known perf gap vs Chromium here, especially on older GPUs. No background JS (no service worker, no polling, no web workers), but compositing is more expensive than a flat UI would be.</li>
</ol>
<p>If you can, a DevTools → Performance recording during a session would pin which one. Happy to dig in if you share the profile (or file an issue).</p>
<p><strong>File sharing / multimedia.</strong> Yes, users will ask for it (it’s the most common extension beyond text), and yes, files at rest would make the client-side crypto case more obviously compelling than text alone does.</p>
<p>The blocker isn’t architectural (files encrypt and store fine in the same ciphertext-only model), it’s operator-risk. Once media flows through the service — even encrypted — the operator inherits compliance obligations that a text-only service sidesteps, most critically around CSAM reporting mandates and, indirectly, payment-processor categorisation (image-hosting platforms tend to be treated more strictly regardless of whether the operator can see the content). None of that is insurmountable, but it requires legal backing and content-moderation infrastructure that a solo operation does not yet have.</p>
<p>So: <strong>text-only for now because operator-risk doesn’t support multimedia; revisit when that changes.</strong></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387556" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-387556" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387556"
                     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="387558" data-post-id="387558">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="wycliffogembo87" data-post="12" data-topic="75093">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/w/90ced4/48.png" class="avatar"> wycliffogembo87:</div>
<blockquote>
<p>you’re right that server-side utility is low. Still: yes, but the minimum.</p>
</blockquote>
</aside>
<p>Thanks, short term capture of message history etc can be done with a GenServer. This way, the persistent risk can be side steped. Without persistent, the passive compromise or future operator risks will disappear.</p>
<aside class="quote no-group" data-username="wycliffogembo87" data-post="12" data-topic="75093">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/w/90ced4/48.png" class="avatar"> wycliffogembo87:</div>
<blockquote>
<p>the operator inherits compliance obligations that a text-only service sidesteps, most critically around CSAM reporting mandates and, indirectly, payment-processor categorisation</p>
</blockquote>
</aside>
<p>I am not a lawyer but I don’t think this is enforceable in short lived, small sized file sharing in private chat.</p>
<p>I also suggest you to dial down on iterations and fancy graphic transitions. I am on a decent laptop and I still feel slow. Your users might want to use a cheap phone.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387558" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-387558" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387558"
                     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="387561" data-post-id="387561">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="wycliffogembo87" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  wycliffogembo87
                    <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>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Yes, the key-exchange problem is real — I don’t think there’s a way around it for any E2E system, but it’s worth being specific about how it lands here.</p>
<p>The “back channel” in the design’s expected use case is usually the relationship itself. Intimate-access is the threat model, which means the other party is almost always someone you already know well — a partner, a family member, a close friend. The exchange is typically “let’s use this random number: 555-2341” said out loud at a dinner table, 30 seconds, no ceremony. For that threat model, that channel is plenty.</p>
<p>That said — every E2E system has the same problem, they just pick different trade-offs:</p>
<ul>
<li><strong>Signal</strong> solves it with phone-number-based discovery, which (as I conceded earlier on the metadata point) means the server knows who’s talking to whom.</li>
<li><strong>Matrix</strong> uses server-brokered room introductions.</li>
<li><strong>Session / Tor / Briar</strong> all require exchanging some identifier through another channel first.</li>
</ul>
<p>sTELgano pushes the exchange fully out-of-band, which is a deliberate trade: no phonebook discovery, no server-side contact graph, in exchange for a trivial-in-practice face-to-face coordination step.</p>
<p>One nuance that softens the chicken-and-egg framing slightly: the phone number isn’t the whole credential. The PIN is chosen <em>independently by each party</em> and never transmitted anywhere. An attacker who intercepts the exchange channel gets the number but not the PIN — they’d still have to get past the 10-attempt server-side lockout, which caps practical brute-force. For a passive eavesdropper (overheard, screenshotted), that matters; for an active attacker with full device access, it doesn’t — which is consistent with the stated threat model.</p>
<p>The concession: for threat models wider than intimate-access, the out-of-band exchange is genuinely a weaker link than a system with forward secrecy and a double-ratchet. That gap is part of why the docs are explicit sTELgano isn’t for those threat models.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387561" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-387561" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387561"
                     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="387562" data-post-id="387562">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="wycliffogembo87" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  wycliffogembo87
                    <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>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Three good suggestions.</p>
<p><strong>GenServer for message storage</strong> — genuinely interesting, I hadn’t properly considered this. Moving the single message into in-memory process state would sidestep passive-compromise and future-operator risk for content. Trade-off is durability across deploys/restarts. Hybrid shape — keep <code>rooms</code> and <code>room_access</code> in Postgres (lookups + lockout counter need durability), move message content in-memory per-room — is probably the right direction. Worth prototyping.</p>
<p><strong>CSAM enforceability for small/ephemeral services</strong> — fair point, “enforcement likely” isn’t the right framing. My concern is less court-risk than operational surface: payment processors and hosting providers categorise services on their own criteria, and a de-banking event is catastrophic for a solo operator regardless of legal specifics. But conceded — it’s not the slam-dunk blocker I framed it as.</p>
<p><strong>Performance on a decent laptop</strong> — serious signal, taking it. Two specific things I’ll look at: moving PBKDF2 into a Web Worker so it doesn’t block the main thread (600k stays for security, stops freezing the UI), and adding a flatter fallback for the backdrop-filter glassmorphism on lower-end devices and Firefox+Windows. Cheap-phone users are in the target market — not an edge case.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387562" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-387562" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387562"
                     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="387592" data-post-id="387592">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="wycliffogembo87" data-post="15" data-topic="75093">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/w/90ced4/48.png" class="avatar"> wycliffogembo87:</div>
<blockquote>
<p>keep <code>rooms</code> and <code>room_access</code> in Postgres (lookups + lockout counter need durability),</p>
</blockquote>
</aside>
<p>They can be in memory too. lockout counter can be reset at reboot without lost of functionality. As for room look up, if you do not persist anything else, then room itself does not need persistence (nothing there to look up). You can regard every new channel access as room creation.</p>
<p>On key exchange and pin code: People now days are notoriously bad at memorizing phone number and passwords. I totally rely on my address book and password manager. So, if I have an item in my phone contact as “my secret sTELgano number with my paramour” as a note, things will not bode well. As for pin code, if a user has &gt;1 channels on sTELgano and browser autofill the pin code based on domain name, things will fall apart too.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387592" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-387592" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387592"
                     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="387595" data-post-id="387595">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="wycliffogembo87" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  wycliffogembo87
                    <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>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p><strong>Fully in-memory — technically yes, but it becomes a different product.</strong> Concrete consequences I’d want to not lose:</p>
<ul>
<li><strong>Lockout counter reset-on-reboot = 10 attempts per uptime window, not per attacker career.</strong> Anyone who can trigger or wait for restarts refreshes their budget; the 30-minute time-based lockout guarantee becomes unenforceable — legitimate users also can’t know when theirs resets.</li>
<li><strong>“Every access is creation” works for synchronous / pure-P2P products.</strong> sTELgano is asynchronous: A sends, goes offline, B reads an hour later. A deploy between those two events drops the message. Reliability regression, not a privacy gain.</li>
<li><strong>TTL enforcement dies without persistence.</strong> Paid tier (1 year) and free tier (7 days) both need to survive restarts; in-memory resets the clock on every boot.</li>
<li><strong>Monetization needs persistence.</strong> <code>extension_tokens</code> have to outlive restarts or payments evaporate on the first deploy.</li>
</ul>
<p>For the current product (asynchronous, time-based rate-limits, paid tiers), minimum persistence <em>is</em> the minimum. A stricter ephemeral product could work fully in-memory — but that’s a different product with a different UX contract. Happy to be convinced otherwise if there’s a specific version I’m missing.</p>
<p><strong>Contact-note concern — let me reframe.</strong> I don’t think a new contact needs to be created at all. You add the generated steg number as an <em>additional</em> number on an existing real contact: John Doe already exists in your contacts with <code>+254 722 222222</code>; you just add <code>+254 733 444444</code> as a second number on the same card. A suspicious partner browsing contacts sees “John Doe: two numbers” — completely unremarkable (work/personal, dual-SIM, new number, whatever). No note, no label, no new contact to justify — the contact’s name <em>is</em> the implicit label because it’s literally the person’s name. That mirrors what the product copy already implies (“saved in the other’s real contact card”), but the onboarding UX probably doesn’t make this pattern explicit enough today. Fair feedback — filing as a UX clarity fix.</p>
<p><strong>PIN autofill by domain — real problem, you’re right to surface it.</strong> Current state: the app uses <code>autocomplete="one-time-code"</code> plus non-standard field attributes to discourage browser password managers from offering to save or fill the PIN. But browser heuristics change frequently, and a user with multiple channels on <a href="http://stelgano.com" rel="noopener nofollow ugc">stelgano.com</a> is exactly the case where “suggest the same PIN for this site” would break everything. Worth an actual audit on current Chrome/Firefox/Safari/Brave, not just trust the attributes. Filing.</p>
<p><strong>Memorization load for users with &gt;1 channels — unresolvable trade-off.</strong> Unique PIN per channel = more secure, harder to remember. Same PIN everywhere = single point of failure. Password manager = breaks the passcode test. The current design takes the security side, which means users with 3+ channels legitimately have memorization load. Worth naming in the docs as a known limit rather than pretending it isn’t there.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387595" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-387595" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387595"
                     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="387597" data-post-id="387597">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks for the shout out <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"> I tried to visit <a href="http://stelgano.com" rel="noopener nofollow ugc">stelgano.com</a> but couldn’t get the websocket to connect/page to load. Are you running into deployment/config issues with fly? Anything I can help with?</p>
<p>Edit to say looks to be up now, really cool idea <img src="https://forum.elixirforum.com/images/emoji/apple/heart.png?v=15" title=":heart:" class="emoji" alt=":heart:" loading="lazy" width="20" height="20"></p>
<p>For me, I start to think about useability questions:</p>
<ul>
<li>n = 1; perhaps this is configurable at some step in the flow because maybe people need a little history at times and not at others</li>
<li>how does the admin dashboard fit in to this, metrics for something that is supposed to be invisible? maybe this where you can give people more control/configurability over the way the service works for them</li>
</ul>
<p>I haven’t had a moment to look at the code or more in depth, but cool that you open sourced it too.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387597" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-387597" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387597"
                     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="387598" data-post-id="387598">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="wycliffogembo87" data-post="17" data-topic="75093">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/w/90ced4/48.png" class="avatar"> wycliffogembo87:</div>
<blockquote>
<p>TELgano is asynchronous: A sends, goes offline, B reads an hour later.</p>
</blockquote>
</aside>
<p>I didn’t know this is the intended usage pattern. Text only, 1 on 1 only, enforced N=1, Async communication make it very unique; and you will face quite some initial inertia.</p>
<p>If you decide to go down this route, then file/pic upload may not be very high on your priority list. Voice recording should be higher.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387598" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/19">Post #18</a>
	                </div>
	            </div>
              <div id="likers-container-387598" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387598"
                     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="387607" data-post-id="387607">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="wycliffogembo87" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  wycliffogembo87
                    <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>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p><strong>“Didn’t know this is the intended pattern”</strong> — that’s the UX signal that stings. If a technically engaged reader missed that async-text-only is deliberate after this much discussion, casual readers certainly will too. Filing a copy fix so the homepage and <code>/about</code> state it up front rather than leaving it implicit.</p>
<p><strong>“Initial inertia”</strong> — accepted. The combination (text-only, 1:1, N=1, async) has no existing mental model to inherit from WhatsApp/Signal/Telegram — every constraint has to be re-justified rather than carried over. That’s the cost of the positioning; it’s worth paying for the population that actively wants this shape, and I’m prepared for the product to stay niche because of it.</p>
<p><strong>“Voice over file/pic uploads”</strong> — I hadn’t properly considered this, and you’re right the ordering was wrong in my head. A few reasons voice has a materially different operator-risk profile than images:</p>
<ul>
<li><strong>Payment-processor categorisation differs</strong> — voice-messaging isn’t placed in the same merchant-risk bucket that image-hosting services fall into. That maps directly to the operational-surface concern I named earlier.</li>
<li><strong>DMCA exposure is minor</strong> — short voice notes don’t attract the same active copyright-monitoring apparatus as image/audio/video platforms.</li>
<li><strong>Voice fits the async rhythm better</strong> — a 30-second note is turn-based by nature in a way a snapshot isn’t.</li>
<li><strong>Format is cleaner</strong> — smaller file sizes, no EXIF / geolocation metadata to strip before encryption.</li>
</ul>
<p>Updated position: when operator-risk eventually supports <em>any</em> multimedia, voice is plausibly the first unlock — not images. Still not a near-term build — “text-only for now” remains the stance — but voice-before-images is a different ordering than where I’d landed, and it’s the right one. Thanks for the push.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387607" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/20">Post #19</a>
	                </div>
	            </div>
              <div id="likers-container-387607" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387607"
                     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="387609" data-post-id="387609">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="wycliffogembo87" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  wycliffogembo87
                    <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>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks — and appreciate you coming over to engage.</p>
<p><strong>Deployment</strong> — not on <a href="http://fly.io" rel="noopener nofollow ugc">fly.io</a> <em>yet</em>. Currently a DigitalOcean droplet with <strong>Caddy</strong> fronting Phoenix on :4000 (systemd + Let’s Encrypt). Plan is to move to <a href="http://fly.io" rel="noopener nofollow ugc">fly.io</a> once there’s enough traffic to justify the scaling story, so your offer might come in handy later — happy to pick your brain when that migration is on the cards. The WebSocket issue was most likely a transient deploy or DNS blip; I’ll audit the Caddy reverse-proxy config regardless in case there’s a reproducible gap.</p>
<p><strong>N=1 configurability</strong> — I think this one stays a hard invariant. N=1 isn’t a feature users opt into, it’s the constraint the whole product is designed around — <em>“no history, anywhere”</em> is the passcode-test claim, and the moment N becomes configurable the claim becomes conditional (<em>“no history unless you changed the setting”</em>). Users who want even occasional history have strong options already — Signal, Telegram, WhatsApp — and pulling them in here by making N a knob would dilute the one axis that makes sTELgano meaningfully different without giving them anything the alternatives don’t already offer better.</p>
<p>That said, you’re pointing at something real. But I’d argue the product is structured around how real conversations actually work — people don’t hold full context in their heads, they ask the other person what they were replying to, or include the reference paraphrased in their own reply (quote-reply pattern). N=1 mirrors that rhythm rather than fighting it. Users who want an external record can copy-paste before replying — worth being upfront about in the docs — but the in-product coping patterns (ask, paraphrase, quote) handle most of what “I want history” actually means in practice.</p>
<p><strong>Admin dashboard</strong> — good question to raise. It’s the <em>operator’s</em> view, not the user’s — aggregate counters only (per-country lifetime totals, per-day global new/expired). No per-room or per-user data stored. The design constraint was <em>“answer ‘how many rooms from Kenya?’ without ever being able to answer ‘which rooms from Kenya?’”</em> — country and day stats live in two separate tables specifically because they can’t be joined back to individual rooms. User-facing configurability is a separate axis, and one where I’m more conservative than most (tied to the N=1 answer above).</p>
<p>Curious how Mosslet landed on similar “should this be configurable?” debates — suspect you’ve had your own version of this conversation.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387609" 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/stelgano-a-zero-knowledge-messaging-app-on-phoenix-1-8-liveview-feedback-welcome-on-the-unauthenticated-channel-design/75093/21">Post #20</a>
	                </div>
	            </div>
              <div id="likers-container-387609" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387609"
                     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/75093/load_more?page=3">Load more posts (1 remaining)</a>
</div></template></turbo-stream>