<?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>Forem: c4605</title>
    <description>The latest articles on Forem by c4605 (@c4605).</description>
    <link>https://forem.com/c4605</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%2F3608654%2F771f1b69-3104-48b0-b155-2cee13f468bd.png</url>
      <title>Forem: c4605</title>
      <link>https://forem.com/c4605</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/c4605"/>
    <language>en</language>
    <item>
      <title>shadow-cljs-vite-plugin v0.0.6: Fixing HMR and ES Module Compatibility</title>
      <dc:creator>c4605</dc:creator>
      <pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/c4605/shadow-cljs-vite-plugin-v006-fixing-hmr-and-es-module-compatibility-20m4</link>
      <guid>https://forem.com/c4605/shadow-cljs-vite-plugin-v006-fixing-hmr-and-es-module-compatibility-20m4</guid>
      <description>&lt;p&gt;Two weeks after the initial release of &lt;a href="https://github.com/bolasblack/shadow-cljs-vite-plugin" rel="noopener noreferrer"&gt;shadow-cljs-vite-plugin&lt;/a&gt;, v0.0.6 brings some important fixes that make the development experience much smoother.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: ES Module Imports
&lt;/h2&gt;

&lt;p&gt;When I first released the plugin, there were edge cases where ClojureScript code wouldn't import correctly in Vite's ESM environment. This was particularly problematic for users deploying to Cloudflare Workers.&lt;/p&gt;

&lt;p&gt;The fix required coordinating with shadow-cljs upstream. We submitted a &lt;a href="https://github.com/thheller/shadow-cljs/pull/1249" rel="noopener noreferrer"&gt;PR #1249&lt;/a&gt; to address the root cause, which was merged in v3.3.5. Thanks to &lt;a href="https://github.com/thheller" rel="noopener noreferrer"&gt;@thheller&lt;/a&gt; for working through it with me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: HMR "Namespace already declared" Error
&lt;/h2&gt;

&lt;p&gt;This was a frustrating one. During hot module replacement, Vite re-executes module code, but global state (like &lt;code&gt;goog.loadedModules_&lt;/code&gt;) persists across HMR updates. Google Closure Library assumes modules are loaded exactly once and throws errors on duplicate registration.&lt;/p&gt;

&lt;p&gt;The solution was to patch &lt;code&gt;goog.provide&lt;/code&gt; and &lt;code&gt;goog.module&lt;/code&gt; to be idempotent:&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="nx"&gt;goog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;goog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isProvided_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;goog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;constructNamespace_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;goog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="o"&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;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&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;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;goog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadedModules_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;goog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moduleLoaderState_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moduleName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&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;origModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;origModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Better HMR Batching
&lt;/h2&gt;

&lt;p&gt;Previously, if you saved multiple files quickly (or your editor formatted on save), each change triggered a separate HMR update. Now the plugin batches multiple file changes into a single update using a debounce mechanism.&lt;/p&gt;

&lt;p&gt;Additionally, changes to &lt;code&gt;shadow-cljs.edn&lt;/code&gt; now automatically restart Vite, so you don't have to manually restart when modifying your build configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailwind CSS Support
&lt;/h2&gt;

&lt;p&gt;The plugin works great with Tailwind CSS. I've been using this combination on &lt;a href="https://blog.c4605.com/en/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; frontend with no issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;shadow-cljs-vite-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin is stable and I'm using it in production. Feedback and contributions are always welcome on &lt;a href="https://github.com/bolasblack/shadow-cljs-vite-plugin" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>clojurescript</category>
      <category>shadowcljs</category>
      <category>vite</category>
    </item>
    <item>
      <title>bitcoin-wallet-connector: One API to Connect All Bitcoin Wallets</title>
      <dc:creator>c4605</dc:creator>
      <pubDate>Tue, 06 Jan 2026 12:14:52 +0000</pubDate>
      <link>https://forem.com/c4605/bitcoin-wallet-connector-one-api-to-connect-all-bitcoin-wallets-42h4</link>
      <guid>https://forem.com/c4605/bitcoin-wallet-connector-one-api-to-connect-all-bitcoin-wallets-42h4</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/c4605/the-bitcoin-wallet-ecosystem-is-a-mess-a-developers-rant-426"&gt;previous article&lt;/a&gt;, I ranted about how chaotic the Bitcoin wallet ecosystem is: WBIPs standards that nobody implements, sats-connect compatibility issues, wallets each doing their own thing with APIs...&lt;/p&gt;

