<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>bret.io</title>
  <id>https://bret.io/feed.xml</id>
  <updated>2024-02-13T18:24:49.707Z</updated>
  <link rel="self" type="application/atom+xml" href="https://bret.io/feed.xml"/>
  <link rel="alternate" type="application/json" href="https://bret.io/feed.json"/>
  <link rel="alternate" type="text/html" href="https://bret.io"/>
  <author>
    <name>Bret Comnes</name>
    <uri>https://bret.io</uri>
  </author>
  <generator uri="https://github.com/bcomnes/jsonfeed-to-atom#readme" version="1.2.4">jsonfeed-to-atom</generator>
  <rights>© 2024 Bret Comnes</rights>
  <subtitle>A running log of announcements, projects and accomplishments.</subtitle>
  <entry>
    <id>https://bret.io/blog/2024/the-art-of-doing-science-and-engineering/#2024-02-13T18:24:49.707Z</id>
    <title>The Art of Doing Science and Engineering</title>
    <updated>2024-02-13T18:24:49.707Z</updated>
    <published>2024-02-13T18:24:49.707Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<figure>
  <a href="./img/cover.jpeg">
    <img loading="auto" src="./img/cover.jpeg" alt="Book Cover">
  </a>
  <figcaption>The art of doing science and engineering : Learning to Learn by Richard W. Hamming</figcaption>
</figure>
<p>Richard Hamming’s “The Art of Doing Science and Engineering” is a book capturing the lessons he taught in a course he gave at the U.S. Navy Postgraduate School in Monterey, CA.
He characterizes what he was trying to teach was “style” of thinking in science and engineering.</p>
<p>Having a physics degree myself, and also finding myself periodically ruminating on the agony of professional software development and hoping to find some overlap between my professional field and Hamming’s life experience, I gave it a read.</p>
<p>The book is filled with nuggets of wisdom and illustrates a career of move-the-needle science and engineering at Bell Labs.
I didn’t personally find much value in many of the algebraic walk-through’s of various topics like information theory, but learning about how Hamming discovered error correcting codes definitely was interesting and worth a read.</p>
<p>The highlight of book comes in the second half where he includes interesting stories, analogies and observations on nearly every page. Below are my highlights I pulled while reading.</p>
<h2 id="on-what-makes-good-design" tabindex="-1">On What Makes Good Design</h2>
<blockquote>
<p>That brings up another point, which is now well recognized in software for computers but which applies to hardware too. Things change so fast that part of the system design problem is that the system will be constantly upgraded in ways you do not now know in any detail! Flexibility must be part of the modern design of things and processes. Flexibility built into the design means not only will you be better able to handle the changes which will come after installation, but it also contributes to your own work as the small changes which inevitably arise both in the later stages of design and in the field installation of the system…</p>
<p>Thus rule two:</p>
<p><strong>Part of systems engineering design is to prepare for changes so they can be gracefully made and still not degrade the other parts.</strong></p>
<p>– p.367</p>
</blockquote>
<p>This quote is my favorite out of the entire book.
It feels like a constant fight in software engineering between the impulse to lock down runtime versions, specific dependency versions, and other environmental factors versus developing software in such a way that accommodates wide variance in all of these different component factors.
Both approaches argue reliability and flexibility, however which approach actually tests for it?</p>
<p>In my experience, the tighter the runtime dependency specifications, the faster fragility spreads, and it’s satisfying to hear Hamming’s experience echo this observation. Sadly though, his observation that those writing software will universally understand this simply hasn’t held up.</p>
<blockquote>
<p>Good design protects you from the need for too many highly accurate components in the system. But such design principals are still, to this date, ill understood and need to be researched extensively. Not that good designers do not understand this intuitively, merely it is not easily incorporated into the design methods you were thought in school.</p>
<p>Good minds are still need in spite of all the computing tools we have developed. The best mind will be the one who gets the principle into the design methods taught so it will be automatically available for lesser minds!.</p>
<p>– p.268</p>
</blockquote>
<p>Here Hamming is describing H.S. Black’s feedback circuit’s tolerance for low accuracy components as what constitutes good design. I agree! Technology that works at any scale, made out of commodity parts with minimal runtime requirements tends to be what is most useful across the longest amount of time.</p>
<h2 id="on-committees" tabindex="-1">On Committees</h2>
<blockquote>
<p>Committee decisions, which tend to diffuse responsibility, are seldom the best in practice—most of the time they represent a compromise which has none of the virtues of any path and tends to end in mediocrity.</p>
<p>– p.274</p>
</blockquote>
<p>I appreciated his observations on committees, and their tendency to launder responsibility.
They serve a purpose, but its important to understand their nature.</p>
<h2 id="on-data-and-observation" tabindex="-1">On Data and Observation</h2>
<blockquote>
<p>The Hawthorne effect strongly suggests the proper teaching method will always to be in a state of experimental change, and it hardly matters just what is done; all that matters is both the professor and the students believe in the change.</p>
<p>– p.288</p>
</blockquote>
<blockquote>
<p>It has been my experience, as well as the experience of many others who have looked, that data is generally much less accurate than it is advertised to be. This is not a trivial point—we depend on initial data for many decisions, as well as for the input data for simulations which result in decisions.</p>
<p>– p.345</p>
</blockquote>
<blockquote>
<p>Averages are meaningful for homogeneous groups (homogeneous with respect to the actions that may later be taken), but for diverse groups averages are often meaningless. As earlier remarked, the average adult has one breast and one testicle, but that does not represent the average person in our society.</p>
<p>– p.356</p>
</blockquote>
<blockquote>
<p>You may think the title means that if you measure accurately you will get an accurate measurement, and if not then not, but it refers to a much more subtle thing—the way you choose to measure things controls to a large extent what happens. I repeat the story Eddington told about the fishermen who went fishing with a net. They examined the size of the fish they caught and concluded there was a minimum size to the fish in the sea. The instrument you use clearly affects what you see.</p>
<p>– p.373</p>
</blockquote>
<p>Intuitively I think many people who attempt to measure anything understand that their approach reflects in the results to some degree.
I hadn’t heard of the <a href="https://en.wikipedia.org/wiki/Hawthorne_effect">Hawthorne effect</a> before, but intuitively it makes sense.</p>
<p>People with an idea on how to improve something implement their idea and it works, because they want it to work and allow the effects to be fully effective.
Someone else is prescribed this idea or brought into the fold where the idea is implemented and the benefits of the idea evaporate.</p>
<p>I’ve long suspected that in the context of professional software development, where highly unscrutinized benchmarks and soft data are the norm, people start with an opinion or theory and work back to data that supports it.
Could it just be that people need to believe that working in a certain way is necessary for them to work optimally? Could it be “data” is often just a work function used to out maneuver competing ideas?</p>
<p>Anyway, just another thing to factor for when data is plopped in your lap.</p>
<h2 id="on-theory" tabindex="-1">On Theory</h2>
<blockquote>
<p>Moral: there need not be a unique form of a theory to account for a body of observations; instead, two rather different-looking theories can agree on all the predicted details. You cannot go from a body of data to a unique theory! I noted this in the last chapter.</p>
<p>–p.314</p>
</blockquote>
<blockquote>
<p>Heisenberg derived the uncertainty principle that conjugate variables, meaning Fourier transforms, obeyed a condition in which the product of the uncertainties of the two had to exceed a fixed number, involving Planck’s constant. I earlier commented, Chapter 17, this is a theorem in Fourier transforms-any linear theory must have a corresponding uncertainty principle, but among physicists it is still widely regarded as a physical effect from nature rather than a mathematical effect of the model.</p>
<p>–p.316</p>
</blockquote>
<p>I appreciate Hamming suggesting that some of our understanding of physical reality could be a byproduct of the model being used to describe it.
It’s not exactly examined closely in undergraduate or graduate quantum mechanics, and I find it interesting Hamming, who’s clearly highly intuitive with modeling, also raises this question.</p>
<h2 id="predictions" tabindex="-1">Predictions</h2>
<blockquote>
<p>Let me now turn to predictions of the immediate future. It is fairly clear that in time “drop lines” from the street to the house (they may actually be buried, but will probably still be called “drop lines”) will be fiber optics. Once a fiber-optic wire is installed, then potentially you have available almost all the information you could possibly want, including TV and radio, and possibly newspaper articles selected according to your interest profile (you pay the printing bill which occurs in your own house). There would be no need for separate information channels most of the time. At your end of the fiber there are one or more digital filters. Which channel you want, the phone, radio, or TV, can be selected by you much as you do now, and the channel is determined by the numbers put into the digital filter-thus the same filter can be multipurpose, if you wish. You will need one filter for each channel you wish to use at the same time (though it is possible a single time-sharing filter would be available) and each filter would be of the same standard design. Alternately, the filters may come with the particular equipment you buy.</p>
<p>– p.284-285</p>
</blockquote>
<p>Here Hamming is predicting the internet. He got very close, and it’s interesting to think that these signals would all just be piped to your house in a bundle you you pay for a filter to unlock access to the ones you want. Hey Cable TV worked that for a long time!</p>
<h2 id="on-leadership" tabindex="-1">On Leadership</h2>
<blockquote>
<p>But a lot of evidence on what enabled people to make big contributions points to the conclusion that a famous prof was a terrible lecturer and the students had to work hard to learn it for themselves! I again suggest a rule:</p>
<p><strong>What you learn from others you can use to follow;</strong></p>
<p><strong>What you learn for yourself you can use to lead.</strong></p>
<p>– p.292</p>
</blockquote>
<p>Learn by doing, not by following.</p>
<blockquote>
<p><strong>What you did to become successful is likely to be counterproductive when applied at a later date.</strong></p>
<p>– p.342</p>
</blockquote>
<p>It’s easy to blame changing trends in software development for the disgustingly short half-life of knowledge regarding development patterns and tools, but I think it’s probably just the nature of knowledge based work.
Operating by yourself may be effective and work well, but its not a recipe for success at any given moment in time.</p>
<blockquote>
<p>A man was examining the construction of a cathedral. He asked a stonemason what he was doing chipping the stones, and the mason replied, “I am making stones.” He asked a stone carver what he was doing; “I am carving a gargoyle.” And so it went; each person said in detail what they were doing. Finally he came to an old woman who was sweeping the ground. She said, “I am helping build a cathedral.”
If, on the average campus, you asked a sample of professors what they were going to do in the next class hour, you would hear they were going to “teach partial fractions,” “show how to find the moments of a normal distribution,” “explain Young’s modulus and how to measure it,” etc. I doubt you would often hear a professor say, “I am going to educate the students and prepare them for their future careers.”
This myopic view is the chief characteristic of a bureaucrat. To rise to the top you should have the larger view—at least when you get there.</p>
<p>– p.360</p>
</blockquote>
<p>Software bureaucrats aplenty. Really easy to fall into this role.</p>
<blockquote>
<p>I must come to the topic of “selling” new ideas. You must master three things to do this (Chapter 5):</p>
<ol>
<li>Giving formal presentations,</li>
<li>Producing written reports, and</li>
<li>Mastering the art of informal presentations as they happen to occur.</li>
</ol>
<p>All three are essential—you must learn to sell your ideas, not by propaganda, but by force of clear presentation. I am sorry to have to point this out; many scientists and others think good ideas will win out automatically and need not be carefully presented. They are wrong;</p>
<p>– p.396</p>
</blockquote>
<p>One thing I regret over the last 10 years of my career is not writing down more insights I have learned through experience.
Ideas simply don’t transmit if they aren’t written down or put into some consumable format like video or audio.
Nearly every annoying tool or developer trend you are forced to use is in play because it communicated the idea through blogs, videos and conference talks.
And those who watched echoed these messages.</p>
<h2 id="on-experts" tabindex="-1">On Experts</h2>
<blockquote>
<p><strong>An expert is one who knows everything about nothing; a generalist knows nothing about everything.</strong></p>
<p>In an argument between a specialist and a generalist, the expert usually wins by simply (1) using unintelligible jargon, and (2) citing their specialist results, which are often completely irrelevant to the discussion. The expert is, therefore, a potent factor to be reckoned with in our society. Since experts both are necessary and also at times do great harm in blocking significant progress, they need to be examined closely. All too often the expert misunderstands the problem at hand, but the generalist cannot carry though their side to completion. The person who thinks they understand the problem and does not is usually more of a curse (blockage) than the person who knows they do not understand the problem.</p>
<p>– p.333</p>
</blockquote>
<p>Understand when you are generalist and a specialist.</p>
<blockquote>
<p>Experts, in looking at something new, always bring their expertise with them, as well as their particular way of looking at things. Whatever does not fit into their frame of reference is dismissed, not seen, or forced to fit into their beliefs. Thus really new ideas seldom arise from the experts in the field. You cannot blame them too much, since it is more economical to try the old, successful ways before trying to find new ways of looking and thinking.</p>
<p><strong>If an expert says something can be done he is probably correct, but if he says it is impossible then consider getting another opinion.</strong></p>
<p>– p.336</p>
</blockquote>
<p>Anyone wading into a technical field will encounter experts at every turn.
They have valuable information, but they are also going to give you dated, myopic advice (gatekeeping?).
I like Hamming’s framing here and it reflects my experience when weighing expert opinion.</p>
<blockquote>
<p>In some respects the expert is the curse of our society, with their assurance they know everything, and without the decent humility to consider they might be wrong. Where the question looms so important, I suggested to you long ago to use in an argument, “What would you accept as evidence you are wrong?” Ask yourself regularly, “Why do I believe whatever I do?” Especially in the areas where you are so sure you know, the area of the paradigms of your field.</p>
<p>– p.340</p>
</blockquote>
<p>I love this exercise. It will also drive you crazy. Tread carefully.</p>
<blockquote>
<p>Systems engineering is indeed a fascinating profession, but one which is hard to practice. There is a great need for real systems engineers, as well as perhaps a greater need to get rid of those who merely talk a good story but cannot play the game effectively.</p>
<p>– p.372</p>
</blockquote>
<p>Controversial, harsh, but true.</p>
<h2 id="the-binding" tabindex="-1">The Binding</h2>
<p>The last thing I want to recognize is the beautiful cloth resin binding and quality printing of the book. Bravo Stripe Press for still producing beautiful artifacts at affordable pricing in the age of print on demand.</p>
]]></content>
    <link rel="alternate" href="https://bret.io/blog/2024/the-art-of-doing-science-and-engineering/"/>
  </entry>
  <entry>
    <id>https://bret.io/blog/2024/async-neocities-bin/#2024-01-15T23:55:33.582Z</id>
    <title>async-neocities has a bin</title>
    <updated>2024-01-15T23:55:33.582Z</updated>
    <published>2024-01-15T23:55:33.582Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p><a href="https://github.com/bcomnes/async-neocities"><code>async-neocities</code></a> <a href="https://github.com/bcomnes/async-neocities/releases/tag/v3.0.0">v3.0.0</a> is now available and introduces a CLI.</p>
