<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
     xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
     xmlns:georss="http://www.georss.org/georss"
     xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
     xmlns:media="http://search.yahoo.com/mrss/"><channel>
  <title>Convivial Computing</title>
  <atom:link href="https://convivialcomputing.net/rss.xml" rel="self" type="application/rss+xml" />
  <link>https://convivialcomputing.net</link>
  <description><![CDATA[]]></description>
  <language>en</language>
  <pubDate>Thu, 21 May 2026 06:48:32 -0400</pubDate>
  <lastBuildDate>Thu, 21 May 2026 06:48:32 -0400</lastBuildDate>
  <generator>Emacs 30.2 Org-mode 9.7.11</generator>
  <webMaster>swannod@sdf.org (David Nolen)</webMaster>
  <image>
    <url>https://orgmode.org/img/org-mode-unicorn-logo.png</url>
    <title>Convivial Computing</title>
    <link>https://convivialcomputing.net</link>
  </image>

  <item>
    <title>Designing for a Lighter Web</title>
    <link>https://convivialcomputing.net/posts/2026-05-20-lite-mode.html</link>
    <author>swannod@sdf.org (David Nolen)</author>
    <guid isPermaLink="false">https://convivialcomputing.net/posts/2026-05-20-lite-mode.html</guid>
    <pubDate>Wed, 20 May 2026 00:00:00 -0400</pubDate>

    <description><![CDATA[<p>
    Back in 2011 most JavaScript web developers were still carefully curating their dependencies and manually filling in gaps in browser functionality. <a href="https://clojurescript.org">ClojureScript</a> differentiated itself by offering a richer set of tools (cljs.core) and a large standard library (<a href="https://github.com/clojure/closure-library">Google Closure Library</a>) counter-balanced by state-of-the-art tree-shaking via the <a href="https://github.com/google/closure-compiler">Google Closure Compiler</a>.
    </p>

    <p>
    But in a few short years that distinction became less clear as the industry collectively pivoted to larger and larger client-centric web applications built upon ever more complex dependency graphs and the tooling to make it work (sorta). Whether you wrote JavaScript, TypeScript, or ClojureScript, or used framework X or Y, these decisions amounted to choosing the color of the deck chair. When I started my current job last year, the company product required megabytes of bundled JavaScript and took seconds to achieve an interactive state.
    </p>

    <p>
    A year and a rewrite later, our application can present 100X more data <i>instantly</i> yet we deliver only ~30K brotli of client code. Implementing UI features is faster as we don't need to wade through component APIs unaligned to our narrower use cases. We don't need to manage state in two different locations. We haven't spent any time designing and building a client API that's constantly evolving (polite for breaking) to fit business needs. We're certainly not fiddling with Node dependencies or babysitting the build tooling. There's still an incredible amount of work to be done, but a good chunk of incidental complexity and busy work vanished.
    </p>

    <p>
    I don't believe that our experience is an isolated one. The growing popularity of <a href="https://htmx.org">HTMX</a>, <a href="https://data-star.dev">DataStar</a> (we use this), and related approaches that look more like <a href="https://johnresig.com/blog/ajax-experience-boston/">2006 than 2026</a>, show that developers who have retained their keen senses in an AI-pilled age are turning away from the current excesses and are out in the wilderness foraging for something simpler. Perhaps somewhere in that wilderness lie the ruins of a civilized age.
    </p>

    <p>
    Start with a convivial use case
    I think one mark of a great tool is its suitability for convivial usecases rather than industrial ones. Years before I was paid to write ClojureScript, I just used it for <i>fun</i> on my blog. The <a href="https://swannodette.github.io/2013/08/02/100000-processes/">gzipped bundle sizes of my ClojureScript examples</a> were either smaller or comparable to minified and gzipped <a href="https://jquery.com">jQuery</a>. These days that's positively slim, but I wanted to narrow the gap relative to handwritten, dependency-less JavaScript.
    </p>

    <p>
    One idea I had floating around for a long time was a ClojureScript compiler mode that swapped in the simpler copy-on-write data structures from the original 2011 release rather than the <a href="https://hypirion.com/musings/understanding-persistent-vector-pt-1">sophisticated persistent datastructure implementations</a> we faithfully ported from Clojure Java source files. 
    </p>

    <p>
    Last summer while pondering HTMX, DOM morphing, and DataStar, I started jotting down implementation notes in my decade old ClojureScript org file and tweaking the ClojureScript compiler late into the night.
    </p>

    <p>
    Unraveling the knots
    It's only a half-joke that Clojure is a bunch of data structures with a programming language attached. In the early days, Clojure made a number of clever optimizations around these core data structures, and these optimizations were often strongly connected. That is, they don't play well with tree-shaking. For example, if you use <code>cljs.core/PersistentVector</code>, well then you need <code>cljs.core/TransientVector</code> and <code>cljs.core/ChunkedSeq</code> and <code>cljs.core/ChunkedCons</code> and, and ...
    </p>

    <p>
    So writing an innocuous <code>[]</code> actually pulls in quite a few things. For server code this isn't very interesing, but on the client these interconnections get in the way of shaving off a few more kilobytes. ClojureScript had a lower bound of about 18 kilobytes brotli. Could we get that down to 12K? 8K? Less?
    </p>

    <p>
    The other knot to untie in the code base was printing, because REPL stands for Read-Eval-<i>Print</i>-Loop. Because printing knows about every single data type including internal structures in some cases, <code>(println "Hello world!")</code> all by itself will pull in the entire standard library because, again, Clojure is a bunch of data structures with a programming language attached.
    </p>

    <p>
    <b>Ouch</b>.
    </p>

    <p>
    Art of the Metaobject Protocol
    I like to think that Clojure took a page from the <a href="https://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Protocol">Art of the Metaobject Protocol</a>. Rather than design a Lisp on concrete datatypes as is traditionally done, Clojure erected a language on a rich set of interfaces. Later, ClojureScript was the first dialect of the Clojure programming language to be written on top of Clojure protocols. The beauty of this polymorphic library design meant that <code>:lite-mode</code> was largely a copy-and-paste affair, no significant changes to the standard library required. Most of the effort was in updating the 2011 implementations to match the broader expectations 15 years later. A few new protocols had been added and quite a few behavioral expectations from Clojure were captured in a number of tests.
    </p>

    <p>
    Under <code>:lite-mode</code>, ClojureScript simply emits constructor calls to the old data structures. Google Closure advanced compilation will see that none of the more complex implementations are used and remove them.
    </p>

    <p>
    For the case of printing, the answer was even simpler. Just elide the printing machinery. Recursive printing of ClojureScript datastructures is just dead weight for many simpler programs. Every data structure implements <code>toString</code>. If the user supplies the <code>:elide-to-string</code> compiler flag we simply drop those implementations.
    </p>

    <p>
    What follows is the comparison table I maintained by hand in the org mode file during development. I haven't changed anything here at all and in some cases I don't even know what I was thinking anymore. <b>expression</b> is the ClojureScript expression. <b>size</b> is the actual size of the advanced compiled output. <b>brotli</b> is after compression. <b>release</b> is the baseline (the output of the main branch after brotli).
    </p>

    <table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">


    <colgroup>
    <col  class="org-left" />

    <col  class="org-left" />

    <col  class="org-left" />

    <col  class="org-left" />

    <col  class="org-left" />
    </colgroup>
    <thead>
    <tr>
    <th scope="col" class="org-left">expression</th>
    <th scope="col" class="org-left">size</th>
    <th scope="col" class="org-left">brotli</th>
    <th scope="col" class="org-left">release</th>
    <th scope="col" class="org-left">note</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td class="org-left">:foo</td>
    <td class="org-left">5K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">[]</td>
    <td class="org-left">15K</td>
    <td class="org-left">3K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">needed RSeq</td>
    </tr>

    <tr>
    <td class="org-left">{}</td>
    <td class="org-left">32K</td>
    <td class="org-left">6K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">{:foo "bar"}</td>
    <td class="org-left">3K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">surprising!</td>
    </tr>

    <tr>
    <td class="org-left">(seq {:foo "bar"})</td>
    <td class="org-left">28K</td>
    <td class="org-left">6K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">-seq min assumption</td>
    </tr>

    <tr>
    <td class="org-left">()</td>
    <td class="org-left">11K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">#{}</td>
    <td class="org-left">22K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(1 2 3)</td>
    <td class="org-left">14K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(count [])</td>
    <td class="org-left">15K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(count {})</td>
    <td class="org-left">19K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(conj [] 1)</td>
    <td class="org-left">16K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(conj {} [1 2])</td>
    <td class="org-left">29K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(-conj {} [1 2])</td>
    <td class="org-left">29K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(-assoc {} :bar 2)</td>
    <td class="org-left">23K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(-assoc [0] 0 1)</td>
    <td class="org-left">4K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">WHAT</td>
    </tr>

    <tr>
    <td class="org-left">(-dissoc {} :foo)</td>
    <td class="org-left">22K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">what I expect</td>
    </tr>

    <tr>
    <td class="org-left">(map inc (range 10))</td>
    <td class="org-left">27K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(-&gt;&gt; (map inc ... (drop 1)))</td>
    <td class="org-left">28K</td>
    <td class="org-left">6K</td>
    <td class="org-left">19K</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(reduce + 0 [1 2 3 4 5])</td>
    <td class="org-left">18K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(merge {:foo 1} {:bar 2})</td>
    <td class="org-left">31K</td>
    <td class="org-left">7K</td>
    <td class="org-left">18K</td>
    <td class="org-left">&#xa0;</td>
    </tr>

    <tr>
    <td class="org-left">(merge {:foo 1} {:bar 2})</td>
    <td class="org-left">43K</td>
    <td class="org-left">8K</td>
    <td class="org-left">18K</td>
    <td class="org-left">w/ .toString</td>
    </tr>

    <tr>
    <td class="org-left">(println (merge {:foo 1} {:bar 2}))</td>
    <td class="org-left">62K</td>
    <td class="org-left">12K</td>
    <td class="org-left">16K</td>
    <td class="org-left">release is 89K</td>
    </tr>

    <tr>
    <td class="org-left">(. HashMap -EMPTY)</td>
    <td class="org-left">21K</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">&#xa0;</td>
    <td class="org-left">reasonable</td>
    </tr>
    </tbody>
    </table>

    <p>
    "surprising!" and "WHAT" comments indicate cases where Google Closure Compiler could see through the data structure implementation (easier with 2011 code) and optimize almost everything away.
    </p>

    <p>
    The broad takeaway is that the main branch was already stunning. You really can fearlessly use the ClojureScript standard library and not worry about getting a bloated output. <code>:lite-mode</code> gets rid of the 18K wall, but the more you use the standard library the returns gradually diminish.
    </p>

    <p>
    But this isn't a problem. ClojureScript provides great support for interacting with JavaScript objects and arrays. For more discerning users <code>:lite-mode</code> + <code>:elide-to-string</code> guarantees that the code size won't unexpectedly explode. Part of the changes included adding a bunch of tests that verify the size of programs under the new compiler flags avoid introducing unintended tree-shaking issues to the standard library. Even if many people never use these flags, <i>everyone</i> benefits from the analysis that was required to achieve these results. I'd like to a draw analogy with NetBSD. Most people are not going to run NetBSD on an Amiga, but as a result NetBSD is a simple and light system <i>everywhere</i>.
    </p>

    <p>
    We need a lighter web, and ClojureScript is not a bad tool to bring along the way.
    </p>
    ]]></description>