&lt;p&gt;This article introduces the library I built: &lt;a href="https://github.com/bolasblack/bitcoin-wallet-connector" rel="noopener noreferrer"&gt;bitcoin-wallet-connector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Try the &lt;strong&gt;&lt;a href="https://bitcoin-wallet-connector.netlify.app/iframe.html?id=components-bitcoinconnectionprovider--default&amp;amp;viewMode=story" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; first, or check out the &lt;strong&gt;&lt;a href="https://bitcoin-wallet-connector.netlify.app/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Library Focuses On
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Smoothing Out the Ridiculous Differences
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;bitcoin-wallet-connector&lt;/code&gt; provides a unified API that lets you connect to all supported wallets with the same code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BitcoinWalletConnector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UnisatWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;XverseWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LeatherWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bitcoin-wallet-connector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Register the wallets you want to support&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BitcoinWalletConnector&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nc"&gt;UnisatWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nc"&gt;XverseWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nc"&gt;LeatherWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;// Subscribe to available wallet changes&lt;/span&gt;
&lt;span class="c1"&gt;// Note: The timing of wallet extension API injection is unpredictable&lt;/span&gt;
&lt;span class="c1"&gt;// (some inject at DOMContentLoaded, others after the load event),&lt;/span&gt;
&lt;span class="c1"&gt;// so subscribing is recommended over getting&lt;/span&gt;
&lt;span class="nx"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeAvailableAdapters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;availableAdapters&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Available wallets:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;availableAdapters&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="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// =&amp;gt; ['unisat', 'xverse', ...]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Connect wallet - same API for all wallets&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;adapterId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;availableAdapters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapterId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Get addresses, sign, send transactions - unified interface&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addresses&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;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAddresses&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;result&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;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello Bitcoin!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Write the code once, support all wallets.&lt;/p&gt;

&lt;p&gt;Currently supported wallets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Wallet&lt;/th&gt;
&lt;th&gt;Adapter&lt;/th&gt;
&lt;th&gt;Extra Dependencies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://unisat.io/" rel="noopener noreferrer"&gt;UniSat&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UnisatWalletAdapterFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.xverse.app/" rel="noopener noreferrer"&gt;Xverse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;XverseWalletAdapterFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sats-connect&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.okx.com/web3" rel="noopener noreferrer"&gt;OKX&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OkxWalletAdapterFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://leather.io/" rel="noopener noreferrer"&gt;Leather&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LeatherWalletAdapterFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@leather.io/rpc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://web3.bitget.com/" rel="noopener noreferrer"&gt;Bitget&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BitgetWalletAdapterFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://wallet.magiceden.io/" rel="noopener noreferrer"&gt;Magic Eden&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MagicEdenWalletAdapterFactory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sats-connect&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All adapters implement the same interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WalletAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Connect/Disconnect&lt;/span&gt;
  &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// Get addresses&lt;/span&gt;
  &lt;span class="nf"&gt;getAddresses&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WalletAdapterAddress&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// Message signing&lt;/span&gt;
  &lt;span class="nf"&gt;signMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignMessageResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// Send BTC&lt;/span&gt;
  &lt;span class="nf"&gt;sendBitcoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;receiverAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;satoshiAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// PSBT signing&lt;/span&gt;
  &lt;span class="nf"&gt;signAndFinalizePsbt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;psbtHex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;signIndices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;][],&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signedPsbtHex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c1"&gt;// Listen for address changes&lt;/span&gt;
  &lt;span class="nf"&gt;onAddressesChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No matter which wallet users choose, your business logic stays the same.&lt;/p&gt;

&lt;h4&gt;
  
  
  About sendRunes/sendInscriptions/sendBRC20
&lt;/h4&gt;

&lt;p&gt;Currently, this library only supports &lt;code&gt;signMessage&lt;/code&gt;, &lt;code&gt;sendBitcoin&lt;/code&gt;, and &lt;code&gt;signPsbt&lt;/code&gt;. It doesn't support &lt;code&gt;sendRunes&lt;/code&gt;, &lt;code&gt;sendInscriptions&lt;/code&gt;, or &lt;code&gt;sendBRC20&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because these involve more complex dependencies (like needing an Ordinals Indexer, a BRC20 Indexer, etc.). These would make a &lt;code&gt;Connector&lt;/code&gt; overly complicated.&lt;/p&gt;

&lt;p&gt;In my view, this should be the responsibility of a &lt;code&gt;Transaction Builder&lt;/code&gt;. The &lt;code&gt;Transaction Builder&lt;/code&gt; handles transaction construction, then passes it to the &lt;code&gt;Connector&lt;/code&gt; for signing and broadcasting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Matters
&lt;/h3&gt;

&lt;p&gt;When designing this library, I prioritized &lt;strong&gt;dependency security&lt;/strong&gt;. The reason is simple: wallet libraries directly handle user assets, and any security vulnerability could result in real financial losses.&lt;/p&gt;

&lt;h4&gt;
  
  
  Peer Dependencies
&lt;/h4&gt;

&lt;p&gt;I declared important dependencies as peer dependencies rather than bundling them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add bitcoin-wallet-connector @scure/base @scure/btc-signer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You directly control the versions of these dependencies&lt;/li&gt;
&lt;li&gt;If a dependency has a security vulnerability, you can &lt;strong&gt;upgrade immediately&lt;/strong&gt; without waiting for this library to release a new version&lt;/li&gt;
&lt;li&gt;You won't end up with two versions of &lt;code&gt;@scure/btc-signer&lt;/code&gt; bundled in your final build&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Optional Dependencies: Install Only What You Need
&lt;/h4&gt;

&lt;p&gt;Wallet SDKs (like &lt;code&gt;sats-connect&lt;/code&gt;, &lt;code&gt;@leather.io/rpc&lt;/code&gt;) are &lt;strong&gt;optional&lt;/strong&gt; peer dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Only supporting UniSat and OKX? No extra dependencies needed&lt;/span&gt;