<pre><code class="hljs language-console">Usage: async-neocities [options]

    Example: async-neocities --src public

    --help, -h            print help text
    --src, -s             The directory to deploy to neocities (default: &quot;public&quot;)
    --cleanup, -c         Destructively clean up orphaned files on neocities
    --protect, -p         String to minimatch files which will never be cleaned up
    --status              Print auth status of current working directory
    --print-key           Print api-key status of current working directory
    --clear-key           Remove the currently associated API key
    --force-auth          Force re-authorization of current working directory

async-neocities (v3.0.0)
</code></pre>
<p>When you run it, you will see something similar to this:</p>
<pre><code class="hljs language-console"><span class="hljs-meta prompt_">&gt; </span><span class="language-bash">async-neocities --src public</span>

Found siteName in config: bret
API Key found for bret
Starting inspecting stage...
Finished inspecting stage.
Starting diffing stage...
Finished diffing stage.
Skipping applying stage.
Deployed to Neocities in 743ms:
    Uploaded 0 files
    Orphaned 0 files
    Skipped 244 files
    0 protected files
</code></pre>
<p><a href="https://github.com/bcomnes/async-neocities"><code>async-neocities</code></a> was previously available as a GitHub Action called <a href="https://github.com/marketplace/actions/deploy-to-neocities">deploy-to-neocities</a>. This Action API remains available, however the CLI offers a local-first workflow that was not previously offered.</p>
<h2 id="local-first-deploys" tabindex="-1">Local First Deploys</h2>
<p>Now that <code>async-neocities</code> is available as a CLI, you can easily configure it as an <code>npm</code> script and run it locally when you want to push changes to <a href="https://neocities.org">neocities</a> without relying on GitHub Actions.
It also works great in Actions with side benefit of deploys working exactly the same way in both local and remote environments.</p>
<p>Here is a quick example of that:</p>
<ul>
<li>Install <code>async-neocities@^3.0.0</code> to your project’s <code>package.json</code>.</li>
<li>Set up a <code>package.json</code> deploy script:<pre><code class="hljs language-json"> <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;build&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run clean &amp;&amp; run-p build:*&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;build:tb&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;top-bun&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;clean&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;rm -rf public &amp;&amp; mkdir -p public&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;deploy&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s build deploy:*&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;deploy:async-neocities&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;async-neocities --src public --cleanup&quot;</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
</code></pre>
</li>
<li>Run a deploy once locally to set up the <code>deploy-to-neocities.json</code> config file. Example config contents:<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span><span class="hljs-attr">&quot;siteName&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;bret&quot;</span><span class="hljs-punctuation">}</span>
</code></pre>
</li>
<li>Run deploys locally with <code>npm run deploy</code>.</li>
<li>Configure your CI to run <code>npm run deploy</code> and configure the token secret.<pre><code class="hljs language-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">neociteis</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">node-version:</span> <span class="hljs-number">21</span>
  <span class="hljs-attr">FORCE_COLOR:</span> <span class="hljs-number">2</span>

<span class="hljs-attr">concurrency:</span> <span class="hljs-comment"># prevent concurrent deploys doing starnge things</span>
  <span class="hljs-attr">group:</span> <span class="hljs-string">deploy-to-neocities</span>
  <span class="hljs-attr">cancel-in-progress:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">LFS</span> <span class="hljs-string">file</span> <span class="hljs-string">list</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">git</span> <span class="hljs-string">lfs</span> <span class="hljs-string">ls-files</span> <span class="hljs-string">-l</span> <span class="hljs-string">|</span> <span class="hljs-string">cut</span> <span class="hljs-string">-d&#x27;</span> <span class="hljs-string">&#x27; -f1 | sort &gt; .lfs-assets-id
    - name: Restore LFS cache
      uses: actions/cache@v3
      id: lfs-cache
      with:
        path: .git/lfs
        key: $<span class="hljs-template-variable">{{ runner.os }}</span>-lfs-$<span class="hljs-template-variable">{{ hashFiles(&#x27;.lfs-assets-id&#x27;) }}</span>-v1
    - name: Git LFS Pull
      run: git lfs pull

    - name: Use Node.js
      uses: actions/setup-node@v4
      with:
        node-version: $<span class="hljs-template-variable">{{env.node-version}}</span>
    - run: npm i
    - run: npm run deploy
      env:
        NEOCITIES_API_TOKEN: $<span class="hljs-template-variable">{{ secrets.NEOCITIES_API_TOKEN }}</span>
</span></code></pre>
</li>
</ul>
<p>The <code>async-neocities</code> CLI re-uses the same ENV name as <code>deploy-to-neocities</code> action so migrating to the CLI requires no additional changes to the Actions environment secrets.</p>
<h2 id="clis-vs-actions" tabindex="-1">CLIs vs Actions</h2>
<p>This prompts some questions regarding when are CLIs and when are actions most appropriate. Lets compare the two:</p>
<h3 id="clis" tabindex="-1">CLIs</h3>
<ul>
<li>Pro: Local deploys</li>
<li>Pro: Easily re-usable in CI as well</li>
<li>Con: Requires Node.js, but this is not a problem when already using Node.js</li>
</ul>
<h2 id="actions" tabindex="-1">Actions</h2>
<ul>
<li>Pro: Works great with Non-node ecosystems without requiring a <code>package.json</code> or <code>node_modules</code></li>
<li>Con: Only runs in CI environments</li>
</ul>
<h2 id="conclusion" tabindex="-1">Conclusion</h2>
<p>In addition to the CLI, <code>async-neocities</code> migrates to full Node.js <code>esm</code> and internally enables <code>ts-in-js</code> though the types were far to dynamic to export full type support with the time I had available.</p>
<p>With respect to an implementation plan going forward regarding CLIs vs actions, I’ve summarized my thoughts below:</p>
<p>Implement core functionality as a re-usable library.
Exposing a CLI makes that library an interactive tool that provides a local first workflow and is equally useful in CI.
Exposing the library in an action further opens up the library to a wider language ecosystem which would otherwise ignore the library due to foreign ecosystem ergonomic overhead.
The action is simpler to implement than a CLI but the CLI offers a superior experience within the implemented language ecosystem.</p>
]]></content>
    <link rel="alternate" href="https://bret.io/blog/2024/async-neocities-bin/"/>
  </entry>
  <entry>
    <id>https://bret.io/blog/2024/open-for-work/#2024-01-04T19:15:53.198Z</id>
    <title>Open for Work 2024</title>
    <updated>2024-01-04T19:15:53.198Z</updated>
    <published>2024-01-04T19:15:53.198Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p>Happy New Year!
My contract at Socket Supply Co is wrapping up after a productive 6 months of contributions and I am starting a rare open call for work.</p>
<p>Please check out <a href="/cv/">my resume</a> to see what I’ve been up to and enjoy some highlights below.
If you think any of my qualities line up with your hiring needs, <a href="mailto:bcomnes+ofw@fastmail.com">please let me know</a>!</p>
<p>(Spare a click? Please boost! <a href="https://twitter.com/bcomnes/status/1743027108044300528">x.com</a>, <a href="https://bsky.app/profile/bret.io/post/3ki6rjh3mns2m">bsky</a>, <a href="https://fosstodon.org/@bcomnes/111699965788623357">mastodon</a>, <a href="https://www.linkedin.com/feed/update/urn:li:activity:7148799710118502400/">linkedin</a>)</p>
<figure>
  <a href="/cv/">
    <picture>
      <!-- <source srcset="./img/resume-2024-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/resume-2024-dark.png" alt="Image of resume in 2024">
    </picture>
  </a>
</figure>
<p>Birds eye view: I have a variety of experience building software/SAAS infrastructure from the ground up and contributing to large existing codebase and orgs. I generate between 1500 and 3000+ contributions a year, some of which are captured for public scrutiny.</p>
<p>I work primarily in <a href="https://developer.mozilla.org/en-US/docs/Web/javascript">Javascript</a>, and now <a href="https://www.typescriptlang.org">Typescript</a> for frontend and backend, but I also really love working in <a href="https://go.dev">Go</a>.</p>
<figure>
  <a href="https://github.com/bcomnes/">
    <picture>
      <source srcset="./img/contributions-dark.png" media="(prefers-color-scheme: dark)">
      <img loading="auto" src="./img/contributions.png" alt="Image of GitHub contributions">
    </picture>
  </a>
  <figcaption>A diagram of my publicly visible GitHub contributions for all time.</figcaption>
  </figure>
</figure>
<p>In addition to my professional work, I work on open source tools and products while my kids are napping on the weekend. My latest project is 🥖 <a href="https://breadcrum.net">breadcrum.net</a> which lets you extract articles and media from around the web so that you can read/listen/watch them in your preferred podcast app and reader device. It’s free right now too! You should check it out.</p>
<figure>
  <a href="/">
    <picture>
      <source srcset="./img/personal-dark.png" media="(prefers-color-scheme: dark)">
      <img loading="auto" src="./img/personal.png" alt="Image of personal projects">
    </picture>
  </a>
</figure>
<p>I enjoy working within the GitHub open source model, and have publisher status on over <a href="https://www.npmjs.com/~bret">300 npm packages</a>, some of which I even originated. There is a decent chance I could push code changes into your web projects!</p>
<figure>
  <a href="https://www.npmjs.com/~bret">
    <picture>
      <img loading="auto" src="./img/npm.png" alt="Image of npm packages">
    </picture>
  </a>
  </figure>
</figure>
<p>One of my most recent package adoptions, <a href="https://github.com/bcomnes/npm-run-all2">npm-run-all2</a>, grew from 1000 to over 11,000 dependents over the last few months. Incredible growth that hasn’t slowed down. I am pleased to contribute back a small portion of the work that I benefit from when building with the open source ecosystem.</p>
<figure>
  <a href="https://github.com/bcomnes/npm-run-all2/network/dependents">
    <picture>
      <source srcset="./img/npm-run-all-dark.png" media="(prefers-color-scheme: dark)">
      <img loading="auto" src="./img/npm-run-all.png" alt="Image of npm-run-all2 dependents">
    </picture>
  </a>
  </figure>
</figure>
<p>I will be reaching out to a bunch of you individually, but in the meantime if you are seeing this, please reach out! I would love to reconnect to personally catch up and also chat about ideas for work.</p>
]]></content>
    <link rel="alternate" href="https://bret.io/blog/2024/open-for-work/"/>
  </entry>
  <entry>
    <id>https://bret.io/blog/2023/reorganized/#2023-12-02T20:49:41.713Z</id>
    <title>Reorganized</title>
    <updated>2023-12-02T20:49:41.713Z</updated>
    <published>2023-12-02T20:49:41.713Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p>Behold, a mildly redesigned and reorganized landing page:</p>
