<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexey Sitnikov</title>
    <description>The latest articles on DEV Community by Alexey Sitnikov (@by_sitnikov).</description>
    <link>https://dev.to/by_sitnikov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3984336%2F51bb45de-c3a6-49a7-8877-34efa327f59e.png</url>
      <title>DEV Community: Alexey Sitnikov</title>
      <link>https://dev.to/by_sitnikov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/by_sitnikov"/>
    <language>en</language>
    <item>
      <title>I built an offline wiki that fits in a single 19 KB HTML file</title>
      <dc:creator>Alexey Sitnikov</dc:creator>
      <pubDate>Sun, 14 Jun 2026 19:42:58 +0000</pubDate>
      <link>https://dev.to/by_sitnikov/i-built-an-offline-wiki-that-fits-in-a-single-19-kb-html-file-1aai</link>
      <guid>https://dev.to/by_sitnikov/i-built-an-offline-wiki-that-fits-in-a-single-19-kb-html-file-1aai</guid>
      <description>&lt;p&gt;I kept hitting the same wall looking for an offline wiki. &lt;a href="https://kiwix.org/" rel="noopener noreferrer"&gt;Kiwix&lt;/a&gt; is great, but it's an app plus multi-GB ZIM files. IPFS needs connectivity and setup. I just wanted something dead simple: a knowledge file you can open on any phone with &lt;strong&gt;no install&lt;/strong&gt;, and hand to the next person over Bluetooth or a USB stick.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Portable Knowledge Mesh&lt;/strong&gt;. It's one &lt;code&gt;reader.html&lt;/code&gt; (~19 KB). You open it — even straight from &lt;code&gt;file://&lt;/code&gt; — load a &lt;code&gt;.mesh&lt;/code&gt; pack, and read/search offline. To share it, you just send the file; the other person opens it in any browser. The whole thing — reader &lt;strong&gt;plus&lt;/strong&gt; a 21-article survival pack — is &lt;strong&gt;under 50 KB&lt;/strong&gt;. It fits in a single chat message.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 Code: &lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh" rel="noopener noreferrer"&gt;https://github.com/by-sitnikov/portable-knowledge-mesh&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 Live demo (loads instantly, no backend): &lt;a href="https://by-sitnikov.github.io/portable-knowledge-mesh/" rel="noopener noreferrer"&gt;https://by-sitnikov.github.io/portable-knowledge-mesh/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/by-sitnikov" rel="noopener noreferrer"&gt;
        by-sitnikov
      &lt;/a&gt; / &lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh" rel="noopener noreferrer"&gt;
        portable-knowledge-mesh
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🌐 Portable Knowledge Mesh — an offline wiki in a single file&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A Wikipedia that spreads via Bluetooth, USB, and AirDrop — no server, no install, no internet required.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh/LICENSE-MIT" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6b39ffb5b84fd424caf0a1e787ae112a458f49d3755bd45e8dcee4cb6983f044/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f64652d4d49542d79656c6c6f772e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh/LICENSE-CC" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e96ba20d4ad3391c17fd32a72efa44a1c9b55ea9daf65f3991e4edc1a194d504/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f6e74656e742d434325323042592d2d5341253230342e302d6c69676874677265792e737667" alt="Content: CC BY-SA 4.0"&gt;&lt;/a&gt;
&lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh/releases/v0.1.0/" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2cfb1e3300d6f613a60ed13342412763f6c32edbd6c3b0bf9f55d6a37122e736/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72656c656173652d76302e312e302d3265613434662e737667" alt="Release"&gt;&lt;/a&gt;
&lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh#why-this-works-architecture" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d5349aae788ae716f56cd55a51d3e438c111ccaa8fc29e8f1e4c8e0c19baa0fe/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275696c7425323062792d6f6e652532307765622532306465762d3030643461612e737667" alt="Made by one dev"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; · &lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; · &lt;em&gt;translations wanted — see &lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh/CONTRIBUTING.md" rel="noopener noreferrer"&gt;#good-first-issue&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is this?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Portable Knowledge Mesh is a &lt;strong&gt;single HTML file&lt;/strong&gt; that turns any phone or laptop into an &lt;strong&gt;offline wiki&lt;/strong&gt;. It opens in a browser — even straight from a USB stick, an email attachment, or a Bluetooth share — and lets you read, search, and pass on curated knowledge without ever touching a server.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Zero install&lt;/strong&gt; — double-click &lt;code&gt;reader.html&lt;/code&gt;, it works. No app store, no setup.&lt;/li&gt;
&lt;li&gt;📴 &lt;strong&gt;Fully offline&lt;/strong&gt; — no internet, no backend, no API keys, no account.&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Tamper-proof&lt;/strong&gt; — every knowledge pack is cryptographically signed (ECDSA P-256, WebCrypto). Forgeries are rejected automatically.&lt;/li&gt;
&lt;li&gt;🦠 &lt;strong&gt;Viral by design&lt;/strong&gt; — send a &lt;code&gt;.mesh&lt;/code&gt; file over…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/by-sitnikov/portable-knowledge-mesh" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Here are the parts that turned out interesting to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  A single file that runs from &lt;code&gt;file://&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The constraint "must work when double-clicked off a USB stick" kills a lot of the usual toolbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Service Worker&lt;/strong&gt; — they require a secure context; &lt;code&gt;file://&lt;/code&gt; isn't one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No IndexedDB&lt;/strong&gt; — in a &lt;code&gt;file://&lt;/code&gt; (null) origin it's unreliable and non-portable across browsers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So v0.1 keeps everything in memory + &lt;code&gt;sessionStorage&lt;/code&gt;, and a &lt;code&gt;.mesh&lt;/code&gt; is just a single JSON document (zero dependencies, trivially parseable). The fancy ZIP + &lt;code&gt;CompressionStream&lt;/code&gt; container is a later optimization — for v0.1, plain JSON is what makes it bulletproof on &lt;code&gt;file://&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tamper-proof packs with WebCrypto (no libraries)
&lt;/h2&gt;