</item>
<item>
  <title>Community Gardens</title>
  <link>https://convivialcomputing.net/posts/2026-05-16-community-gardens.html</link>
  <author>swannod@sdf.org (David Nolen)</author>
  <guid isPermaLink="false">https://convivialcomputing.net/posts/2026-05-16-community-gardens.html</guid>
  <pubDate>Sat, 16 May 2026 00:00:00 -0400</pubDate>

  <description><![CDATA[<p>
  As Tolstoy once said, all convivial software systems look much the same, but all nonconvivial software systems are nonconvivial in the same way (wink). One tell of nonconvivial software is complex dependency graphs. Everyone scratches their own itch while at the same time depending on something else for some relatively simple unit of functionality. Coordination is low and someone else pays the price.
  </p>

  <p>
  This is in direct contrast to how UNIX systems were historically organized where a user is offered a well curated set of tools for a wide variety of tasks. Determining the precise boundaries of "a useful set of tools" requires a great deal of restraint and a proclivity for "N-O".
  </p>

  <p>
  Some operating system distributions decide that programming language X should be a part of the distribution because someone decided that some aspect of the system required X to work. This inexorably leads to the situation where a great many people convince themselves that isolation via a virtual machine is neccesary for Getting Things Done.
  </p>

  <p>
  More technology will never solve what ails us.
  </p>

  <p>
  In contrast, NetBSD depends only on its own implementation of the Bourne shell. It also picks a good set of tools for serving websites without ceremony, living up to its name. This instance serves this website with the following three lines in <code>/etc/rc.conf</code>:
  </p>

  <div class="org-src-container">
  <pre class="src src-shell"><span class="org-variable-name">http</span>=YES
  <span class="org-variable-name">httpd_wwdir</span>=<span class="org-string">"/var/www/prod"</span>
  <span class="org-variable-name">httpd_flags</span>=<span class="org-string">"..."</span>
  </pre>
  </div>

  <p>
  Ok, but how do we get anything up there?
  </p>

  <p>
  Emacs ships bundled with one of the greatest pieces software ever written, <a href="https://orgmode.org">org-mode</a>. org-mode is a powerful writing tool that makes good on the potential for computers to offer something substantial over the greatest invention in all of human history - pen and paper. Publishing is integral part of writing, thus org-mode includes a generalized publishing mechanism, ox-publish.
  </p>

  <p>
  The only thing missing out of the box for my purposes is HTML generation for code snippets. This can be accomplished with <a href="https://github.com/emacsorphanage/htmlize">htmlize</a>, just 77K of elisp without any further dependencies as the gods intended.
  </p>

  <p>
  But how to use this stuff?
  </p>

  <p>
  Rather than forcing org-mode users to go searching for food on the web like animals, Emacs shares the UNIX philosophy that the system itself should provide high quality comprehensive documentation. For UNIX that's <b><b>man</b></b>, for Emacs, <b><b>M-x info</b></b>. The first few org-mode pages on publishing in Emacs offer a snippet which I modified that provides everything a user needs to immediately generate a static site:
  </p>

  <div class="org-src-container">
  <pre class="src src-elisp">(<span class="org-keyword">setq</span> org-publish-project-alist
  `((<span class="org-string">"posts"</span>
  <span class="org-builtin">:base-directory</span>       <span class="org-string">"posts"</span>
  <span class="org-builtin">:base-extension</span>       <span class="org-string">"org"</span>
  <span class="org-builtin">:publishing-function</span>  org-html-publish-to-html
  <span class="org-builtin">:publishing-directory</span> <span class="org-string">"_site/posts"</span>)))
  </pre>
  </div>

  <p>
  I can immediately focus on what I'm going to write rather being drawn to fiddling around with knobs that don't have much to do with writing. Later, I can figure out how to add custom CSS, site structure, a post index, entry customization, yet none of those concerns are placed before me at the beginning.
  </p>

  <p>
  As a user I'm given a seed and I can tend to it. As I make progress, I look up and become more curious how others have tended to their plots.
  </p>

  <p>
  Exploring these tools is like walking through a beautiful community garden.
  </p>

  <p>
  <a href="https://wiki.netbsd.org/tutorials/how_to_setup_a_webserver/">For more information about the httpd server built into NetBSD</a> and
  <a href="https://taingram.org/blog/org-mode-blog.html">this is a really good article on using Emacs to write a blog</a>.
  </p>
  ]]></description>
</item>
<item>
  <title>What is Convivial Computing?</title>
  <link>https://convivialcomputing.net/posts/2026-05-15-hello.html</link>
  <author>swannod@sdf.org (David Nolen)</author>
  <guid isPermaLink="false">https://convivialcomputing.net/posts/2026-05-15-hello.html</guid>
  <pubDate>Fri, 15 May 2026 00:00:00 -0400</pubDate>

  <description><![CDATA[<p>
  A couple of years ago I read <a href="https://en.wikipedia.org/wiki/Tools_for_Conviviality">Tools for Conviviality by Ivan Illich,</a> probably because <a href="https://en.wikipedia.org/wiki/Alan_Kay">Alan Kay</a> suggested it somewhere. The book is about many things, but a crude summarization might be that tools without limits often have precisely the opposite effect than originally intended. He gives many examples including institionalized medicine, transportation, and education. The book argues that each tool be designed with natural limits and that those limits should be drawn at the scale of human interaction and human communities. The book, as far as I can tell, didn't make much of a splash at the time as the ideas didn't fit neatly into pre-existing polarized buckets; capitalist or communist, both sacrificed at the alter of growth without limits. Now more than fifty years later, amidst climate catastrophes, fragile supply chains, continuous inflation, and general stagnation, perhaps we can better digest what he and others were trying to say.
  </p>

  <p>
  What happens if we apply this thinking to software engineering? 
  </p>

  <p>
  In the States at least, we continue to be enamoured with scale and metrics because <i>quality</i> is a convivial concept. A craftsperson, unlike a factory worker, has their local reputation to uphold. A craftperson will of course make practical judgements about labor, materials and time, one needs to put food on the table after all. But in the end, the work cannot only be about quantifiable things. We live on Earth and we are surrounded by Beauty far beyond the scope our abilities to recreate. We have an Ideal, why we restrict ourselves to useful but crude results?
  </p>

  <p>
  I am reminded of my recent travels in Japan and how it differed from my experience dining out in New York. Most Japanese restaurants have very few seats and often only one cook and one server. If the place is small enough, no server, the cook will serve you. Places fill up quickly. But neighborhood restaurants in Japan are like flowers. The next one will be different, but no less delightful, and you will never get to try them all. This convivial approach to running a business scales in a way that is difficult I think for a great many people in mainstream software to understand.
  </p>

  <p>
  Take for example that this blog runs on <a href="https://netbsd.org">NetBSD 9.2</a>, an operating system released 5 years ago. NetBSD can fit onto a single CD-ROM. <a href="https://blog.infected.systems/posts/2025-04-21-this-blog-is-hosted-on-a-nintendo-wii/">It can serve websites off a Nintento Wii which offers only 128mb of RAM</a>. By choosing to be runnable on a large number of "obsolete" systems, by setting limits, NetBSD in fact guarantees that it runs incredibly well on newer systems, just not the latest systems. If we collectively believed that computers should be good for 10 or 20 years this wouldn't be a problem and <a href="https://www.npr.org/sections/goats-and-soda/2024/10/05/g-s1-6411/electronics-public-health-waste-ghana-phones-computers">we wouldn't filling ewaste landfills in Africa at volumes that make most post-apocalyptic films watch like fairy tales</a>.
  </p>

  <p>
  This not a claim that NetBSD is the answer. The point is that convivial computing focuses on systems that don't require generative assistance because the system is too damn simple - your favorite search engine and a couple of simple prompts in your favorite chatbot will take you the whole way. After that hopefully you write a blog post documenting your exploration so that the next person can save us all some fresh water. And then maybe we get a community and that garden starts to flourish.
  </p>

  <p>
  Speaking of Software Sausage Factories, convivial computing is not <a href="https://resonantcomputing.org">Resonant Computing</a>. The idea that corporate generative chatbots would allow individuals or communities to achieve more agency is like believing nuclear warheads are the right answer to a minor case of ant infestation or the demolition of a small abandoned house. Furthermore, in an ideal world <a href="https://bookwyrm.social/book/41425/s/how-children-fail">where children didn't fail</a>, one could imagine the vast majority of people using such tools responsibly but <a href="https://www.nytimes.com/2025/08/26/opinion/culture/ai-chatgpt-college-cheating-medieval.html?unlocked_article_code=1.ilA.ckaq.Ia98gIzqXclm&amp;smid=url-share">don't hold your breath</a>. Adding more technology has never been and never will be an answer to what truly ails us. 
  </p>

  <p>
  Convivial computing is an intentionally vague concept. There are far more technologies that align with the values described above then I will ever have time to explore, and there are communities with similar values that inspired me to more actively write about technology again.
  </p>

  <p>
  Expect contradictions, expect mistakes.
  </p>

  <p>
  Hello.
  </p>
  ]]></description>
</item>
</channel>
</rss>