<p><img src="./img/screenshot.png" alt="screenshot of the new website"></p>
<p>It’s still not great, but it should make it easier to keep it up to date going forward.</p>
<p>It has 3 sections:</p>
<ul>
<li><a href="/#">Featured Projects</a>: important projects of note.</li>
<li><a href="/#recent-posts">Recent Posts</a>: now that this site has proper blog support, I can highlight recent posts on the landing page.</li>
<li><a href="/#open-source">Open Source</a>: interesting and notable projects that have found some use and that I still maintain. This section now includes a bunch of open source work from the past year that I’ve never had time to write about.</li>
<li><a href="/#past-projects">Past projects</a>: inactive projects that are not longer active, but still interesting enough to share.</li>
</ul>
<p>I removed a bunch of older inactive projects and links and stashed them in a <a href="/projects/previous-projects/">project</a>.</p>
<p>Additionally, the edit button in the page footer now takes you to the correct page in GitHub for editing, so if you ever see a typo, feel free to send in a fix!</p>
<p>Finally, the <a href="/about/">about</a> page includes a live dump of the dependencies that were used to build the website.</p>
]]></content>
    <link rel="alternate" href="https://bret.io/blog/2023/reorganized/"/>
  </entry>
  <entry>
    <id>https://bret.io/projects/previous-projects/#2023-12-02T18:07:13.956Z</id>
    <title>Previously Featured Projects</title>
    <updated>2023-12-02T18:07:13.956Z</updated>
    <published>2023-12-02T18:07:13.956Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p>I felt bad deleting old projects off my landing page, so made a page to stash them so I can reference them later. So, if you happen to be here, enjoy my ancient, old projects that I no longer intend to bug people with.</p>
<section class="portfolio grid-container">
  <div>
    <h2><a href="/projects/websockets/">Websockets</a></h2>
    <figure>
      <a href="/projects/websockets/"><img loading="auto" src="static/websockets.png" alt="Screenshot of Websockets writeup"></a>
      <figcaption>Experiments and abstractions with Websockets and a write up of the discoveries made regarding universal Node.js/DOM modules.</figcaption>
    </figure>
    <ul>
      <li class="lang js"><a href="https://github.com/bcomnes/universal-reconnecting-websocket">bcomnes/universal-reconnecting-websocket</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/websocket-chat">bcomnes/websocket-chat</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/websocket-chat-client">bcomnes/websocket-chat-client</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/dom-event-handler">bcomnes/dom-event-handler</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/node-event-handler">bcomnes/node-event-handler</a></li>
    </ul>
  </div>
  <div>
    <h2 id="css"><a href="https://css-pkg.github.io">css-pkg</a></h2>
    <figure>
      <a href="https://css-pkg.github.io"><img loading="auto" src="static/css-pkg.png" alt="Screenshot of css-pkg website"></a>
      <figcaption>An <a href="https://github.com/css-pkg/">org</a> supporting CSS as node packaged modules, ready for consumption directly out of <code>node_modules</code> and installed with <code>npm</code> or <code>yarn</code>.</figcaption>
    </figure>
    <ul>
      <li class="lang css"><a href="https://github.com/css-pkg/big-cursors.css">css-pkg/big-cursors.css</a></li>
      <li class="lang css"><a href="https://github.com/css-pkg/fraktur.css">css-pkg/fraktur.css</a></li>
      <li class="lang css"><a href="https://github.com/css-pkg/chicago.css">css-pkg/chicago.css</a></li>
      <li class="lang css"><a href="https://github.com/css-pkg/lunchtype.css">css-pkg/lunchtype.css</a></li>
      <li class="lang html"><a href="https://github.com/css-pkg/css-pkg.github.io">css-pkg/css-pkg.github.io</a></li>
    </ul>
  </div>
  <div>
    <h2 id="choo"><a href="https://www.choo.io">Choo</a></h2>
    <figure>
      <a href="https://www.choo.io"><img loading="auto" src="static/choo.png" alt="Choo website screenshot"></a>
      <figcaption>Maintainer and core contributor to the choo framework and ecosystem, a lightweight and decomposable alternative to frameworks like React.</figcaption>
    </figure>
    <ul>
      <li class="lang js"><a href="https://github.com/choojs/choo">choojs/choo</a></li>
      <li class="lang js"><a href="https://github.com/shama/on-load">shama/on-load</a></li>
      <li class="lang js"><a href="https://github.com/shama/bel">shama/bel</a></li>
    </ul>
  </div>
  <div>
    <h2 id="nanocomponent"><a href="https://github.com/choojs/nanocomponent">Nanocomponent</a></h2>
    <figure>
      <a href="https://github.com/choojs/nanocomponent"><img loading="auto" class="dark-icon" src="static/nanocomponent.jpg" alt="Screenshot of nanocomponent life cycle"></a>
      <figcaption>Primary author of Nanocomponent, a standalone component model that works well with choo. Shoutout <a href="https://github.com/lrlna">lrlna</a> for the algorithm illustration.</figcaption>
    </figure>
    <ul>
      <li class="lang js"><a href="https://github.com/choojs/nanocomponent">choojs/nanocomponent</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/youtube-component">bcomnes/youtube-component</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/twitter-component">bcomnes/twitter-component</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/nanocomponent-cache">bcomnes/nanocomponent-cache</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/class-cache">bcomnes/class-cache</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/nanomap">bcomnes/nanomap</a></li>
    </ul>
  </div>
  <div>
    <h2 id="datproject"><a href="https://datproject.org">Dat</a></h2>
    <figure>
      <a href="https://github.com/bcomnes/flattree"><img loading="auto" src="static/flattree.png" alt="Screenshot of flattree website"></a>
      <figcaption>Contributing to the <a href="https://github.com/random-access-storage">Dat module ecosystem</a> and provided early review of the <a href="https://github.com/dat-ecosystem-archive/docs/blob/5b37b1b8bd0615c1d487abfc4b1543dfdacbfd36/papers/dat-paper.md">hypercore protocol spec</a>.</figcaption>
    </figure>
    <ul>
      <li class="lang go"><a href="https://github.com/bcomnes/flattree">bcomnes/flattree</a></li>
      <li class="lang js"><a href="https://github.com/random-access-storage/random-access-http">random-access-storage/random-access-http</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/xor-stream">bcomnes/xor-stream</a></li>
      <li class="lang js"><a href="https://github.com/mafintosh/hyperirc">mafintosh/hyperirc</a></li>
    </ul>
  </div>
  <div>
    <h2 id="leveldb"><a href="https://leveljs.org">Leveldb</a></h2>
    <figure>
      <a href="https://github.com/hypermodules/level-auto-index/"><img loading="auto" src="static/leveldb.png" alt="Screenshot of level-auto-index"></a>
      <figcaption>level-auto-index creates and maintains supplementary <a href="https://leveljs.org">LevelDB</a> indexes automatically.</figcaption>
    </figure>
    <ul>
      <li class="lang js"><a href="https://github.com/bcomnes/level-idx">hypermodules/level-idx</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/level-auto-index">hypermodules/level-auto-index</a></li>
      <li class="lang js"><a href="https://github.com/hypermodules/level-hookdown">hypermodules/level-hookdown</a></li>
    </ul>
  </div>
  <div>
    <h2 id="electron"><a href="https://www.electronjs.org">Electron</a></h2>
    <figure>
      <a href="https://github.com/bcomnes/mooon"><img loading="auto" src="static/mooon.png" alt="Screenshot of Mooon.app"></a>
      <figcaption>Creating Electron apps, developer tools and contributing to the Electron prebuild ecosystem to help improve native modules in Electron.</figcaption>
    </figure>
    <ul>
      <li class="lang js"><a href="https://github.com/hypermodules/dti">hypermodules/dti</a></li>
      <li class="lang js"><a href="https://github.com/bcomnes/mooon">bcomnes/mooon</a></li>
      <li class="lang js"><a href="https://zhealthdocumentation.com/etch-suite/">zhealthdocumentation.com/etch-suite</a></li>
      <li class="lang js"><a href="https://github.com/prebuild">Home Prebuilders Association</a></li>
    </ul>
  </div>
  <div>
    <h2 id="2016-reading"><a href="https://gist.github.com/bcomnes/6d76c3ace861d4d0ed57f2cde2346ffe">Late 2016 Reading List</a></h2>
    <figure>
      <a href="https://gist.github.com/bcomnes/6d76c3ace861d4d0ed57f2cde2346ffe"><img loading="auto" src="static/2016readinglist.png" alt="Screenshot of 2016 reading list"></a>
      <figcaption>A list of things I was reading in late 2016.</figcaption>
    </figure>
  </div>
</section>
]]></content>
    <link rel="alternate" href="https://bret.io/projects/previous-projects/"/>
  </entry>
  <entry>
    <id>https://bret.io/blog/2023/a-couple-of-posts/#2023-11-30T00:44:10.337Z</id>
    <title>A Couple of Recent Posts</title>
    <updated>2023-11-30T00:44:10.337Z</updated>
    <published>2023-11-30T00:44:10.337Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p>I wrote a couple of new technical blog posts for <a href="https://socketsupply.co/">Socket Supply Co.</a>'s recent <a href="https://github.com/socketsupply/socket/releases/tag/v0.5.0">0.5</a> release.</p>
<ul>
<li><a href="https://socketsupply.co/blog/frontend-testing-with-socket/">Frontend Testing with Socket</a></li>
<li><a href="https://socketsupply.co/blog/improved-file-resolution/">Improved File Resolution</a></li>
</ul>
<p>If you read them, let me know what you think!</p>
]]></content>
    <link rel="alternate" href="https://bret.io/blog/2023/a-couple-of-posts/"/>
  </entry>
  <entry>
    <id>https://bret.io/blog/2023/reintroducing-top-bun/#2023-11-23T15:14:54.910Z</id>
    <title>Reintroducing top-bun 7 🥐</title>
    <updated>2023-11-23T15:14:54.910Z</updated>
    <published>2023-11-23T15:14:54.910Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p>After some unexpected weekends of downtime looking after sick toddlers, I’m happy to re-introduce <a href="https://github.com/bcomnes/top-bun"><code>top-bun</code> v7</a>.</p>