&lt;span class="c"&gt;# Need Xverse support?&lt;/span&gt;
pnpm add sats-connect

&lt;span class="c"&gt;# Need Leather support?&lt;/span&gt;
pnpm add @leather.io/rpc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only install what you need, &lt;strong&gt;reducing your exposure to malicious package scripts&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dynamic Imports: Lazy Loading
&lt;/h4&gt;

&lt;p&gt;This is another important security design: &lt;strong&gt;Wallet SDKs are lazy-loaded via &lt;code&gt;dynamic import()&lt;/code&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Internal implementation sketch&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAvailability&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;getPrecondition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unisat&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initializer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Only when user actually wants to connect this wallet&lt;/span&gt;
    &lt;span class="c1"&gt;// will the corresponding implementation be loaded&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;UnisatWalletAdapterImpl&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./UnisatWalletAdapter.impl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UnisatWalletAdapterImpl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose &lt;code&gt;sats-connect&lt;/code&gt; gets compromised in a supply chain attack (not uncommon in the npm ecosystem). If your users only use UniSat wallet, &lt;strong&gt;the malicious code won't be loaded or executed&lt;/strong&gt;, because &lt;code&gt;sats-connect&lt;/code&gt; is only imported when users click "Connect Xverse".&lt;/p&gt;

&lt;p&gt;This should &lt;strong&gt;reduce users' exposure to supply chain attacks&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Integration
&lt;/h2&gt;

&lt;p&gt;Currently React integration is provided with an out-of-the-box Context Provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BitcoinConnectionProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useBitcoinConnectionContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bitcoin-wallet-connector/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;UnisatWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;XverseWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bitcoin-wallet-connector/adapters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapterFactories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nc"&gt;UnisatWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nc"&gt;XverseWalletAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BitcoinConnectionProvider&lt;/span&gt;
      &lt;span class="na"&gt;adapterFactories&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;adapterFactories&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onWalletConnected&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connected:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onWalletDisconnected&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WalletUI&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;BitcoinConnectionProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;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;function&lt;/span&gt; &lt;span class="nf"&gt;WalletUI&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;walletSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;availableAdapters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;disconnect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;useBitcoinConnectionContext&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;walletSession&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="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Connected: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;walletSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adapterId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Disconnect&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;availableAdapters&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;adapterId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;adapterId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapterId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Connect &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;adapterId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core &lt;code&gt;BitcoinWalletConnector&lt;/code&gt; is framework-agnostic. If you're using Vue, Svelte, Solid, or other frameworks, wrapping it into corresponding hooks/composables should be straightforward. Contributions are welcome!&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started and Contribute
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add bitcoin-wallet-connector @scure/base @scure/btc-signer

&lt;span class="c"&gt;# Install wallet SDKs as needed&lt;/span&gt;
pnpm add sats-connect      &lt;span class="c"&gt;# Xverse / Magic Eden&lt;/span&gt;
pnpm add @leather.io/rpc   &lt;span class="c"&gt;# Leather&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or get the demo running in 5 minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm storybook&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open &lt;a href="http://localhost:6006" rel="noopener noreferrer"&gt;http://localhost:6006&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can test wallet connections, signing, and other features in Storybook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contributing
&lt;/h3&gt;

&lt;p&gt;This is an open source project, and community contributions are very welcome!&lt;/p&gt;

&lt;p&gt;If you want to add support for a new wallet, there's one small preference: &lt;strong&gt;try to avoid depending on the wallet's official SDK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because many wallet APIs are just mounted on the &lt;code&gt;window&lt;/code&gt; object and can be called directly without introducing an additional SDK. For example, the UniSat, OKX, and Bitget adapters have zero external dependencies.&lt;/p&gt;