&lt;p&gt;If knowledge spreads device-to-device with no server, "is this pack genuine?" matters. Each pack is signed: every article (block) is hashed with SHA-256, a Merkle root is computed over the sorted block hashes, and the publisher signs that root with &lt;strong&gt;ECDSA P-256&lt;/strong&gt; via the browser's built-in &lt;code&gt;crypto.subtle&lt;/code&gt;. The reader re-derives everything and verifies — change one byte of a signed article and the badge turns red.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// recompute block hashes -&amp;gt; Merkle root -&amp;gt; verify the publisher's signature, all in-browser&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyPack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;per&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;per&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sha256Hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sha256Hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;per&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;per&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tampered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// content was modified&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jwk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pubkey_jwk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ECDSA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;namedCurve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;P-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ECDSA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;b64ToBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature_b64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verified&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tampered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// no private key = can't re-sign a forged root&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two attack paths, both covered: edit a block and the recomputed hash won't match the manifest; edit the manifest to match and the signature over the new root fails (you can't re-sign without the private key).&lt;/p&gt;

&lt;h2&gt;
  
  
  The CRDT trap I had to avoid (for v0.2 editing)
&lt;/h2&gt;

&lt;p&gt;v0.2 will let local communities adapt articles. My first instinct was a CRDT like Yjs or Automerge — until I remembered the &lt;strong&gt;quota death spiral&lt;/strong&gt;: sequence/text CRDTs accumulate tombstones, history grows without bound, and safe garbage collection needs coordination among &lt;em&gt;all&lt;/em&gt; nodes. In an offline mesh where devices may not meet for months, there's no server to compact — so the document just bloats until &lt;code&gt;QuotaExceededError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix is choosing a lighter CRDT, not abandoning CRDTs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LWW-Map per block&lt;/strong&gt; instead of a sequence CRDT — only the current value per field is kept, so no tombstone history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lamport clocks, never wall-clock.&lt;/strong&gt; Offline devices have unreliable, forgeable clocks (dead CMOS battery → 1970; set the date to 2099 and you always "win"). Conflicts resolve by logical time, tie-broken by author key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted-snapshot compaction&lt;/strong&gt; — a trusted editor publishes a signed snapshot that peers who trust them accept as a new baseline, dropping old ops. Compaction with no server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sneakernet first
&lt;/h2&gt;

&lt;p&gt;WebRTC is tempting for sync, but without STUN/TURN it only connects inside one LAN — too fragile to be the critical path. So the &lt;strong&gt;primary&lt;/strong&gt; transport is just &lt;em&gt;moving the file&lt;/em&gt;: Bluetooth, AirDrop, USB, QR, email. WebRTC LAN-sync is a v0.3 bonus, never a dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it is now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;v0.1&lt;/strong&gt; — portable read-only reader (this release)&lt;/li&gt;
&lt;li&gt;🚧 &lt;strong&gt;v0.2&lt;/strong&gt; — editable overlay + local web of trust + Lamport sync over file exchange&lt;/li&gt;
&lt;li&gt;📡 &lt;strong&gt;v0.3&lt;/strong&gt; — installed PWA mode + WebRTC LAN-sync + trusted-snapshot compaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It ships with &lt;strong&gt;Barefoot Skills&lt;/strong&gt; — 21 practical offline articles (water, energy, repair, food, shelter), risk-stratified so safety-critical topics are signed and read-only. Code is MIT, content CC BY-SA, all sourced from open corpora (Appropedia, Wikibooks, Practical Action).&lt;/p&gt;

&lt;h2&gt;
  
  
  I'd love your feedback
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is the single-file / "sneakernet" approach genuinely useful, or a gimmick?&lt;/li&gt;
&lt;li&gt;What knowledge packs would be worth curating first — first aid, repair, farming, a specific language?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And if you want to poke holes in the crypto or the v0.2 merge design, please do — that's exactly the kind of review I'm hoping for.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/by-sitnikov/portable-knowledge-mesh" rel="noopener noreferrer"&gt;https://github.com/by-sitnikov/portable-knowledge-mesh&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