<p>Re-introduce? Well, you may remember <a href="https://github.com/bcomnes/top-bun/tree/v6.0.0"><code>@siteup/cli</code></a>, a spiritual offshoot of <a href="https://github.com/ungoldman/sitedown"><code>sitedown</code></a>, the static site generator that turned a directory of markdown into a website.</p>
<h2 id="tb-v7" tabindex="-1">Whats new with <code>top-bun</code> v7?</h2>
<p>Let’s dive into the new feature, changes and additions in <code>top-bun</code> 7.</p>
<h3 id="rename-to-top-bun" tabindex="-1">Rename to <code>top-bun</code></h3>
<p><code>@siteup/cli</code> is now <code>top-bun</code>.
As noted above, <code>@siteup/cli</code> was a name hack because I didn’t snag the bare <code>npm</code> name when it was available, and someone else had the genius idea of taking the same name. Hey it happens.</p>
<p>I described the project to Chat-GPT and it recommended the following gems:</p>
<ul>
<li><code>quick-brick</code></li>
<li><code>web-erect</code></li>
</ul>
<p>OK Chat-GPT, pretty good, I laughed, but I’m not naming this <code>web-erect</code>.</p>
<p>The kids have a recent obsession with <a href="https://wallaceandgromit.com">Wallace &amp; Gromit</a> and we watched a lot of episodes while she was sick. Also I’ve really been enjoying 🥖 <a href="https://breadcrum.net">bread themes</a> so I decided to name it after Wallace &amp; Gromit’s bakery “Top Bun” in their hit movie <a href="https://en.wikipedia.org/wiki/A_Matter_of_Loaf_and_Death">“A Matter of Loaf and Death”</a>.</p>
<div class="responsive-container"><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/zXBmZLmfQZ4?si=cGvWktfUU2xnqHrM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></div>
<h3 id="a-docs-website" tabindex="-1">A Docs Website</h3>
<p><code>top-bun</code> now builds it’s own repo into a docs website. It’s slightly better than the GitHub README.md view, so go check it out! It even has a real domain name so you know its for real.</p>
<ul>
<li>🌎 <a href="https://top-bun.org">top-bun.org</a></li>
</ul>
<figure>
  <a href="./img/docs-site.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/docs-site.png" alt="Screenshot of the docs site">
    </picture>
  </a>
  <figcaption>top-bun builds itself into its own docs website in an act dubbed "dogfooding".</figcaption>
</figure>
<h3 id="css-bundling-is-now-handled-by-esbuild" tabindex="-1"><code>css</code> bundling is now handled by <code>esbuild</code></h3>
<p><code>esbuild</code> is an amazing tool. <code>postcss</code> is a useful tool, but its slow and hard to keep up with. In <code>top-bun</code>, <code>css</code> bundling is now handled by <a href="https://esbuild.github.io/content-types/#css"><code>esbuild</code></a>.
<code>css</code> bundling is now faster and less fragile, and still supports many of the same transforms that <code>siteup</code> had before. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting">CSS nesting</a> is now supported in every modern browser so we don’t even need a transform for that. Some basic transforms and prefixes are auto-applied by setting a relatively modern browser target.</p>
<p><code>esbuild</code> doesn’t support import chunking on css yet though, so each <code>css</code> entrypoint becomes its own bundle. If <code>esbuild</code> ever gets this optimization, so will <code>top-bun</code>. In the meantime, <code>global.css</code>, <code>style.css</code> and now <code>layout.style.css</code> give you ample room to generally optimize your scoped css loading by hand. It’s simpler and has less moving parts!</p>
<figure>
  <a href="./img/esbuild-css.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/esbuild-css.png" alt="Screenshot of esbuild css docs">
    </picture>
  </a>
  <figcaption>The esbuild css docs are worth a cruise through.</figcaption>
</figure>
<h3 id="multi-layout-support" tabindex="-1">Multi-layout support</h3>
<p>You can now have more than one layout!
In prior releases, you could only have a single <code>root</code> layout that you customized on a per-page basis with variables.
Now you can have as many layouts as you need.
<strong>They can even nest</strong>.
Check out this example of a nested layout from this website. It’s named <code>article.layout.js</code> and imports the <code>root.layout.js</code>. It wraps the <code>children</code> and then passes the results to <code>root.layout.js</code>.</p>
<pre><code class="hljs language-js"><span class="hljs-comment">// article.layout.js</span>

<span class="hljs-keyword">import</span> { html } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;uhtml-isomorphic&#x27;</span>
<span class="hljs-keyword">import</span> { sep } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;node:path&#x27;</span>
<span class="hljs-keyword">import</span> { breadcrumb } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;../components/breadcrumb/index.js&#x27;</span>

<span class="hljs-keyword">import</span> defaultRootLayout <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./root.layout.js&#x27;</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">articleLayout</span> (args) {
  <span class="hljs-keyword">const</span> { children, ...rest } = args
  <span class="hljs-keyword">const</span> vars = args.<span class="hljs-property">vars</span>
  <span class="hljs-keyword">const</span> pathSegments = args.<span class="hljs-property">page</span>.<span class="hljs-property">path</span>.<span class="hljs-title function_">split</span>(sep)
  <span class="hljs-keyword">const</span> wrappedChildren = html`<span class="language-xml">
    </span><span class="hljs-subst">${breadcrumb({ pathSegments })}</span><span class="language-xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;article-layout h-entry&quot;</span> <span class="hljs-attr">itemscope</span> <span class="hljs-attr">itemtype</span>=<span class="hljs-string">&quot;http://schema.org/NewsArticle&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;article-header&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;p-name article-title&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;headline&quot;</span>&gt;</span></span><span class="hljs-subst">${vars.title}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;metadata&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">address</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;author-info&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;author&quot;</span> <span class="hljs-attr">itemscope</span> <span class="hljs-attr">itemtype</span>=<span class="hljs-string">&quot;http://schema.org/Person&quot;</span>&gt;</span>
            </span><span class="hljs-subst">${vars.authorImgUrl
              ? html`<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">height</span>=<span class="hljs-string">&quot;40&quot;</span> <span class="hljs-attr">width</span>=<span class="hljs-string">&quot;40&quot;</span>  <span class="hljs-attr">src</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.authorImgUrl}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.authorImgAlt}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;u-photo&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;image&quot;</span>&gt;</span>`</span>
              : <span class="hljs-literal">null</span>
            }</span><span class="language-xml">
            </span><span class="hljs-subst">${vars.authorName &amp;&amp; vars.authorUrl
              ? html`<span class="language-xml">
                  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.authorUrl}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;p-author h-card&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;url&quot;</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;name&quot;</span>&gt;</span></span><span class="hljs-subst">${vars.authorName}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>`</span>
              : <span class="hljs-literal">null</span>
            }</span><span class="language-xml">
          <span class="hljs-tag">&lt;/<span class="hljs-name">address</span>&gt;</span>
          </span><span class="hljs-subst">${vars.publishDate
            ? html`<span class="language-xml">
              <span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;published-date dt-published&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;datePublished&quot;</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.publishDate}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;#&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;u-url&quot;</span>&gt;</span>
                  </span><span class="hljs-subst">${(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(vars.publishDate)).toLocaleString()}</span><span class="language-xml">
                <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>`</span>
            : <span class="hljs-literal">null</span>
          }</span><span class="language-xml">
          </span><span class="hljs-subst">${vars.updatedDate
            ? html`<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;dt-updated&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;dateModified&quot;</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.updatedDate}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span>&gt;</span>Updated </span><span class="hljs-subst">${(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(vars.updatedDate)).toLocaleString()}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>`</span>
            : <span class="hljs-literal">null</span>
          }</span><span class="language-xml">
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;e-content&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;articleBody&quot;</span>&gt;</span>
        </span><span class="hljs-subst">${<span class="hljs-keyword">typeof</span> children === <span class="hljs-string">&#x27;string&#x27;</span>
          ? html([children])
          : children /* Support both uhtml and string children. Optional. */
        }</span><span class="language-xml">
      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

      <span class="hljs-comment">&lt;!--
        &lt;footer&gt;
            &lt;p&gt;Footer notes or related info here...&lt;/p&gt;
        &lt;/footer&gt;
      --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
    </span><span class="hljs-subst">${breadcrumb({ pathSegments })}</span><span class="language-xml">
  `</span>

  <span class="hljs-keyword">return</span> <span class="hljs-title function_">defaultRootLayout</span>({ <span class="hljs-attr">children</span>: wrappedChildren, ...rest })
}
</code></pre>
<h3 id="layout-styles-and-js-bundles" tabindex="-1">Layout styles and js bundles</h3>
<p>With multi-layout support, it made sense to introduce two more style and js bundle types:</p>
<ul>
<li>Layout styles: styles that load on every page that uses a layout</li>
<li>Layout bundles: js bundles that load on every page that uses a layout</li>
</ul>
<p>Prior the <code>global.css</code> and <code>global.client.js</code> bundles served this need.</p>
<figure>
  <a href="./img/layout-bundles.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/layout-bundles.png" alt="Screenshot of layout bundles in an editor">
    </picture>
  </a>
  <figcaption>Layouts introduce a new asset scope in the form of layout clients and style.</figcaption>
</figure>
<h3 id="layouts-and-global-assets-live-anywhere" tabindex="-1">Layouts and Global Assets live anywhere</h3>
<p>Layouts, and <code>global.client.js</code>, etc used to have to live at the root of the project <code>src</code> directory. This made it simple to find them when building, and eliminated duplicate singleton errors, but the root of websites is already crowded. It was easy enough to find these things anywhere, so now you can organize these special files in any way you like. I’ve been using:</p>
<ul>
<li><code>layouts</code>: A folder full of layouts</li>
<li><code>globals</code>: A folder full of the globally scoped files</li>
</ul>
<figure>
  <a href="./img/anywhere.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/anywhere.png" alt="Screenshot of globals and layouts living anywhere in an editor">
    </picture>
  </a>
  <figcaption>You are free to organize globals and layouts wherever you want now. The globals and layouts folders are great choice!</figcaption>
</figure>
<h3 id="template-files" tabindex="-1">Template files</h3>
<p>Given the <code>top-bun</code> variable cascade system, and not all website files are html, it made sense to include a templating system for generating any kind of file from the <code>global.vars.js</code> variable set. This lets you generate random website “sidefiles” from your site variables.</p>
<p>It works great for generating RSS feeds for websites built with <code>top-bun</code>. Here is the template file that generates the RSS feed for this website:</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">import</span> pMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;p-map&#x27;</span>
<span class="hljs-keyword">import</span> jsonfeedToAtom <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;jsonfeed-to-atom&#x27;</span>

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@template</span> <span class="hljs-variable">T</span>
 * <span class="hljs-doctag">@typedef</span> {<span class="hljs-type">import(&#x27;@siteup/cli&#x27;).TemplateAsyncIterator&lt;T&gt;</span>} <span class="hljs-variable">TemplateAsyncIterator</span>
 */</span>

<span class="hljs-comment">/** <span class="hljs-doctag">@type</span> {<span class="hljs-type">TemplateAsyncIterator&lt;{
 *  siteName: string,
 *  siteDescription: string,
 *  siteUrl: string,
 *  authorName: string,
 *  authorUrl: string,
 *  authorImgUrl: string
 *  layout: string,
 *  publishDate: string
 *  title: string
 * </span>}&gt;} */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> * feedsTemplate ({
  <span class="hljs-attr">vars</span>: {
    siteName,
    siteDescription,
    siteUrl,
    authorName,
    authorUrl,
    authorImgUrl
  },
  pages
}) {
  <span class="hljs-keyword">const</span> blogPosts = pages
    .<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">page</span> =&gt;</span> [<span class="hljs-string">&#x27;article&#x27;</span>, <span class="hljs-string">&#x27;book-review&#x27;</span>].<span class="hljs-title function_">includes</span>(page.<span class="hljs-property">vars</span>.<span class="hljs-property">layout</span>) &amp;&amp; page.<span class="hljs-property">vars</span>.<span class="hljs-property">published</span> !== <span class="hljs-literal">false</span>)
    .<span class="hljs-title function_">sort</span>(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(b.<span class="hljs-property">vars</span>.<span class="hljs-property">publishDate</span>) - <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(a.<span class="hljs-property">vars</span>.<span class="hljs-property">publishDate</span>))
    .<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-number">10</span>)

  <span class="hljs-keyword">const</span> jsonFeed = {
    <span class="hljs-attr">version</span>: <span class="hljs-string">&#x27;https://jsonfeed.org/version/1&#x27;</span>,
    <span class="hljs-attr">title</span>: siteName,
    <span class="hljs-attr">home_page_url</span>: siteUrl,
    <span class="hljs-attr">feed_url</span>: <span class="hljs-string">`<span class="hljs-subst">${siteUrl}</span>/feed.json`</span>,
    <span class="hljs-attr">description</span>: siteDescription,
    <span class="hljs-attr">author</span>: {
      <span class="hljs-attr">name</span>: authorName,
      <span class="hljs-attr">url</span>: authorUrl,
      <span class="hljs-attr">avatar</span>: authorImgUrl
    },
    <span class="hljs-attr">items</span>: <span class="hljs-keyword">await</span> <span class="hljs-title function_">pMap</span>(blogPosts, <span class="hljs-keyword">async</span> (page) =&gt; {
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">date_published</span>: page.<span class="hljs-property">vars</span>.<span class="hljs-property">publishDate</span>,
        <span class="hljs-attr">title</span>: page.<span class="hljs-property">vars</span>.<span class="hljs-property">title</span>,
        <span class="hljs-attr">url</span>: <span class="hljs-string">`<span class="hljs-subst">${siteUrl}</span>/<span class="hljs-subst">${page.pageInfo.path}</span>/`</span>,
        <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${siteUrl}</span>/<span class="hljs-subst">${page.pageInfo.path}</span>/#<span class="hljs-subst">${page.vars.publishDate}</span>`</span>,
        <span class="hljs-attr">content_html</span>: <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">renderInnerPage</span>({ pages })
      }
    }, { <span class="hljs-attr">concurrency</span>: <span class="hljs-number">4</span> })
  }

  <span class="hljs-keyword">yield</span> {
    <span class="hljs-attr">content</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(jsonFeed, <span class="hljs-literal">null</span>, <span class="hljs-string">&#x27;  &#x27;</span>),
    <span class="hljs-attr">outputName</span>: <span class="hljs-string">&#x27;feed.json&#x27;</span>
  }

  <span class="hljs-keyword">const</span> atom = <span class="hljs-title function_">jsonfeedToAtom</span>(jsonFeed)

  <span class="hljs-keyword">yield</span> {
    <span class="hljs-attr">content</span>: atom,
    <span class="hljs-attr">outputName</span>: <span class="hljs-string">&#x27;feed.xml&#x27;</span>
  }

  <span class="hljs-keyword">yield</span> {
    <span class="hljs-attr">content</span>: atom,
    <span class="hljs-attr">outputName</span>: <span class="hljs-string">&#x27;atom.xml&#x27;</span>
  }
}
</code></pre>
<h3 id="page-introspection" tabindex="-1">Page Introspection</h3>
<p>Pages, Layouts and Templates can now introspect every other page in the <code>top-bun</code> build.</p>
<p>You can now easily implement any of the following:</p>
<ul>
<li>RSS feeds</li>
<li>Auto-populating index feeds</li>
<li>Tag systems</li>
</ul>
<p>Pages, Layouts and Templates receive a <code>pages</code> array that includes <a href="https://github.com/bcomnes/top-bun/blob/e83ea560f86dabcda68b0f701c8bfa770aba91ed/lib/build-pages/page-data.js#L25">PageData</a> instances for every page in the build. Variables are already pre-resolved, so you can easily filter, sort and target various pages in the build.</p>
<h3 id="full-type-support" tabindex="-1">Full Type Support</h3>
<p><code>top-bun</code> now has full type support. It’s achieved with <a href="https://github.com/voxpelli/types-in-js"><code>types-in-js</code></a> and it took a ton of time and effort.</p>
<p>The results are nice, but I’m not sure the juice was worth the squeeze. <code>top-bun</code> was working really well before types. Adding types required solidifying a lot of trivial details to make the type-checker happy. I don’t even think a single runtime bug was solved. It did help clarify some of the more complex types that had developed over the first 2 years of development though.</p>
<p>The biggest improvement provided here is that the following types are now exported from <code>top-bun</code>:</p>
<pre><code class="hljs language-ts"><span class="hljs-title class_">LayoutFunction</span>&lt;T&gt;
<span class="hljs-title class_">PostVarsFunction</span>&lt;T&gt;
<span class="hljs-title class_">PageFunction</span>&lt;T&gt;
<span class="hljs-title class_">TemplateFunction</span>&lt;T&gt;
<span class="hljs-title class_">TemplateAsyncIterator</span>&lt;T&gt;
</code></pre>
<p>You can use these to get some helpful auto-complete in LSP supported editors.</p>
<h4 id="types-in-js-not-typescript" tabindex="-1"><code>types-in-js</code> not Typescript</h4>
<p>This was the first major dive I did into a project with <a href="https://github.com/voxpelli/types-in-js"><code>types-in-js</code></a> support.
My overall conclusions are:</p>
<ul>
<li><code>types-in-js</code> provides a superior development experience to developing in <code>.ts</code> by eliminating the development loop build step.</li>
<li><a href="https://jsdoc.app">JSDoc</a> and <code>types-in-js</code> are in conflict with each other. <code>types-in-js</code> should win, its better than JSDoc in almost every way (but you still use both).</li>
<li>Most of the JSDoc auto-generating documentation ecosystem doesn’t support <code>types-in-js</code>. Find something that consumes the generated types instead of consuming the JSDoc blocs.</li>
<li>There are a number of rough edges around importing types.</li>
<li>The final api documentation features are nice.</li>
<li>The internal types feel good, but were mostly a waste of time being introduced after the fact.</li>
<li>I wish there was a way to strictly introduce types on just the public interfaces and work back, but this is challenging because many details come from deep within the program.</li>
<li>Typescript puts upper limits on the dynamism normally allowed with JS. It’s a superset syntax, that forces a subset of language functionality.</li>
<li><code>@ts-ignore</code> liberally. Take a pass or two to remove some later.</li>
</ul>
<h3 id="handlebars-support-in-md-and-html" tabindex="-1">Handlebars support in <code>md</code> and <code>html</code></h3>
<p>Previously, only <code>js</code> pages had access to the variable cascade inside of the page itself. Now <code>html</code> and <code>md</code> pages can access these variables with <a href="https://handlebarsjs.com">handlebars</a> placeholders.</p>
<pre><code class="hljs language-markdown"><span class="hljs-section">## My markdown page</span>