&lt;p&gt;Introducing an SDK means one more potential supply chain attack vector, one more package users need to install, and potentially unnecessary bundle size. Of course, if a wallet can only be accessed through its SDK (like Xverse's &lt;code&gt;sats-connect&lt;/code&gt;), that's fine — we can make it an optional peer dependency.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/bolasblack/bitcoin-wallet-connector/blob/master/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; for detailed contribution guidelines.&lt;/p&gt;




&lt;p&gt;The project is fully open source (MIT). Stars and contributions are welcome!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/bolasblack/bitcoin-wallet-connector" rel="noopener noreferrer"&gt;https://github.com/bolasblack/bitcoin-wallet-connector&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://bitcoin-wallet-connector.netlify.app/" rel="noopener noreferrer"&gt;https://bitcoin-wallet-connector.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/bitcoin-wallet-connector" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/bitcoin-wallet-connector&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bitcoin</category>
      <category>wallet</category>
      <category>web3</category>
      <category>react</category>
    </item>
    <item>
      <title>The Bitcoin Wallet Extensions Ecosystem is a Mess: A Developer's Rant</title>
      <dc:creator>c4605</dc:creator>
      <pubDate>Sat, 03 Jan 2026 19:13:49 +0000</pubDate>
      <link>https://forem.com/c4605/the-bitcoin-wallet-ecosystem-is-a-mess-a-developers-rant-426</link>
      <guid>https://forem.com/c4605/the-bitcoin-wallet-ecosystem-is-a-mess-a-developers-rant-426</guid>
      <description>&lt;p&gt;If you're building a Bitcoin dApp, you've probably already discovered that the Bitcoin community's ecosystem is an absolute dumpster fire.&lt;/p&gt;

&lt;p&gt;The simplest example is Bitcoin wallets. Everyone seems eager to create standards for others, but nobody seems eager to actually implement them.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Standards"? What Standards?
&lt;/h2&gt;

&lt;p&gt;Let me be more specific:&lt;/p&gt;

&lt;h3&gt;
  
  
  WBIPs: Even the Standard Makers Don't Follow Their Own Standards
&lt;/h3&gt;

&lt;p&gt;Leather and Xverse created &lt;a href="https://wbips.netlify.app/" rel="noopener noreferrer"&gt;WBIPs&lt;/a&gt;, supposedly to establish a unified Bitcoin wallet API standard, but:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The signPsbt finalize parameter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/wbips/wbips-docs/blob/a7862572785686da8c0097125366086fea3c70d6/pages/request_api/signPsbt.md" rel="noopener noreferrer"&gt;&lt;code&gt;signPsbt&lt;/code&gt;&lt;/a&gt; standard defines a &lt;code&gt;finalize&lt;/code&gt; parameter, but neither of the initiators implemented it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leather's &lt;a href="https://github.com/leather-io/mono/blob/2570b8e89e1a7d67ff2ec469c42b321f5d956e92/packages/rpc/src/methods/bitcoin/sign-psbt.ts#L25-L35" rel="noopener noreferrer"&gt;code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xverse's &lt;a href="https://github.com/secretkeylabs/sats-connect-core/blob/cfbbdfde28a646f1b5114dad39ec76da85c3ffe9/src/request/types/btcMethods.ts#L178-L192" rel="noopener noreferrer"&gt;code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The wallet auto-discovery mechanism is completely wrong&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wbips/wbips-docs/blob/a7862572785686da8c0097125366086fea3c70d6/pages/wbips/WBIP004.md" rel="noopener noreferrer"&gt;WBIP004&lt;/a&gt; defines a wallet plugin auto-discovery mechanism, where the first step is to inject the wallet provider into &lt;code&gt;wbip_providers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But in reality, both Xverse and Leather inject into &lt;code&gt;window.btc_providers&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leather's &lt;a href="https://github.com/leather-io/mono/blob/2570b8e89e1a7d67ff2ec469c42b321f5d956e92/packages/provider/src/add-leather-to-providers.ts#L21-L52" rel="noopener noreferrer"&gt;code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xverse's &lt;a href="https://github.com/secretkeylabs/xverse-web-extension/blob/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/inpage/index.ts#L53" rel="noopener noreferrer"&gt;code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Two other major wallets don't care at all&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not to mention that &lt;strong&gt;UniSat&lt;/strong&gt; and &lt;strong&gt;OKX Wallet&lt;/strong&gt;, which actually have massive traffic, seem to have zero interest in supporting WBIPs.&lt;/p&gt;

&lt;h3&gt;
  
  
  sats-connect: A Standard on Top of Standards
&lt;/h3&gt;

&lt;p&gt;Then, beyond WBIPs, Xverse also created &lt;a href="https://github.com/xverseapp/sats-connect" rel="noopener noreferrer"&gt;sats-connect&lt;/a&gt;, apparently wanting to extend some of their own APIs beyond WBIPs, implementing a standard on top of a standard.&lt;/p&gt;

&lt;p&gt;This isn't bad, especially since it also provides compatibility with the UniSat API. Magic Eden even claims to be "sats-connect compatible."&lt;/p&gt;

&lt;p&gt;"This sounds absolutely amazing! sats-connect is the meta! The savior of the world!" Right? That's what you're thinking.&lt;/p&gt;

&lt;p&gt;But there are still "two small clouds" hovering over this perfect world:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;sats-connect doesn't support OKX Wallet&lt;/strong&gt; (even though OKX Wallet's API has now migrated to be UniSat-compatible, sats-connect can't auto-discover it)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Magic Eden's auto-discovery follows a different standard&lt;/strong&gt;: &lt;a href="https://github.com/ExodusMovement/bitcoin-wallet-standard" rel="noopener noreferrer"&gt;&lt;code&gt;ExodusMovement/bitcoin-wallet-standard&lt;/code&gt;&lt;/a&gt;, not the WBIPs standard. So you can't rely on sats-connect to auto-discover it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Wait What, Another Standard?
&lt;/h3&gt;

&lt;p&gt;"Wait, what the hell is this &lt;code&gt;ExodusMovement/bitcoin-wallet-standard&lt;/code&gt;? I've never heard of it!"&lt;/p&gt;

&lt;p&gt;Right, before I tried to integrate Magic Eden, I hadn't heard of it either. Now I've heard of it, and so have you.&lt;/p&gt;

&lt;h3&gt;
  
  
  More Fun Facts
&lt;/h3&gt;

&lt;p&gt;Oh, here's a "fun fact": if your users have both Xverse and Magic Eden installed, and you don't handle it properly, when they click "Connect Wallet," they might &lt;strong&gt;randomly receive a confirmation request from one of the two wallets&lt;/strong&gt;. Life is full of surprises.&lt;/p&gt;

&lt;p&gt;Oh, and another "fun fact": although Magic Eden claims to be "sats-connect compatible," it's actually compatible with &lt;strong&gt;an older version of the API&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show Some Code
&lt;/h2&gt;

&lt;p&gt;You might think, "Fine, I'll just integrate them all myself." So let's look at the code differences for implementing a simple "connect wallet" feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// UniSat - Direct window object call&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unisatResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unisat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAccounts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Leather - Yet another API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;leatherResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LeatherProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getAddresses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Xverse - From official docs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sats-connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xverseResponse&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;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getAddresses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Magic Eden - From official docs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AddressPurpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BitcoinNetworkType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sats-connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;getProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getBtcProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;purposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AddressPurpose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ordinals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AddressPurpose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Address for receiving Ordinals and payments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BitcoinNetworkType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mainnet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onFinish response, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;connectionStatus&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setAccounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onCancel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request canceled&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four wallets, four different implementations.&lt;/p&gt;

&lt;p&gt;What if you need to integrate 6 wallets in your project? Congratulations, you'll need to go through all the issues I mentioned above, and enjoy wasting a huge chunk of your life along with me.&lt;/p&gt;

&lt;h2&gt;
  
  
  To Sum It Up
&lt;/h2&gt;

&lt;p&gt;"F**k the stupid ecosystem, what a dumpster fire!" — you/I thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I Built a Library
&lt;/h2&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/bolasblack/bitcoin-wallet-connector" rel="noopener noreferrer"&gt;bitcoin-wallet-connector&lt;/a&gt; to help others waste less time on this crap.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;next article&lt;/strong&gt;, I'll detail the usage and design philosophy of this library. If you can't wait, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://bitcoin-wallet-connector.netlify.app/iframe.html?id=components-bitcoinconnectionprovider--default&amp;amp;viewMode=story" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/bolasblack/bitcoin-wallet-connector" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bitcoin</category>
      <category>wallet</category>
      <category>web3</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Moved My Config Sharing Repo to LLM-oriented Mode</title>
      <dc:creator>c4605</dc:creator>
      <pubDate>Fri, 02 Jan 2026 10:41:03 +0000</pubDate>
      <link>https://forem.com/c4605/i-moved-my-config-sharing-repo-to-llm-oriented-mode-5fik</link>
      <guid>https://forem.com/c4605/i-moved-my-config-sharing-repo-to-llm-oriented-mode-5fik</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Using npm to distribute shared configs isn't great. "LLM-oriented config sharing" lets you keep full ownership of your configs while enjoying intelligent upgrade assistance.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;And here's my updated repo &lt;a href="https://github.com/bolasblack/js-metarepo/tree/develop/packages/toolconfs" rel="noopener noreferrer"&gt;https://github.com/bolasblack/js-metarepo/tree/develop/packages/toolconfs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What is "LLM-oriented Config Sharing"?
&lt;/h2&gt;

&lt;p&gt;Simply put, config sharing projects provide &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt;, and consumers use it to let LLMs download/update project config files to keep them up to date—instead of distributing configs through npm registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Why Not Use npm Anymore?
&lt;/h2&gt;

&lt;p&gt;Traditional ways of sharing tool configurations—whether through &lt;code&gt;extends&lt;/code&gt; or presets—all face a fundamental contradiction: &lt;strong&gt;the trade-off between convenience and flexibility&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pros of Inheritance Mode
&lt;/h3&gt;

&lt;p&gt;When you use something like &lt;code&gt;extends: "@company/eslint-config"&lt;/code&gt;, the benefits are obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Easy upgrades&lt;/strong&gt;: Just &lt;code&gt;npm update&lt;/code&gt;, and all projects using this config get the updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency guaranteed&lt;/strong&gt;: All projects in the team use the same rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized maintenance&lt;/strong&gt;: Improvements and bug fixes only need to happen in one place&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  But Reality is Harsh
&lt;/h3&gt;

&lt;p&gt;Problems arise when you need to do anything "non-standard":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Custom requirements&lt;/strong&gt;: Your project has special needs and requires overriding certain rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency version conflicts&lt;/strong&gt;: You need to upgrade ESLint, but the upstream config package hasn't caught up yet (e.g., ESLint moved to flat config mode, and you need the latest version, but I'm lazy and haven't updated yet 🤪)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule disagreements&lt;/strong&gt;: Upstream updated a rule, but it doesn't fit your project (the problem is you might not know at first, and only realize "damn, I got burned" when things break)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, you have two choices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: Patch on top of inheritance&lt;/strong&gt;&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;// eslint.config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Override one rule&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some-rule&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;off&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Override another&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;another-rule&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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="na"&gt;different&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;options&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="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Still need to handle plugin version conflicts...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As patches pile up, your "inheritance" becomes layer upon layer of overrides, ending up harder to maintain than writing from scratch.&lt;/p&gt;

&lt;p&gt;ESLint is actually not too bad. Tools like Prettier and lint-staged that don't have extends/merge mechanisms are even more troublesome. I previously wrote a super complicated lint-staged config (see &lt;a href="https://github.com/bolasblack/js-metarepo/blob/0270b291651bcbc75a3ecd81d391f4ad836a814e/packages/toolconfs/lint-staged.config.js" rel="noopener noreferrer"&gt;&lt;code&gt;lint-staged.config.js&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/bolasblack/js-metarepo/blob/0270b291651bcbc75a3ecd81d391f4ad836a814e/packages/toolconfs/lint-staged.helpers.js" rel="noopener noreferrer"&gt;&lt;code&gt;lint-staged.helpers.js&lt;/code&gt;&lt;/a&gt;, or see belo). It looks decent (well, at least I think so), but it's actually super hard to use and makes a very simple thing extremely complex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B: Eject&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many scaffold tools provide an &lt;code&gt;eject&lt;/code&gt; command that copies all config files into your project, giving you full control.&lt;/p&gt;

&lt;p&gt;But after ejecting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You lose the ability to auto-upgrade; you have to update yourself and can't benefit from upstream's tested plugin compatibility&lt;/li&gt;
&lt;li&gt;When upstream has important updates, you need to manually diff and merge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is the dilemma&lt;/strong&gt;: In inheritance mode you're constrained by upstream; after ejecting you bear the maintenance cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLM is a Game Changer
&lt;/h2&gt;

&lt;p&gt;Now we have LLMs. Times have changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The New Workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get latest configs&lt;/strong&gt;: LLM fetches the latest recommended configs directly from llms.md/llms.txt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart merging&lt;/strong&gt;: LLM understands your project context and merges new configs into existing ones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve customizations&lt;/strong&gt;: Your custom modifications are recognized and preserved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update on demand&lt;/strong&gt;: When you need to upgrade, let LLM handle the merge conflicts&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why is This Better Than Traditional Approaches?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;All configs live in your codebase&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No hidden config files deep in &lt;code&gt;node_modules&lt;/code&gt;, no confusion about "where did this rule come from". Every line of config is clearly visible in your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upgrades become controllable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Upgrading is no longer an "all or nothing" decision. LLM can help you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See what changed upstream&lt;/li&gt;
&lt;li&gt;Understand the impact of each change&lt;/li&gt;
&lt;li&gt;Selectively apply updates&lt;/li&gt;
&lt;li&gt;Automatically handle merge conflicts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Customization is a first-class citizen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your customizations are no longer "patches"—they're part of the config. LLM understands and respects these customizations when updating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency version freedom&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can upgrade any dependency anytime without waiting for upstream config packages to update. Run into issues? LLM can help adjust the config (and configs are easier for them to read now too).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upstream config files become simpler&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's look at my lint-staged config files again.&lt;/p&gt;

&lt;p&gt;Previous version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/bolasblack/js-metarepo/blob/0270b291651bcbc75a3ecd81d391f4ad836a814e/packages/toolconfs/lint-staged.config.js" rel="noopener noreferrer"&gt;&lt;code&gt;lint-staged.config.js&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;css&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lint-staged.helpers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&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;*.{ts,tsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*.{js,jsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*.{css,scss,sass,less}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*.{md,mdx}&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Helper functions I had to write so downstream could reuse and customize the lint-staged config: &lt;a href="https://github.com/bolasblack/js-metarepo/blob/0270b291651bcbc75a3ecd81d391f4ad836a814e/packages/toolconfs/lint-staged.helpers.js" rel="noopener noreferrer"&gt;&lt;code&gt;lint-staged.helpers.js&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&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;wrap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;ensureArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ensureArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&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;tap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ensureArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ensureArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&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;finish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prettier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;cliFileNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fileNamesToCliArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// prettier-ignore&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yarn prettier --write &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cliFileNames&lt;/span&gt;&lt;span class="p"&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;eslint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;cliFileNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fileNamesToCliArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint&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="c1"&gt;// prettier-ignore&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yarn eslint &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cliFileNames&lt;/span&gt;&lt;span class="p"&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;prCmds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cmds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;operators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;atoms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;prCmds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;prettier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;prettier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;css&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;prettier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;prettier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ensureArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fileNamesToCliArg&lt;/span&gt;&lt;span class="p"&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;function&lt;/span&gt; &lt;span class="nf"&gt;ensureArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&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;obj&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fileNamesToCliArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&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;names&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;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&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="s1"&gt; &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;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/bolasblack/js-metarepo/blob/25d4495adcba67bda9daf65b04a204acc01cb795/packages/toolconfs/lint-staged.config.js" rel="noopener noreferrer"&gt;&lt;code&gt;lint-staged.config.js&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
Actually, this is so simple now, let me just inline it:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&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;*.{ts,tsx,js,jsx}&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prettier --write&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;eslint&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;*.{css,scss,sass,less,md,mdx}&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prettier --write&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;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lint-staged.helpers.js&lt;/code&gt; no longer exists—because it's no longer needed&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  You've Said a Lot, But Doesn't This Still Look Like a Step Backward?
&lt;/h2&gt;

&lt;p&gt;Yes and no.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes&lt;/strong&gt;: Config files do exist independently in each project, just like the old days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No&lt;/strong&gt;: Because the maintenance cost has fundamentally changed:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before&lt;/strong&gt;: Manually read CHANGELOG or diff against upstream config -&amp;gt; Figure out what changed -&amp;gt; Manually modify -&amp;gt; Test and verify&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Now&lt;/strong&gt;: Tell LLM "upgrade to the latest recommended config" -&amp;gt; Review LLM's changes -&amp;gt; Done&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The cognitive burden of maintenance shifts from "I need to understand all these configs" to "I need to review LLM's suggestions". This is what's different from the early days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary (AI version, couldn't write this myself)
&lt;/h2&gt;

&lt;p&gt;Traditional config sharing forces a choice between "one-size-fits-all convenience" and "full autonomy with complexity". The LLM-oriented approach lets us have both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain full ownership and transparency of configs&lt;/li&gt;
&lt;li&gt;While enjoying intelligent assistance for upgrades and maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a step backward in technology—it's redefining best practices with new tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/bolasblack/js-metarepo/blob/develop/packages/toolconfs/docs/moved-to-llm-oriented.en.md" rel="noopener noreferrer"&gt;The Original Article&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>maintainability</category>
    </item>
    <item>
      <title>I built (another) Elm-style `useEffectReducer` hook for React ⚛️</title>
      <dc:creator>c4605</dc:creator>
      <pubDate>Wed, 12 Nov 2025 22:21:51 +0000</pubDate>
      <link>https://forem.com/c4605/i-built-another-elm-style-useeffectreducer-hook-for-react-5fkd</link>
      <guid>https://forem.com/c4605/i-built-another-elm-style-useeffectreducer-hook-for-react-5fkd</guid>
      <description>&lt;p&gt;GitHub: &lt;a href="https://github.com/bolasblack/react-components/tree/develop/packages/useEffectReducer" rel="noopener noreferrer"&gt;bolasblack/react-components/tree/develop/packages/useEffectReducer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🌳 What is “Elm-style”?
&lt;/h2&gt;

&lt;p&gt;Elm popularized the &lt;strong&gt;Model-View-Update&lt;/strong&gt; pattern, where each update returns both the next model and the commands to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design keeps all logic about &lt;em&gt;“what happens when event X occurs”&lt;/em&gt; in &lt;strong&gt;one place&lt;/strong&gt; — the update function — instead of scattering side effects across random &lt;code&gt;useEffect&lt;/code&gt;s.&lt;br&gt;
It also keeps reducers pure while making &lt;strong&gt;when and what side effects happen&lt;/strong&gt; explicit and interpretable by a runtime.&lt;/p&gt;

&lt;p&gt;React’s docs echo this philosophy: effects should be an &lt;em&gt;escape hatch&lt;/em&gt;, not the default — see &lt;a href="https://react.dev/learn/you-might-not-need-an-effect" rel="noopener noreferrer"&gt;You Might Not Need an Effect&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to see how this kind of modeling makes UI logic elegant and maintainable, check out David Khourshid’s classic post &lt;a href="https://dev.to/davidkpiano/no-disabling-a-button-is-not-app-logic-598i"&gt;&lt;strong&gt;“No, disabling a button is not app logic.”&lt;/strong&gt;&lt;/a&gt; 💡&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  ⚛️ So why &lt;em&gt;another&lt;/em&gt; Elm-style reducer?
&lt;/h2&gt;

&lt;p&gt;I know there are several similar libraries — and I like many of them! — but I wanted a variant with different trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🧱 &lt;strong&gt;Some are archived.&lt;/strong&gt; For example, &lt;a href="https://github.com/davidkpiano/useEffectReducer" rel="noopener noreferrer"&gt;&lt;code&gt;useEffectReducer&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/soywod/react-use-bireducer" rel="noopener noreferrer"&gt;&lt;code&gt;react-use-bireducer&lt;/code&gt;&lt;/a&gt; are now read-only.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⚙️ &lt;strong&gt;Keep it tiny.&lt;/strong&gt; Fits in one file with almost no dependencies — copy, tweak, or delete it whenever you want.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🧪 &lt;strong&gt;Effects as plain objects + separate interpreter.&lt;/strong&gt; I prefer returning serializable effect descriptors and implementing the &lt;em&gt;actual&lt;/em&gt; effect logic in one dedicated place. It’s easier to test reducers (assert on descriptors) without invoking real side effects. (Elm’s &lt;code&gt;update : Msg -&amp;gt; Model -&amp;gt; (Model, Cmd Msg)&lt;/code&gt; inspires this split.)&lt;/p&gt;

&lt;p&gt;Of course, &lt;code&gt;useEffectReducer&lt;/code&gt; doesn’t stop you from returning a function as an effect — the API is flexible enough; it’s just not my personal preference 🙂.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;💬 &lt;strong&gt;Lower the barrier.&lt;/strong&gt; I don’t want people curious about the Elm-style approach to feel like they must first study Elm’s &lt;code&gt;Cmd Msg&lt;/code&gt; system or learn a full-blown state machine library like &lt;a href="https://xstate.js.org/" rel="noopener noreferrer"&gt;XState&lt;/a&gt; just to try this pattern.&lt;/p&gt;

&lt;p&gt;Both &lt;strong&gt;XState&lt;/strong&gt; and &lt;strong&gt;Elm&lt;/strong&gt; are &lt;em&gt;amazing&lt;/em&gt; — I’m genuinely thankful to David Khourshid and all of XState’s contributors, and to the Elm community. I’ve used XState in multiple real projects (it’s saved me countless times!), but it does have a learning curve. Moving from &lt;code&gt;useReducer&lt;/code&gt; to &lt;code&gt;useEffectReducer&lt;/code&gt;, on the other hand, should feel smooth and approachable 🚀.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🧪 Example usage
&lt;/h2&gt;

&lt;p&gt;Let’s reimplement the example from David Khourshid’s article&lt;br&gt;
&lt;a href="https://dev.to/davidkpiano/no-disabling-a-button-is-not-app-logic-598i"&gt;&lt;strong&gt;“No, disabling a button is not app logic.”&lt;/strong&gt;&lt;/a&gt;, but this time using &lt;code&gt;useEffectReducer&lt;/code&gt; ✨&lt;/p&gt;

&lt;p&gt;

&lt;iframe src="https://codesandbox.io/embed/7wdzgz?module=%2Fsrc%2FApp.tsx"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;You can also check out both implementations on GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🪄 &lt;strong&gt;useEffectReducer version:&lt;/strong&gt;
&lt;a href="https://github.com/bolasblack/react-components/blob/eb47a2416e8cc95bb4fa7e6fbf776ac2432a2468/packages/useEffectReducer/src/useEffectReducer.stories.tsx#L121-L203" rel="noopener noreferrer"&gt;useEffectReducer.stories.tsx → L121–203&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⚙️ &lt;strong&gt;useReducer version:&lt;/strong&gt;
&lt;a href="https://github.com/bolasblack/react-components/blob/eb47a2416e8cc95bb4fa7e6fbf776ac2432a2468/packages/useEffectReducer/src/useEffectReducer.stories.tsx#L24-L119" rel="noopener noreferrer"&gt;useEffectReducer.stories.tsx → L24–119&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Related work
&lt;/h2&gt;

&lt;p&gt;Here are some excellent existing takes on bringing &lt;strong&gt;Elm-style “state + effects” reducers&lt;/strong&gt; into React:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/davidkpiano/useEffectReducer" rel="noopener noreferrer"&gt;&lt;code&gt;davidkpiano/useEffectReducer&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — by &lt;em&gt;David Khourshid&lt;/em&gt;, effectful reducers via an &lt;code&gt;exec&lt;/code&gt; helper; archived.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/soywod/react-use-bireducer" rel="noopener noreferrer"&gt;&lt;code&gt;soywod/react-use-bireducer&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — returns &lt;code&gt;[state, effects]&lt;/code&gt; and processes effects through a separate “effect reducer”; archived.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/ncthbrt/react-use-elmish" rel="noopener noreferrer"&gt;&lt;code&gt;ncthbrt/react-use-elmish&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — Elmish-style hook combining reducer logic with async helpers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/redux-loop/redux-loop" rel="noopener noreferrer"&gt;&lt;code&gt;redux-loop&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — Redux enhancer adding Elm-like effect tuples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/dai-shi/use-reducer-async" rel="noopener noreferrer"&gt;&lt;code&gt;dai-shi/use-reducer-async&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — extends &lt;code&gt;dispatch&lt;/code&gt; for async actions; conceptually related.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://gist.github.com/sophiebits/145c47544430c82abd617c9cdebefee8" rel="noopener noreferrer"&gt;&lt;code&gt;useReducerWithEmitEffect&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — Sophie Alpert’s gist that inspired much of this work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📜 Good Articles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Christian Ekrem - "&lt;a href="https://cekrem.github.io/posts/chapter-2-take-2/" rel="noopener noreferrer"&gt;Chapter 2, Take 2: Why I Changed Course&lt;/a&gt; - If you want to understand Elm’s architecture, you can read this newly published article&lt;/li&gt;
&lt;li&gt;David Khourshid – "&lt;a href="https://dev.to/davidkpiano/redux-is-half-of-a-pattern-1-2-1hd7"&gt;Redux is half of a pattern (1/2)&lt;/a&gt;"&lt;/li&gt;
&lt;li&gt;David Khourshid – "&lt;a href="https://medium.com/%40DavidKPiano/there-are-so-many-fundamental-misunderstandings-about-xstate-and-state-machines-in-general-in-13aec57d2f85" rel="noopener noreferrer"&gt;There are so many fundamental misunderstandings about XState (and state machines in general)&lt;/a&gt;"&lt;/li&gt;
&lt;li&gt;David Khourshid – "&lt;a href="https://dev.to/davidkpiano/no-disabling-a-button-is-not-app-logic-598i"&gt;No, disabling a button is not app logic.&lt;/a&gt;"&lt;/li&gt;
&lt;li&gt;React docs – “You Might Not Need an Effect” &lt;a href="https://react.dev/learn/you-might-not-need-an-effect" rel="noopener noreferrer"&gt;https://react.dev/learn/you-might-not-need-an-effect&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>react</category>
      <category>elm</category>
      <category>redux</category>
    </item>
  </channel>
</rss>