Hey this is a markdown page for {{ vars.siteName }} that uses handlebars templates.
</code></pre>
<h3 id="locally-shipped-default-styles" tabindex="-1">Locally shipped default styles</h3>
<p>Previously, if you opted for the default layout, it would import <a href="http://mine-css.neocities.org">mine.css</a> from <a href="https://unpkg.com">unpkg</a>. This worked, but went against the design goal of making <code>top-bun</code> sites as reliable as possible (shipping all final assets to the dest folder).</p>
<p>Now when you build with the default layout, the default stylesheet (and theme picker js code) is built out into your <code>dest</code> folder.</p>
<h3 id="built-in-browser-sync" tabindex="-1">Built-in <code>browser-sync</code></h3>
<p><code>@siteup/cli</code> previously didn’t ship with a development server, meaning you had to run one in parallel when developing. This step is now eliminated now that <code>top-bun</code> ships <a href="https://browsersync.io"><code>browser-sync</code></a>. <code>browser-sync</code> is one of the best Node.js development servers out there and offers a bunch of really helpful dev tools built right in, including scroll position sync so testing across devices is actually enjoyable.</p>
<p>If you aren’t familiar with browser-sync, here are some screenshots of fun feature:</p>
<figure class="borderless">
  <a href="./img/bs-remote-debugging.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/bs-remote-debugging.png" alt="Screenshot of Browser Sync debugging">
    </picture>
  </a>
  <figcaption>BrowserSync remote debugging</figcaption>
</figure>
<figure class="borderless">
  <a href="./img/bs-grid.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/bs-grid.png" alt="Screenshot of Browser Sync grid">
    </picture>
  </a>
  <figcaption>BrowserSync Grid overlay</figcaption>
</figure>
<figure class="borderless">
  <a href="./img/bs-outline.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/bs-outline.png" alt="Screenshot of Browser Sync css outline">
    </picture>
  </a>
  <figcaption>BrowserSync CSS outline</figcaption>
</figure>
<figure class="borderless">
  <a href="./img/bs-depth.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/bs-depth.png" alt="Screenshot of Browser Sync css depth">
    </picture>
  </a>
  <figcaption>BrowserSync CSS depth</figcaption>
</figure>
<figure class="borderless">
  <a href="./img/bs-depth.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/bs-depth.png" alt="Screenshot of Browser Sync css depth">
    </picture>
  </a>
  <figcaption>BrowserSync CSS depth</figcaption>
</figure>
<figure class="borderless">
  <a href="./img/bs-network-throttle.png">
    <picture>
      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
      <img loading="auto" src="./img/bs-network-throttle.png" alt="Screenshot of Browser Sync network throttle">
    </picture>
  </a>
  <figcaption>BrowserSync Network throttle</figcaption>
</figure>
<h3 id="top-bun-eject" tabindex="-1"><code>top-bun</code> eject</h3>
<p><code>top-bun</code> now includes an <code>--eject</code> flag, that will write out the default layout, style, client and dependencies into your <code>src</code> folder and update <code>package.json</code>. This lets you easily get started with customizing default layouts and styles when you decide you need more control.</p>
<pre><code class="hljs language-console">√ default-layout % npx top-bun --eject

top-bun eject actions:
  - Write src/layouts/root.layout.mjs
  - Write src/globals/global.css
  - Write src/globals/global.client.mjs
  - Add mine.css@^9.0.1 to package.json
  - Add uhtml-isomorphic@^2.0.0 to package.json
  - Add highlight.js@^11.9.0 to package.json

Continue? (Y/n) y
Done ejecting files!
</code></pre>
<p>The default layout is always supported, and its of course safe to rely on that.</p>
<h3 id="improved-log-output-%F0%9F%AA%B5" tabindex="-1">Improved log output 🪵</h3>
<p>The logging has been improved quite a bit. Here is an example log output from building this blog:</p>
<pre><code class="hljs language-console"><span class="hljs-meta prompt_">&gt; </span><span class="language-bash">top-bun --watch</span>

Initial JS, CSS and Page Build Complete
bret.io/src =&gt; bret.io/public
├─┬ projects
│ ├─┬ websockets
│ │ └── README.md: projects/websockets/index.html
│ ├─┬ tron-legacy-2021
│ │ └── README.md: projects/tron-legacy-2021/index.html
│ ├─┬ package-automation
│ │ └── README.md: projects/package-automation/index.html
│ └── page.js: projects/index.html
├─┬ jobs
│ ├─┬ netlify
│ │ └── README.md: jobs/netlify/index.html
│ ├─┬ littlstar
│ │ └── README.md: jobs/littlstar/index.html
│ ├── page.js:      jobs/index.html
│ ├── zhealth.md:   jobs/zhealth.html
│ ├── psu.md:       jobs/psu.html
│ └── landrover.md: jobs/landrover.html
├─┬ cv
│ ├── README.md: cv/index.html
│ └── style.css: cv/style-IDZIRKYR.css
├─┬ blog
│ ├─┬ 2023
│ │ ├─┬ reintroducing-top-bun
│ │ │ ├── README.md: blog/2023/reintroducing-top-bun/index.html
│ │ │ └── style.css: blog/2023/reintroducing-top-bun/style-E2RTO5OB.css
│ │ ├─┬ hello-world-again
│ │ │ └── README.md: blog/2023/hello-world-again/index.html
│ │ └── page.js: blog/2023/index.html
│ ├── page.js:   blog/index.html
│ └── style.css: blog/style-NDOJ4YGB.css
├─┬ layouts
│ ├── root.layout.js:             root
│ ├── blog-index.layout.js:       blog-index
│ ├── blog-index.layout.css:      layouts/blog-index.layout-PSZNH2YW.css
│ ├── blog-auto-index.layout.js:  blog-auto-index
│ ├── blog-auto-index.layout.css: layouts/blog-auto-index.layout-2BVSCYSS.css
│ ├── article.layout.js:          article
│ └── article.layout.css:         layouts/article.layout-MI62V7ZK.css
├── globalStyle:               globals/global-OO6KZ4MS.css
├── globalClient:              globals/global.client-HTTIO47Y.js
├── globalVars:                global.vars.js
├── README.md:                 index.html
├── style.css:                 style-E5WP7SNI.css
├── booklist.md:               booklist.html
├── about.md:                  about.html
├── manifest.json.template.js: manifest.json
├── feeds.template.js:         feed.json
├── feeds.template.js-1:       feed.xml
└── feeds.template.js-2:       atom.xml

[Browsersync] Access URLs:
 --------------------------------------
       Local: http://localhost:3000
    External: http://192.168.0.187:3000
 --------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 --------------------------------------
[Browsersync] Serving files from: /Users/bret/Developer/bret.io/public
Copy watcher ready
</code></pre>
<h3 id="support-for-mjs-and-cjs-file-extensions" tabindex="-1">Support for <code>mjs</code> and <code>cjs</code> file extensions</h3>
<p>You can now name your page, template, vars, and layout files with the <code>mjs</code> or <code>cjs</code> file extensions. Sometimes this is a necessary evil. In general, set your <code>type</code> in your <code>package.json</code> correctly and stick with <code>.js</code>.</p>
<h2 id="what%E2%80%99s-next-for-top-bun" tabindex="-1">What’s next for <code>top-bun</code></h2>
<p>The current plan is to keep sitting on this feature set for a while. But I have some ideas:</p>
<ul>
<li>Tell the world about it?</li>
<li>Server routes with <a href="https://fastify.dev">fastify</a> (or expose a plugin to slot into an existing server)?</li>
<li>Deeper integration with <a href="https://ghub.io/uhtml"><code>uhtml</code></a>?</li>
<li>More web-component examples? <code>top-bun</code> is already one of the best environments for implementing sites that use web-components. Page bundles are a perfect place to register components!</li>
<li>Comparisons with other tools in the “enterprise-js” ecosystem?</li>
</ul>
<p>If you try out <code>top-bun</code>, I would love to hear about your experience. Do you like it? Do you hate it? <a href="https://github.com/bcomnes/top-bun/discussions">Open an discussion item.</a> or reach out privately.</p>
<h2 id="history" tabindex="-1">History of <code>top-bun</code></h2>
<p>OK, now time for the story behind <code>top-bun</code> aka <code>@siteup/cli</code>.</p>
<p>I ran some experiments with orthogonal tool composition a few years ago. I realized I could build sophisticated module based websites by composing various tools together by simply running them in parallel.</p>
<p>What does this idea look like? See this snippet of a <code>package.json</code>:</p>
<pre><code class="hljs language-json"> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;build&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run clean &amp;&amp; run-p build:*&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;build:css&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;postcss src/index.css -o public/bundle.css&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;build:md&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;sitedown src -b public -l src/layout.html&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;build:feed&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;generate-feed src/log --dest public &amp;&amp; cp public/feed.xml public/atom.xml&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;build:static&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;cpx &#x27;src/**/*.{png,svg,jpg,jpeg,pdf,mp4,mp3,js,json,gif}&#x27; public&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;build:icon&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;gravatar-favicons --config favicon-config.js&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;watch&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run clean &amp;&amp; run-p watch:* build:static&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;watch:css&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s &#x27;build:css -- --watch&#x27;&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;watch:serve&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;browser-sync start --server &#x27;public&#x27; --files &#x27;public&#x27;&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;watch:md&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run build:md -- -w&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;watch:feed&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s build:feed&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;watch:static&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run build:static -- --watch&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;watch:icon&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s build:icon&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;clean&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;rimraf public &amp;&amp; mkdirp public&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;start&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run watch&quot;</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<ul>
<li>I have <a href="https://postcss.org"><code>postcss</code></a> building css bundles, enabling an <code>@import</code> based workflow for css, as well as providing various transforms I found useful.</li>
<li>The markdown is built with <a href="https://github.com/ungoldman/sitedown"><code>sitedown</code></a>.</li>
<li>I wrote a tool to generate a RSS feed from markdown called <a href="https://github.com/bcomnes/generate-feed"><code>generate-feed</code></a></li>
<li>I generate favicons from a gravatar identifier with <a href="https://github.com/bcomnes/gravatar-favicons"><code>gravatar-favicons</code></a>.</li>
<li><code>js</code> bundling could easily be added in here with <a href="https://esbuild.github.io"><code>esbuild</code></a> or <a href="https://ghub.io/rollup">rollup</a>.</li>
<li>Steps are grouped into <code>build</code> and <code>watch</code> prefixes, and managed with <a href="https://github.com/bcomnes/npm-run-all2">npm-run-all2</a> which provides shortcuts to running these tasks in parallel.</li>
</ul>
<p>I successfully implemented this pattern across 4-5 different websites I manged. It work beautifully. Some of the things I liked about it:</p>
<ul>
<li>✅ Tools could be swapped out easily.</li>
<li>✅ I could experiment individual ideas on individual sites, without forcing them on every other project.</li>
<li>✅ All of the tools were versioned gated, and I could update them as I had time.</li>
<li>✅ It worked very well with <a href="/projects/package-automation/">release automation</a>.</li>
</ul>
<p>But it had a few drawbacks:</p>
<ul>
<li>❌ I liked the pattern so much, that I wanted to add websites to more projects, but setting this all up was a hassle.</li>
<li>❌ When I discovered a change I liked, re-implementing the change across multiple projects become tedious.</li>
<li>⚠️ Orthogonal tool composition helps keep this arrangement clean and fast, but some steps really do benefit from the awareness of the results of other steps.</li>
</ul>
<p>So I decided to roll up all of the common patterns into a single tool that included the discoveries of this process.</p>
<h2 id="%40siteup%2Fcli-extended-sitedown" tabindex="-1"><code>@siteup/cli</code> extended <code>sitedown</code></h2>
<p>Because it was clear <code>sitedown</code> provided the core structure of this pattern (making the website part), I extended the idea in the project <code>@siteup/cli</code>. Here are some of the initial features that project shipped with:</p>
<ul>
<li>Variable cascade + frontmatter: You could define global and page variables used to customize parts of the site layout. This lets you set the title, or other parts of the <code>&lt;head&gt;</code> tag easily from the <a href="https://jekyllrb.com/docs/front-matter/">frontmatter</a> section of markdown pages.</li>
<li>A <code>js</code> layout: This let you write a simple JS program that receives the variable cascade and child content of the page as a function argument, and return the contents of the page after applying any kind of template tool available in JS.</li>
<li><code>html</code> pages: <a href="https://commonmark.org">CommonMark</a> supports <code>html</code> in markdown, but it also has some funny rules that makes it more picky about how the <code>html</code> is used. I wanted a way to access the full power of <code>html</code> without the overhead of markdown, and this page type unlocked that.</li>
<li><code>js</code> pages: Since I enjoy writing JavaScript, I also wanted a way to support generating pages using any templating system and data source imaginable so I also added support to generate pages from the return value of a JS function.</li>
<li>JavaScript bundling: I wanted to make it super easy to include client side JavaScript, so it included some conventions for setting that up quickly and easily.</li>
<li>CSS bundling: Instead of adding the complexity of css-modules or other transform-based scoped css solution, <code>siteup</code> opted to make it super easy to add a global <code>css</code> bundle, and page scoped <code>css</code> bundles, which both supported a <code>postcss</code> <code>@import</code> workflow and provided a few common transforms to make working with <code>css</code> tolerable (nesting, prefixing etc).</li>
<li>Static files: Including static files was such a common pattern that I also included the ability, to copy over static files without any additional configuration. I believe <code>sitedown</code> also added this feature subsequently.</li>
<li>Asset co-location: I wanted a way to co-locate assets, code and anything near where it was depended upon. It is such a natural way to organize complex collections, and so many tools break this, or require special locations for special files. Let me put it wherever I want in <code>src</code>!</li>
</ul>
<p>After sitting on the idea of <code>siteup</code> for over a year, by the time I published it to <code>npm</code>, the name was taken, so I used the npm org name hack to get a similar name <code>@siteup/cli</code>. <strong>SILLY NPM!</strong></p>
<p>I enjoyed how <code>@siteup/cli</code> came out, and have been using it for 2 year now. Thank you of course to <a href="https://ungoldman.com">ungoldman</a> for laying the foundation of most of these tools and patterns. Onward and upward to <code>top-bun</code>!</p>
<p><img src="./img/contribs.png" alt="contribution graph"></p>
]]></content>
    <link rel="alternate" href="https://bret.io/blog/2023/reintroducing-top-bun/"/>
  </entry>
  <entry>
    <id>https://bret.io/blog/2023/hello-world-again/#2023-08-30T18:06:24.000Z</id>
    <title>Hello world (again) 🌎</title>
    <updated>2023-08-30T18:06:24.000Z</updated>
    <published>2023-08-30T18:06:24.000Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p><a href="./sunset.jpeg"><img src="./sunset.jpeg" alt="sunset"></a></p>
<p>This blog has been super quiet lately, sorry about that.
I had some grand plans for finishing up my website tools to more seamlessly
support blogging.</p>
<p>The other day around 3AM, I woke up and realized that the tools aren’t stopping me
from writing, I am.
Also my silently implemented policy about not writing about ‘future plans and ideas before they are ready’ was implemented far to strictly.
It is in fact a good thing to write about in-progress ideas and projects slightly out into the future.
This is realistic, interesting, and avoids the juvenile trap of spilling ideas in front of the world only to see you never realize them.
So here I am writing a blog post again.</p>
<p>Anyway, no promises, but it is my goal to write various <em>ahem</em> opinions, thoughts and ideas more often because nothing ever happens unless you write it down.</p>
<h2 id="some-basic-updates" tabindex="-1">Some basic updates</h2>
<p>Here are some updates from the last couple years that didn’t make it onto this site before.</p>
<ul>
<li>I’m back in <a href="https://www.openstreetmap.org/#map=13/40.8455/-124.0532">California</a>.</li>
<li>I moved a bunch (homes, jobs). I bought a home and then sold it. I don’t recommend it!</li>
<li>I have a son and a daughter and am married. I recommend this!</li>
<li>I started <a href="https://hifiwi.fi">HifiWi.fi</a></li>
<li>The first product is <a href="https://breadcrum.net/">Breadcrum</a>, a bookmarking service with textual and media archiving super powers.</li>
<li>HifiWi has subsumed operation of <a href="https://gumcast.com/">Gumcast</a>, a tool to allow podcasting with GumRoad. Without advertising, it has picked up 100s of users organically from search results.</li>
<li>I had a near 2 year tenure at <a href="https://socket.dev">Socket Inc</a> dabbling in GitHub apps and npm security concerns.</li>
<li>I’m now working at <a href="https://socketsupply.co">socketsupply.co</a> on a runtime and P2P full time again. Really happy to be back in this space again.</li>
<li>I still work remote and intend to continue working remote.</li>
<li>A bunch of my projects are running on my own website/app builder too called <a href="https://github.com/bcomnes/top-bun">top-bun</a>. It’s maybe a 3rd of the way done, but has been useful for about 90% of my needs. I would like to write up a proper blog post about it someday.</li>
<li><a href="https://github.com/bcomnes/deploy-to-neocities/">bcomnes/deploy-to-neocities</a> has 300+ public users deploying websites from GitHub actions to Neocities.</li>
<li>My fork <a href="https://github.com/bcomnes/npm-run-all2">bcomnes/npm-run-all2</a> (co-maintained by <a href="https://voxpelli.com">voxpelli.com</a>) has picked up 1000+ public dependents.</li>
<li>Now that twitter (<em>ahem</em> x) has completely abandoned its charter as an open web website, you can find me posting on <a href="https://fosstodon.org/@bcomnes">@bcomnes@fosstodon.org</a> and <a href="https://bsky.app/profile/bret.io">@bret.io on bsky</a>. I would like to make this website authoritative though for me posts though.</li>
<li>I’m very lucky to have a new office that is a 1 minute walk from home:</li>
</ul>
<p><a href="./office.jpeg"><img src="./office.jpeg" alt="pic of the office"></a></p>
]]></content>
    <link rel="alternate" href="https://bret.io/blog/2023/hello-world-again/"/>
  </entry>
  <entry>
    <id>https://bret.io/projects/tron-legacy-2021/#2021-08-05T23:09:46.781Z</id>
    <title>Tron Legacy 2021</title>
    <updated>2021-08-05T23:09:46.781Z</updated>
    <published>2021-08-05T23:09:46.781Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<p>I updated the Sublime Text <a href="https://packagecontrol.io/packages/Tron%20Color%20Scheme"><code>Tron Color Scheme</code></a> today after a few weeks of reworking it for the recent <a href="https://www.sublimetext.com/blog/articles/sublime-text-4">release of Sublime Text 4</a>.</p>
<p>The <a href="https://github.com/bcomnes/sublime-tron-color-scheme/releases/tag/v2.0.0"><code>2.0.0</code> release</a> converts the older <a href="https://www.sublimetext.com/docs/color_schemes_tmtheme.html"><code>.tmTheme</code></a> format into the Sublime specific theme format.
Overall the new Sublime theme format (<a href="https://www.sublimetext.com/docs/color_schemes.html"><code>.sublime-color-scheme</code></a>) is a big improvement, largely due to its simple JSON structure and its variables support.</p>
<p>JSON is, despite the common arguments against it, super readable and easily read and written by humans.
The variable support makes the process of making a theme a whole lot more automatic, since you no longer have to find and replace colors all over the place.</p>
<p>The biggest problem I ran into was poor in-line color highlighting when working with colors, so I ended up using a VSCode plugin called <a href="https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight"><code>Color Highlight</code></a> in a separate window.
Sublime has a great plugin also called <a href="https://packagecontrol.io/packages/Color%20Highlight"><code>Color Highlight</code></a> that usually works well, but not in this case.
The Sublime <code>Color Highlight</code> variant actually does temporary modifications to color schemes, which seriously gets in the way when working on color scheme files.</p>
<p>The rewrite is based it off of the new <a href="https://www.youtube.com/watch?v=_HoltQwvF2o">Mariana Theme</a> that ships with <abbr title="Sublime Text 4">ST4</abbr>, so the theme should have support for most of the latest features in <abbr title="Sublime Text 4">ST4</abbr> though there are surely features that even Mariana missed.
Let me know if you know of any.</p>
<p>Here a few other points of consideration made during the rewrite:</p>
<ul>
<li>Remove the other theme variants. A breaking change, but they were poorly maintained to begin with.</li>
<li>Renamed the theme file to <code>Tron Legacy 4 (Dark)</code>.</li>
<li><a href="https://github.com/bcomnes/sublime-tron-color-scheme/pull/8">A light theme is in order</a>, but its a non-trivial rewrite to support a light mode.</li>
</ul>
<figure class="borderless">
  <img src="./js.png" alt="Tron Legacy 4 JS Syntax Example">
  <figcaption>JS Syntax example</figcaption>
</figure>
<figure class="borderless">
  <img src="./md.png" alt="Tron Legacy 4 Markdown Syntax Example">
  <figcaption>Markdown Syntax example</figcaption>
</figure>
<figure class="borderless">
  <img src="./py.png" alt="Tron Legacy 4 Python Syntax Example">
  <figcaption>Python Syntax example</figcaption>
</figure>
<figure class="borderless">
  <img src="./c.png" alt="Tron Legacy 4 C Syntax Example">
  <figcaption>C Syntax example</figcaption>
</figure>
<figure class="borderless">
  <img src="./diff.png" alt="Tron Legacy 4 diff Syntax Example">
  <figcaption>diff Syntax example</figcaption>
</figure>
<p>Here a few more relevant links and please let me know what you think if you try it out.</p>
<ul>
<li><a href="https://github.com/bcomnes/sublime-tron-color-scheme">github.com/bcomnes/sublime-tron-color-scheme</a></li>
<li><a href="https://github.com/bcomnes/sublime-tron-color-scheme/releases/tag/v2.0.0">Release page</a></li>
</ul>
<h2 id="syndication" tabindex="-1">Syndication</h2>
<ul>
<li><a href="https://twitter.com/bcomnes/status/1423418998725742602" rel="syndication" class="u-syndication">twitter thread</a></li>
</ul>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Sublime Tron Legacy color scheme fully updated for <a href="https://twitter.com/sublimehq?ref_src=twsrc%5Etfw">@sublimehq</a> Text 4. Full syntax support, lots of other small improvements. Also it supports &#39;glow&#39; text✌️ <a href="https://t.co/vShbGThgDF">pic.twitter.com/vShbGThgDF</a></p>&mdash; 🌌🌵🛸Bret🏜👨‍👩‍👧🚙 (@bcomnes) <a href="https://twitter.com/bcomnes/status/1423418998725742602?ref_src=twsrc%5Etfw">August 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><small><date>2021-08-05T23:09:46.781Z</date></small></p>
]]></content>
    <link rel="alternate" href="https://bret.io/projects/tron-legacy-2021/"/>
  </entry>
  <entry>
    <id>https://bret.io/jobs/littlstar/#2021-07-16T19:20:44.161Z</id>
    <title>Littlstar Portfolio</title>
    <updated>2021-07-16T19:20:44.161Z</updated>
    <published>2021-07-16T19:20:44.161Z</published>
    <author>
      <name>Bret Comnes</name>
      <uri>https://bret.io</uri>
    </author>
    <content type="html"><![CDATA[<img src="./littlstar-logo.svg" height=100 width=100 style='float: left; margin-right: 1em;'>
<p>After a short sabbatical in Denmark at <a href="https://archive.ph/m8Igr">Hyperdivision</a>, I joined <a href="https://littlstar.info">Littlstar</a>.
Here is a quick overview of some of the more interesting projects I worked on.
I joined Littlstar during a transitional period of the company and help them transition from a VR video platform to a video on demand and live streaming platform, and now an NFT sales and auction platform.</p>
<h2 id="nycpf-vr-platform" tabindex="-1">NYCPF VR Platform</h2>
<figure class="borderless">
  <img src="./nycpf.png" alt="NYCPF-XR screenshot">
  <figcaption>NYCPF used a local process talking over websocket RPC to a webapp running in a local browser.</figcaption>
</figure>
<p>My first project was picking up on an agency style project developing a novel VR reality training platform for <a href="https://www.nycpolicefoundation.org">NYCPF</a>, powered by a custom <a href="https://github.com/holepunchto/hypercore">hypercore</a> p2p file sharing network, delivering in-house developed unity VR scenarios.
These scenarios could then be brought to various locations like schools or events where NYCPF could guide participants through various law enforcement scenarios with different outcomes based on participant choices within the simulation.</p>
<p>By utilizing a p2p and offline first design approach, we were able to deliver an incredibly flexible and robust delivery platform that had all sorts of difficult features to develop for traditional file distribution platforms such as:</p>
<ul>
<li>Automated offline re-distribution of simulation data to a fleet of devices.</li>
<li>Bittorrent style inverted bandwidth characteristics (more demand = more availability).</li>
<li>Extremely high performance content distribution speeds on commodity hardware.</li>
<li>WAN and LAN peering between devices.</li>
<li>Centrally controlled editorial over available content, with eventual-consistency properties to offline or air-gaped clients.</li>
<li>Cross platform codebase targeting Windows, MacOS and Linux. The dev team utilized all 3 platforms during development, interchangeably.</li>
</ul>
<p>While the project was built on the backs of giants like the <a href="https://hypercore-protocol.org">Hypercore protocol</a>, as well as the amazing work of my <a href="https://github.com/orgs/little-core-labs/people">colleague</a>, I contributed in a number of areas to move the project forward during my time contracting on it.</p>
<ul>
<li>Take ownership of the preexisting React application.</li>
<li>Simplify and maintain a mix of internal open source and closed source packages.</li>
<li>Simplify React app state layer by eliminating the need for Redux in favor of built in React state solutions.</li>
<li>Implement offline mode via Service workers in conjunction with Hypercore.</li>
<li>Packaging and delivery tasks.</li>
<li>Improved progress UI/UX.</li>
<li>Contribute to native packaging tools build with <a href="https://github.com/vercel/pkg">pkg</a> and <a href="https://github.com/little-core-labs/tiny-module-compiler">tiny-module-compiler</a>.</li>
</ul>
<p><img src="./tmc.png" alt="Tiny Module Compiler"></p>
<p>Some of the discrete software packages that resulted from this project are described below.</p>
<h3 id="secure-rpc-protocol" tabindex="-1"><a href="https://github.com/little-core-labs/secure-rpc-protocol"><code>secure-rpc-protocol</code></a></h3>
<p>Secure <a href="https://github.com/little-core-labs/rpc-protocol">rpc-protocol</a> over any duplex socket using <a href="https://github.com/emilbayes/noise-peer">noise-protocol</a>.
This was a refactor of an existing RPC over websocket solution the project was using.
It improved upon the previous secure RPC already used by switching to using the <a href="http://noiseprotocol.org">noise protocol</a> which implements well understood handshake patterns that can be shared and audited between projects, rather than relying on a novel implementation at the RPC layer.
It also decoupled the RPC protocol from the underlying socket being used, so that the RPC system could be used over any other channels we might want in the future.</p>
<figure class="borderless">
  <img src="./secure-rpc-protocol.png" alt="Secure RPC Protocol">
  <figcaption><a href="https://github.com/little-core-labs/secure-rpc-protocol">Secure RPC protocol</a> uses the noise protocol for encryption, and works over any duplex socket.</figcaption>
</figure>
<h3 id="async-folder-walker" tabindex="-1"><a href="https://github.com/bcomnes/async-folder-walker"><code>async-folder-walker</code></a></h3>
<p>An async generator that walks files.
This project was a refactor of an existing project called <a href="https://ghub.io/folder-walker">folder-walker</a> implementing a high performance folder walk algorithm using a more modern async generator API.</p>
<figure>
  <img src="./afw.jpeg" alt="Async folder walker">
  <figcaption><a href="https://github.com/bcomnes/async-folder-walker">Async folder walker</a> provides a modern api to folder and filer walking of a directory.</figcaption>
</figure>
<h3 id="unpacker-with-progress" tabindex="-1"><a href="https://github.com/little-core-labs/unpacker-with-progress"><code>unpacker-with-progress</code></a></h3>
<p><code>unpacker-with-progress</code> is a specialized package that unpacks archives, and provides a progress api in order to provide UI feedback.
One of the deficiencies with the NYCPF project when I started was lack of UI feedback during the extraction process.</p>
<p>VR files are very large, and are delivered compressed to the clients.
After the download is complete, the next step to processing the data is unpacking the content.
This step did not provide any sort of progress feedback to the user because the underlying unpacking libraries did not expose this information, or only exposed some of the information needed to display a progress bar.</p>
<p>This library implemented support for unpacking the various archive formats the project required, and also added an API providing uniform unpacking progress info that could be used in the UI during unpacking tasks.</p>
<figure class="borderless">
  <img src="./uwp.png" alt="Unpacker with progress">
  <figcaption><a href="https://github.com/little-core-labs/unpacker-with-progress">unpacker-with-progress</a> is a consistency layer on top of a few unpacking libraries, with an emphasis on progress reporting for use in UI applications.</figcaption>
</figure>
<h3 id="hchacha20" tabindex="-1"><a href="https://github.com/little-core-labs/hchacha20"><code>hchacha20</code></a></h3>
<p>One of the more interesting side projects I worked on was porting over some of the <a href="https://doc.libsodium.org">libsodium</a> primitives to <a href="https://github.com/sodium-friends/sodium-javascript">sodium-javascript</a>.
I utilized a technique I learned about at Hyperdivision where one can write web assembly by hand in the <a href="https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format">WAT format</a>, providing a much wider set of data types, providing the type guarantees needed to write effective crypto.</p>
<p>While the WAT was written for HChaCha20, the effort was quite laborious and it kicked off a debate as to whether it would be better to just wrap <a href="https://github.com/jedisct1/libsodium.js">libsodium-js</a> (the official libsodium js port) in a wrapper that provided the <a href="https://github.com/sodium-friends/sodium-universal">sodium-universal</a> API.  This was achieved by another group in <a href="https://github.com/geut/sodium-javascript-plus">geut/sodium-javascript-plus</a> which successfully ran hypercores in the browser using that wrapper.</p>
<p>Ultimately, this effort was scrapped, determining that noise peer connections in the browser are redundant to webRTC encryption and https sockets.
It was a fun and interesting project none the less.</p>
<figure class="borderless">
  <img src="./hchacha.png" alt="Some HChacha WAT Code">
  <figcaption>A peek into the world of WASM via WAT.</figcaption>
</figure>
<h3 id="reconnecting-sockets" tabindex="-1">Reconnecting sockets</h3>
<p>We were having some state transition bugs between the webapp and the local server process, where the app could get into strange indeterminate states.
Both had independent reconnect logic wrapped up with application code, and it added a lot of chaos to understanding how each process was behaving when things went wrong (especially around sleep cycles on machines).</p>
<p>I implemented a generic reconnecting state machine that could accept any type of socket, and we were able to reduce the number of state transition bugs we had.</p>
<ul>
<li><a href="https://github.com/little-core-labs/reconnecting-socket"><code>little-core-labs/reconnecting-socket</code></a></li>
<li><a href="https://github.com/little-core-labs/reconnecting-simple-websocket"><code>little-core-labs/reconnecting-simple-websocket</code></a></li>
</ul>
<figure class="borderless">
  <img src="./rsw.png" alt="Reconnecting simple websocket">
</figure>
<h2 id="little-core-labs" tabindex="-1">Little Core Labs</h2>
<img src="./lcl.png" width=100 height=100 style='float: left; margin-right: 1em;'>
<p>After working on various agency projects at Littlstar, we formed a separate organization to start a fresh rewrite of the technology stack.</p>
<p>My high level contributions:</p>
<ul>
<li>Terraform running in Github actions</li>
<li>Provisioning AWS infrastructure.</li>
<li>Helping come up with how all of this stuff will work.</li>
</ul>
<p>This was an amazing founders-style opportunity to help rethink and re-implement years of work that had developed at Littlstar prior to  joining.
Effectively starting from 0, we rethought the entire technology pipeline, from operations, to infrastructure, to deployment, resulting in something really nice, modern, minimal, low maintenance and malleable.</p>
<ul>
<li><a href="https://github.com/little-core-labs">Little Core Labs Github</a></li>
<li><a href="https://twitter.com/littlecorelabs">Little Core Labs Twitter</a></li>
</ul>
<figure>
  <img src="./overview.jpg" alt="A snapshot of our cloudcraft">
  <figcaption>
    A screencap of our cloudformation diagram.
  </figcaption>
</figure>
<h2 id="terraform-ops" tabindex="-1">Terraform ops</h2>
<p>A culmination of ingesting the <a href="https://amzn.to/3er6lBB">“Terraform: Up &amp; Running”</a> and <a href="https://amzn.to/3rbNakx">“AWS Certified Solutions Architect”</a> books, as well as building off existing organizational experience with AWS, I helped research and design an operations plan using <a href="https://www.terraform.io">Terraform</a> and <a href="https://github.com/features/actions">GitHub Actions</a>.</p>
<div style='display: grid; grid-template-columns: 1fr 1fr; justify-items: center;'>
  <div>
    <img widht='199.5' height='250' src='./aws.jpg'/>
  </div>
  <div>
    <img width='190.5' height='250' src='./terraform.jpg'/>
  </div>
</div>
<p>This arrangement has proven powerful and flexible.
While it isn’t perfect, it has been effective and reliable and cheap, despite its imperfections in relation to some of the more esoteric edge cases of Terraform.</p>
<p>A quick overview of how its arrange:</p>
<ul>
<li>We have a global terraform repository with segmented terraform files for various services.</li>
<li>The <code>ops</code> global repo runs terraform in a bootstrapped GitHub actions environment.</li>
<li>We can create service level repos from the <code>ops</code> terraform repo, that in turn contain their own Terraform files specific to that service.</li>
<li>One of the benefits was that the GitHub environment worked along side a local environment, due to the use of AWS secrets manager and GitHub actions secrets (all managed in Terraform), so debugging was easy and flexible.</li>
</ul>
<h2 id="github-actions" tabindex="-1">Github actions</h2>
<img width='140' height='140' src='./actions.svg' alt='Actions logo' style='float: left; margin-right: 1em;' />
<p>One of the drawbacks of rolling our own Terraform CI infrastructure was that we had to tackle many small edge cases inside the GitHub actions environment.</p>
<p>It was nice to learn about the various types of custom GitHub actions one can write, as well as expand that knowlege to the rest of the org, but it also ate up a number of days focusing on DevOps problems specific to our CI environment.</p>
<p>Here are some of the problems I helped solve in the actions environment.</p>
<ul>
<li><a href="https://github.com/little-core-labs/install-terraform">install-terraform</a> - Install terraform at a specific version.</li>
<li><a href="https://github.com/little-core-labs/netrc-creds">netrc-creds</a> - Set up <a href="https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html">netrc</a> credentials.</li>
<li><a href="https://github.com/little-core-labs/get-git-tag">get-git-tag</a> - Easily get a git tag.</li>
</ul>
<p><img src="./netrc.png" alt="netrc-creds"></p>
<h2 id="sdk-js" tabindex="-1">sdk-js</h2>
<p>I helped lay the framework for the initial version of <code>sdk-js</code>, the Little Core Labs unified library used to talk to the various back-end services at Little Core Labs.</p>
<p>One of underlying design goals was to solve for the <a href="https://nodejs.org/api/esm.html">newly introduced native ESM features in node</a>, in such a way that the package could be consumed directly in the browser, natively as ESM in node, but also work in dual CJS/ESM environments like Next.js.
While this did add some extra overhead to the project, it serves as a design pattern we can pull from in the future, as well as a provide a highly compatible but modern API client.</p>
<p>I also extracted out this dual package pattern into a reusable template.</p>
<ul>
<li><a href="https://github.com/bcomnes/esm-template">bcomnes/esm-template</a></li>
</ul>
<p><img src="./sdk.png" alt="sdk-js"></p>
<h2 id="rad.live" tabindex="-1"><a href="https://rad.live">rad.live</a></h2>
<p><img src="./rad.jpg" alt="Rad.live"></p>
<p>I was the principal engineer on the new <a href="https://rad.live">Rad.live</a> website.
I established and implemented the tech stack, aiming to take a relatively conservative take on a code base that would maximize the readability and clarity for a Dev team that would eventually grow in size.</p>
<p>A high level, the application is simply an app written with:</p>
<ul>
<li><a href="https://nextjs.org">Next.js</a> - React with a framework to provide standard bundling, SSR and routing features.</li>
<li><a href="https://swr.vercel.app">SWR</a> - Declarative data <a href="https://reactjs.org/docs/hooks-intro.html">‘hooks’</a>, minimizing/eliminating complex state management throughout the app.</li>
<li><a href="https://github.com/little-core-labs/gqlr">GQLR</a> - A simple GraphQL client tuned to ‘just work’.</li>
<li><a href="https://github.com/vercel/styled-jsx">styled-jsx</a> - A simple and understandable CSS-In-JS framework that minimizes orthogonal layout CSS.</li>
</ul>
<p>From a development perspective, it was important to have testing and automation from the beginning.
For this we used:</p>
<ul>
<li>GitHub actions to run CI on every interaction.</li>
<li>Organizational standard linting that supported the unique needs of React.</li>
<li><a href="https://storybook.js.org">Storybook</a> for data/state independent component testing (Using the new and improved storybook API)</li>
<li>Automated component testing, with a minimum of ‘does it render’ testing utilizing the storybook stories.</li>
<li>Release automation.</li>
</ul>
<p>Overall, the codebase has had two other seasoned developers (one familiar and one new at React) jump in and find it productive based on individual testimony.
Gathering feedback from those thatI work with on technical decisions that I make is an important feedback look I always try to incorporate when green fielding projects.
Additionally, it has been a relatively malleable code base that is easy to add MVP features to and is in a great position to grow.</p>
<h3 id="vision.css" tabindex="-1"><code>vision.css</code></h3>
<p><img src="./vision.jpg" alt="vision.css"></p>
<p>I implemented a custom design system in tandem with our design team.
This project has worked out well, and has so far avoided ‘css lockout’, where only one developer can effectively make dramatic changes to an app layout due to an undefined and overly general orthogonal ‘global style sheet’.</p>
<p>The way this was achieved was by focusing on a simple global CSS style sheet that implements the base HTML elements in accordance with the design system created by the design team.
While this does result in a few element variants that are based on a global style class name, they remain in the theme of only styling ‘built in’ html elements, so there is little question what might be found in the global style sheet, and what needs to be a scoped css style.</p>
<p>Some of the features we used for vision.css</p>
<ul>
<li>Standard CSS syntax with a selection of a few ‘yet-to-be-released’ css syntax features.</li>
<li>A <a href="https://postcss.org">postcss</a> build pipeline.</li>
<li>Heavy use of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS variables</a>.</li>
<li>A <a href="https://little-core-labs.github.io/vision.css/">website</a> that contains a style-guide and preview of the various styled elements.</li>
</ul>
<h3 id="eslint-config-12core" tabindex="-1"><a href="https://github.com/little-core-labs/eslint-config-12core"><code>eslint-config-12core</code></a></h3>
<p><img src="./eslint.png" alt="eslint-config-12core"></p>
<p>Linting is the “spell check” of code, but its hard to agree on what rules to follow.
Like most things, having a standard set of rules that is good-enough is always better than no rules, and usually better than an unpredictable and unmaintainable collection of project-unique rules.</p>
<p>I put together a shared ESLint config that was flexible enough for most projects so far at Little Core Labs based on the ‘StandardJS’ ruleset, but remained flexible enough to modify unique org requirements.
Additionally, I’ve implemented it across many of the projects in the Github Org.</p>
<ul>
<li><a href="https://github.com/little-core-labs/eslint-config-12core"><code>eslint-config-12core</code></a></li>
</ul>
<h3 id="gqlr" tabindex="-1"><a href="https://github.com/little-core-labs/gqlr"><code>gqlr</code></a></h3>
<p><img src="./gqlr.png" alt="gqlr"></p>
<p><code>gqlr</code> is a simplified fork of <a href="https://github.com/prisma-labs/graphql-request">graphql-request</a>.</p>
<p>This relatively simple wrapper around the JS fetch API has a gaggle of upstream maintainers with various needs that don’t really match our needs.</p>
<p>The fork simplified and reduced code redundancy, improved maintainability through automation, fixed bugs and weird edge cases and dramatically improved errors and error handling at the correct level of the tech stack.
These changes would have unlikely been accepted upstream, so by forking we are able to gain the value out of open source resources, while still being able to finely tune them for our needs, as well as offer those changes back to the world.</p>
<ul>
<li><a href="https://github.com/little-core-labs/gqlr"><code>gqlr</code></a></li>
</ul>
<h3 id="local-storage-proxy" tabindex="-1"><a href="https://github.com/bcomnes/local-storage-proxy"><code>local-storage-proxy</code></a></h3>
<p><img src="./lsp.gif" alt=""></p>
<p>A configuration solution that allows for persistent overrides stored in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage">local storage</a>, including cache busting capabilities.  Implemented with a recursive <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">JS proxy</a> to simulate native object interactions over a window.localstorage interface.</p>
<ul>
<li><a href="https://github.com/bcomnes/local-storage-proxy"><code>local-storage-proxy</code></a></li>
</ul>
<h3 id="community-maintenance" tabindex="-1">Community maintenance</h3>
<p>We ended up taking on maintainence of a few other packages, providing fixes and improvements where the original authors seem to have left off.</p>
<ul>
<li><a href="https://github.com/little-core-labs/date-input-polyfill">little-core-labs/date-input-polyfill</a> - Polyfill for HTML5 date input element.</li>
<li><a href="https://github.com/little-core-labs/chromafi">little-core-labs/chromafi</a></li>
</ul>
<h3 id="video-platform" tabindex="-1">Video platform</h3>
<p>Here are some snapshots of the video platform we launched.</p>
<p><img src="./channels.jpg" alt=""></p>
<p><img src="./player.jpg" alt=""></p>
<h3 id="nft-auction-platform" tabindex="-1">NFT Auction Platform</h3>
<p>Here are some screenshots of the NFT auction platform I helped build.
The UI was fully responsive and updated on the fly to new results, thanks to the powers of SWR.</p>
<p><img src="./auction.jpg" alt=""></p>
<h3 id="marketing-pages" tabindex="-1">Marketing pages</h3>
<p>I did a few marketing pages as well.</p>
<p><img src="./marketing-rad.jpg" alt=""></p>
<p><img src="./marketing-nft.jpg" alt=""></p>
<p><img src="./marketing-onnit.jpg" alt=""></p>
<p><img src="./marketing-ps.jpg" alt=""></p>
<h2 id="conclusion" tabindex="-1">Conclusion</h2>
<p>While this isn’t everything I did at Littlstar, it captures many of the projects I enjoyed working on, and can hopefully provide some insights into my skills, interests and experiences from the past year.</p>
]]></content>
    <link rel="alternate" href="https://bret.io/jobs/littlstar/"/>
  </entry>
</feed>