<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Qubyte Codes</title>
  <link href="https://qubyte.codes"/>
  <link type="application/atom+xml" rel="self" href="https://qubyte.codes/atom.xml"/>
  <link href="https://ko-fi.com/qubyte" rel="payment" />
  <updated>2026-03-26T00:28:29Z</updated>
  <id>https://qubyte.codes/</id>
  <author>
    <name>qubyte</name>
  </author>

  <entry>
    <id>https://qubyte.codes/blog/private-data-for-js-classes-with-weakmap</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/private-data-for-js-classes-with-weakmap"/>
    <title>Private data for JS classes with WeakMap</title>
    <published>2015-12-30T14:35:00Z</published>
    <updated>2015-12-30T14:35:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Private data has always been awkward in JavaScript. It&amp;#39;s particularly difficult when it comes to
constructors, and with ES2015 recently published, classes too. Let&amp;#39;s say we have an example class,
exported by an ES2015 module:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Example&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;_privateDatum&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;();
  }

  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;_privateDatum&lt;/span&gt;);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the constructor, a field with some private data, &lt;code&gt;_privateDatum&lt;/code&gt; is appended (the value is a
placeholder for illustration). The initial underscore in the name is a common convention and is
meant to tell developers using the class that they shouldn&amp;#39;t touch or look at that field. Why should
this be private? Private stuff is subject to change without your users needing to know about it.
This field could be renamed or go away completely if you refactor, without affecting the public API.
So what&amp;#39;s the problem?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You can&amp;#39;t trust your users!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This isn&amp;#39;t meant as an insult. Your users are cunning, and if they can solve a problem without
filing an issue or raising a pull request, they probably will. They have deadlines after all... If
your class gets very popular, it becomes inevitable that someone is going to use your
private-by-convention field to hack together a solution to a problem they&amp;#39;re having, and you&amp;#39;ll
break their code when you change it. Changes to your public API should be clearly indicated by
changes to the version number and updated documentation. The inner workings of your code on the
other hand, including private data, are subject to dramatic change at any time.&lt;/p&gt;
&lt;p&gt;The solution is to hide the private data, removing the temptation. You can do this using a
&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures&quot;&gt;&lt;em&gt;closure&lt;/em&gt;&lt;/a&gt;. Consider:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Example&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; privateDatum &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;();

    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;log&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(privateDatum);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the private data is now assigned to a variable in the constructor. Since the
variable is not returned, nothing outside the constructor will have access to it. The pain now is
that the &lt;code&gt;log&lt;/code&gt; method has to be attached to the instance inside the constructor, so that it can have
access to the variable. It&amp;#39;s a real shame to lose the nice method syntax. It also make an individual
&lt;code&gt;log&lt;/code&gt; method for each instance, which means objects will each use more memory.&lt;/p&gt;
&lt;p&gt;This is where
&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap&quot;&gt;&lt;code&gt;WeakMap&lt;/code&gt;&lt;/a&gt;
comes in. An instance of &lt;code&gt;WeakMap&lt;/code&gt; has keys which are objects of some kind, and values which can be
whatever you like. &lt;code&gt;WeakMap&lt;/code&gt; instances are especially good, since if they are the last thing to hold
a reference to an object (as a key), then the JS engine is allowed to garbage collect it. This means
the risk of memory leaks is lessened. You &lt;em&gt;could&lt;/em&gt; simulate most aspects of &lt;code&gt;WeakMap&lt;/code&gt; using existing
structures like arrays, but that would always result in a memory leak, since the garbage collector
thinks those objects are in use and cannot clear them up. The final example below shows what this
looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; privateDatum &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;WeakMap&lt;/span&gt;();

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Example&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    privateDatum.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;set&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;());
  }

  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(privateDatum.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;));
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The keys of &lt;code&gt;privateDatum&lt;/code&gt; are the instances of the example class. If nothing else holds a reference
to an instance of the example class, the garbage collector doesn&amp;#39;t count the reference in
&lt;code&gt;privateDatum&lt;/code&gt; and can clear it up! Since the instances are keys, &lt;code&gt;this&lt;/code&gt; can be used in any method
to access the private data. The &lt;code&gt;privateDatum&lt;/code&gt; variable hidden by the module, so the user will have
no access to it!&lt;/p&gt;
&lt;p&gt;This approach can be used with constructor functions and methods appended to the prototype too. The
following constructor produces objects with similar behaviour to those produced by the class in the
previous example:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; privateDatum &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;WeakMap&lt;/span&gt;();

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;Example&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
  privateDatum.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;set&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;random&lt;/span&gt;());
}

&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Example&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;prototype&lt;/span&gt;&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;log&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(privateDatum.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;));
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The good news is that &lt;code&gt;WeakMap&lt;/code&gt; is one of the most well supported features of ES2015. With the
exception of IE Mobile and Opera Mobile, all current versions of major browsers support the
functionality in this post. See the
&lt;a href&#x3D;&quot;http://kangax.github.io/compat-table/es6/#test-WeakMap&quot;&gt;compatibility table&lt;/a&gt;. If you&amp;#39;re using a
maintained version of Node, you&amp;#39;re good to go!&lt;/p&gt;
&lt;h3 id&#x3D;&quot;addendum&quot;&gt;Addendum&lt;/h3&gt;
&lt;p&gt;It&amp;#39;s still possible to gain access to private data stored in a WeakMap by patching
&lt;code&gt;WeakMap.prototype.set&lt;/code&gt; or &lt;code&gt;WeakMap.prototype.get&lt;/code&gt;. This should &lt;em&gt;absolutely never be done!&lt;/em&gt; Along
with the usual reasons to not modify the prototype of a built in constructor, modifying WeakMap
risks undoing the whole reason for using it in the first place. By monitoring objects used as the
keys of a WeakMap, references can be created and the garbage collector may not be able to clean up
after you. That said, patching can be done. If you want to avoid that risk, you can &lt;code&gt;Object.freeze&lt;/code&gt;
both &lt;code&gt;WeakMap&lt;/code&gt; and &lt;code&gt;WeakMap.prototype&lt;/code&gt; before any other code runs.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/about-this-blog</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/about-this-blog"/>
    <title>About this blog</title>
    <published>2016-01-03T12:22:00Z</published>
    <updated>2016-01-03T12:22:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This blog took a long time to get started. Every time I tried to build it, I wound up focussed on
some tech I wanted to use to host it. In the previous iteration, I even
&lt;a href&#x3D;&quot;https://codeberg.org/qubyte/toisu-monorepo&quot;&gt;wrote a server framework&lt;/a&gt;. I took some holiday over the Christmas
period, so I decided to throw everything away and make something minimal.&lt;/p&gt;
&lt;p&gt;I chose to go with NGINX serving flat files produced by a static site generator. This is still me of
course, and on my own time I like to allow myself to indulge in a little reinvention of the wheel,
so I wrote the generator myself. It&amp;#39;s about a hundred lines of code, mainly stitching together other
small modules. I used Node to make the generator, with the following modules:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;module&lt;/th&gt;
&lt;th&gt;explanation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href&#x3D;&quot;https://www.npmjs.com/package/front-matter&quot;&gt;&lt;code&gt;front-matter&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For keeping YAML metadata at the top of post files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href&#x3D;&quot;https://www.npmjs.com/package/marked&quot;&gt;&lt;code&gt;marked&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;To compile post markdown to HTML.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href&#x3D;&quot;https://www.npmjs.com/package/highlight.js&quot;&gt;&lt;code&gt;highlight.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;To highlight code listings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href&#x3D;&quot;https://www.npmjs.com/package/handlebars&quot;&gt;&lt;code&gt;handlebars&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;To render posts into templates and an index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href&#x3D;&quot;https://www.npmjs.com/package/remark&quot;&gt;&lt;code&gt;remark&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;To pluck the first paragraph from each post to render into the index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href&#x3D;&quot;https://www.npmjs.com/package/slug&quot;&gt;&lt;code&gt;slug&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;To make post URLs readable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href&#x3D;&quot;https://www.npmjs.com/packages/clean-css&quot;&gt;&lt;code&gt;clean-css&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;To compile CSS sources together.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Posts are markdown files committed to a git repo. I use the pre-commit hook to compile and add the
rendered posts. At the moment I log into the server to pull changes down. I&amp;#39;ll have that working on
a GitHub hook soon.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve no intention of posting the generator, since that would mean supporting it. It&amp;#39;s strictly for
my use. I&amp;#39;ve linked the modules above since they&amp;#39;ve been very useful to me. If you&amp;#39;re thinking about
setting up a blog and you&amp;#39;re a programmer, I recommend this approach!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/about-this-blog-2</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/about-this-blog-2"/>
    <title>About this blog 2</title>
    <published>2016-01-11T20:15:00Z</published>
    <updated>2016-01-11T20:15:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I touched briefly on the technology used in this blog in
&lt;a href&#x3D;&quot;/blog/about-this-blog.html&quot;&gt;a previous post&lt;/a&gt;, but I didn&amp;#39;t explain the motivation behind a lot of
the choices I made when building it. I&amp;#39;d like to do that in this post. The design and architecture
of this blog is the product of what things I like in other blogs, and also those things that I find
frustrating. Where a choice was not obvious, I opted for the simplest option. The point of the
exercise was to get it online. Below are a few points in no particular order.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;https&quot;&gt;HTTPS&lt;/h3&gt;
&lt;p&gt;SSL certificates are now free thanks to &lt;a href&#x3D;&quot;https://letsencrypt.org&quot;&gt;Let&amp;#39;s Encrypt&lt;/a&gt;, so there is
absolutely no excuse to host blog content over plain HTTP. By doing this you honour the privacy of
your readers. It&amp;#39;s a little clunky if you&amp;#39;re using NGINX (as this blog is), but still far easier
than buying a domain name and hosting, sorting DNS etc.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;no-tracking&quot;&gt;No tracking&lt;/h3&gt;
&lt;p&gt;I don&amp;#39;t currently use tracking or cookies, mainly because I don&amp;#39;t need them. Tracking for a blog is
largely narcissism. There are no sessions for a blog, so no cookies are necessary. I might track
some basic data like user agent later, but that&amp;#39;s mainly so I know what browser features I can get
away with using. That sort of thing can be done server side. I don&amp;#39;t need that now though.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;render-once&quot;&gt;Render once&lt;/h3&gt;
&lt;p&gt;I really like single page apps. I work on one as part of my day job. The distinction here is that a
blog is not an application (at least not in my head). A blog is really just the good old fashioned
home page. Given that, I host HTML pages, and there&amp;#39;s no JavaScript code rendering DOM. This has
some nice repercussions. The browser does very little, so this is kind to batteries. Since the pages
are not dynamic, the pages can be statically generated by me, and hosted as files. This avoids
building HTML on the server and a database to store pre-rendered data. Just as this is kind to the
browser, it&amp;#39;s kind to the server.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;responsiveness&quot;&gt;Responsiveness&lt;/h3&gt;
&lt;p&gt;My CSS skills are pretty basic. I knew when building this that I wanted a classic centred column on
conventional computers, and also for posts to be readable on mobile devices. The CSS is configured
such that the text (including margins) will occupy the full width up to a maximum 800px, and
thereafter the text column remains at 800px wide and centred. Pretty old school, but also clean.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;dated-posts&quot;&gt;Dated posts&lt;/h3&gt;
&lt;p&gt;It&amp;#39;s rare, but I do come across how-to style blog posts which lack dates. This is frustrating, since
it&amp;#39;s difficult to know if the information in the post is stale.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;getting-to-the-point&quot;&gt;Getting to the point&lt;/h3&gt;
&lt;p&gt;Probably my biggest gripe with modern blogs and blog platforms is the massive image which dominates
the top of each post. This fad is terrible. You force the reader to scroll before they can begin
reading. You&amp;#39;re also making the browser download that image, which probably takes more data than the
rest of the page combined. This blog doesn&amp;#39;t do it. I want a reader to be able to start reading with
no interaction at all. This blog has been tested with devices as small as an iPhone 4 to that end.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;mobile-friendly&quot;&gt;Mobile friendly&lt;/h3&gt;
&lt;p&gt;I&amp;#39;ve already touched on this above. A big part of mobile friendly is handling crappy networks.
Crappy give each request a good chance of failing. Since the CSS is very simple, I minify and inline
it in the head of each post. This means that the typical post is just a single file, so there is as
good a chance as possible that the reader will receive the whole post.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;no-comment&quot;&gt;No comment&lt;/h3&gt;
&lt;p&gt;Comments are complex. Rolling my own would mean a database and rendering on the server or in the
browser. Using a third party would probably mean scripts. Both are fine, but I&amp;#39;m not sure if
comments are worth the trouble. If something is really worth saying, you can always tweet to me or
tweet a gist to me. If comments become desirable in the future, I can revisit them.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/how-i-schedule-posts-using-atd</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/how-i-schedule-posts-using-atd"/>
    <title>How I schedule posts using atd</title>
    <published>2016-01-14T19:30:00Z</published>
    <updated>2016-01-14T19:30:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This blog is built with a static site generator. The generator, the markdown source files, and the
generated HTML files are all kept together in the same git repository. Every time I commit a change,
a pre-commit hook runs the generator and adds the generated HTML, so that the blog entries are
always up to date. Then the changes are pushed up to GitHub.&lt;/p&gt;
&lt;p&gt;On the server (running Linux), NGINX is hosting a folder containing the files to serve from a clone
of the repo. To publish a new post, or update an old one, all I have to do is pull the changes from
GitHub. Publishing via the terminal allows me to use a one liner to &lt;em&gt;schedule&lt;/em&gt; the publication.
Many know about &lt;code&gt;cron&lt;/code&gt; for scheduling repeated tasks, but fewer are aware of &lt;code&gt;atd&lt;/code&gt;, which is for one
time scheduled tasks. The one liner I use is:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-bash&quot;&gt;&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;git -C /absolute/path/to/repo pull origin master&amp;quot;&lt;/span&gt; | at 07:00 tomorrow
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;at&lt;/code&gt; accepts input through stdin, which is why I&amp;#39;ve echoed the command and piped it. One big
gotcha is that you have to be aware of the timezone your machine is configured for. I have mine set
to UTC, and schedule according to that.&lt;/p&gt;
&lt;p&gt;There are companion utilities to manage scheduled jobs. The &lt;code&gt;man&lt;/code&gt; page for &lt;code&gt;at&lt;/code&gt; is extremely good,
so I won&amp;#39;t try to better it. The purpose of this post is to show you that it exists and how simple
(especially when compared with &lt;code&gt;cron&lt;/code&gt;) it is to use. You can use it to schedule pretty much
anything!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/private-methods-for-js-classes</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/private-methods-for-js-classes"/>
    <title>Private methods for JS classes</title>
    <published>2016-01-31T02:00:00Z</published>
    <updated>2016-01-31T02:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This is a short companion to an
&lt;a href&#x3D;&quot;/blog/private-data-for-js-classes-with-weakmap&quot;&gt;earlier article I wrote&lt;/a&gt; on using &lt;code&gt;WeakMap&lt;/code&gt; for
private data with JS classes. While private data belongs to instances, private methods can be shared
between instances of a class (just like its regular methods). An implementation using ES2015 modules
looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setContent&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;content&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; text &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createTextNode&lt;/span&gt;(content);

  &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;el&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;appendChild&lt;/span&gt;(text);
}

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Paragraph&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;content&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;el&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createElement&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;p&amp;#x27;&lt;/span&gt;);

    setContent.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;call&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;, content);
  }

  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;replace&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;content&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;el&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;innerHTML&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;&amp;#x27;&lt;/span&gt;;

    setContent.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;call&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;, content);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This module exports a class which wraps a paragraph element (which is not a particularly useful
thing to do, but hopefully illustrates what I&amp;#39;m about to say).&lt;/p&gt;
&lt;p&gt;Each instance of &lt;code&gt;Paragraph&lt;/code&gt; is constructed with some content. The one public method it has is
&lt;code&gt;replace&lt;/code&gt;, which allows the content to be replaced. Both the &lt;code&gt;constructor&lt;/code&gt; and the &lt;code&gt;replace&lt;/code&gt; methods
set content, so rather than repeat the code that does that, the code is placed in the private method
&lt;code&gt;setContent&lt;/code&gt;. &lt;code&gt;setContent&lt;/code&gt; operates on a &lt;code&gt;Paragraph&lt;/code&gt; instance passed in as the context by using
&lt;code&gt;call&lt;/code&gt;. You could just as easily pass the instance in by using a parameter. I&amp;#39;ve chosen the former
approach here to keep the difference between public and private methods to the minimum.&lt;/p&gt;
&lt;p&gt;By virtue of the &lt;code&gt;setContent&lt;/code&gt; method being declared outside of the class declaration, the class does
not make it available. You control which things you export from the module, so if the class doesn&amp;#39;t
make &lt;code&gt;replace&lt;/code&gt; available, and the module doesn&amp;#39;t export it, then nothing outside the module has
access. Thus &lt;code&gt;setContent&lt;/code&gt; is a private method.&lt;/p&gt;
&lt;p&gt;For clarity, &lt;code&gt;replace&lt;/code&gt; being public means that I can do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; paragraph &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Paragraph&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;hello&amp;#x27;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// replace is pubic, so we can use it.&lt;/span&gt;
paragraph.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;replace&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;goodbye&amp;#x27;&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And &lt;code&gt;setContent&lt;/code&gt; being private means that I cannot do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; paragraph &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Paragraph&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;hello&amp;#x27;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// setContent is not a part of the Paragraph&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// definition. We cannot do this.&lt;/span&gt;
paragraph.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setContent&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;goodbye&amp;#x27;&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So there you have it. There&amp;#39;s not a lot to the pattern really. Earlier module types and patterns
allow the same thing to be done. I recommend reading the &lt;a href&#x3D;&quot;https://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript&quot;&gt;section on module patterns&lt;/a&gt;
in Addy Osmani&amp;#39;s excellent book &lt;em&gt;Learning JavaScript Design Patterns&lt;/em&gt; if you want to know more (and
particularly the sub section on the &lt;em&gt;module pattern&lt;/em&gt;).&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/interfaces-for-javascript</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/interfaces-for-javascript"/>
    <title>Interfaces for JavaScript</title>
    <published>2016-03-13T10:45:00Z</published>
    <updated>2016-03-13T10:45:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I use &lt;code&gt;instanceof&lt;/code&gt; a lot in JavaScript. It&amp;#39;s very handy when writing unit tests. It&amp;#39;s easier to do
an &lt;code&gt;instanceof&lt;/code&gt; check than it is to exhaustively probe an object.&lt;/p&gt;
&lt;p&gt;Unfortunately &lt;code&gt;instanceof&lt;/code&gt; usually means an object has been constructed. If the constructed object
is coming from a third party library, or there is no access to the constructor, it can become
fiddly.&lt;/p&gt;
&lt;p&gt;This is why I&amp;#39;m excited about ES2015s &lt;code&gt;Symbol.hasInstance&lt;/code&gt;. It allows you to tune the behaviour of
&lt;code&gt;instanceof&lt;/code&gt; for a class. Here&amp;#39;s a minimal example of an &lt;em&gt;interface class&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;PositiveInteger&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;PositiveInteger is an interface class.&amp;#x27;&lt;/span&gt;);
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; [&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Symbol&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;hasInstance&lt;/span&gt;](value) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; value !&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;number&amp;#x27;&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (value &amp;lt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; value % &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt; &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  }
}

assert.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;ok&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;PositiveInteger&lt;/span&gt;);   &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// does not throw&lt;/span&gt;
assert.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;ok&lt;/span&gt;(-&lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;PositiveInteger&lt;/span&gt;);  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// throws&lt;/span&gt;
assert.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;ok&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;hi&amp;#x27;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;PositiveInteger&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// throws&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; positiveInt &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;PositiveInteger&lt;/span&gt;();    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// throws&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The class above exists &lt;em&gt;only&lt;/em&gt; to provide this &lt;code&gt;instanceof&lt;/code&gt; check. A more interesting example might
be a view. I assert that a view has an element, and render and remove methods. An interface class
for this might be:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;View&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;View is an interface class.&amp;#x27;&lt;/span&gt;);
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; [&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Symbol&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;hasInstance&lt;/span&gt;](value) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!value) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; value.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;render&lt;/span&gt; !&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;function&amp;#x27;&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; value.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;remove&lt;/span&gt; !&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;function&amp;#x27;&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; value.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;element&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;HTMLElement&lt;/span&gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now view objects can come from any source as long as they have render and remove methods and an
element. Objects which implement various interface classes also mean that these interface classes
don&amp;#39;t have to be in the same prototype chain. In other words, it gives you a way to use &lt;code&gt;instanceof&lt;/code&gt;
without invoking inheritance.&lt;/p&gt;
&lt;p&gt;Sadly &lt;code&gt;hasInstance&lt;/code&gt; will be one of the last ES2015 features to make it into browsers, so we&amp;#39;ll have
to wait a while before we can use it. See
&lt;a href&#x3D;&quot;http://kangax.github.io/compat-table/es6/#test-well-known_symbols_Symbol.hasInstance&quot;&gt;the compatibility table&lt;/a&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/a-presentation-on-async-await-and-toisu</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/a-presentation-on-async-await-and-toisu"/>
    <title>A presentation on async-await and Toisu!</title>
    <published>2016-04-06T22:00:00Z</published>
    <updated>2016-04-06T22:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Just before Christmas I gave a presentation on the upcoming async-await JavaScript language feature,
its basis in promises and generators, and finally a tiny server framework (like Express but a lot
leaner and more modular) which can make use of async functions as middleware (since an async
function is indistinguishable from a normal function which returns a promise). I&amp;#39;ll introduce Toisu!
in a blog post soon, but until then here&amp;#39;s the presentation:&lt;/p&gt;
&lt;div class&#x3D;&quot;embed-container&quot;&gt;&lt;iframe src&#x3D;&quot;https://www.youtube-nocookie.com/embed/XPMBAhiV5Wo?rel&#x3D;0&quot; frameborder&#x3D;&quot;0&quot; allowfullscreen&#x3D;&quot;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/adding-missing-features-to-set</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/adding-missing-features-to-set"/>
    <title>Adding missing features to Set</title>
    <published>2016-05-22T16:30:00Z</published>
    <updated>2016-05-22T16:30:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;ES2015 bought a &lt;code&gt;Set&lt;/code&gt; constructor to JavaScript. It&amp;#39;s pretty barebones,
consisting of a constructor which creates objects with a few methods for adding,
removing, checking if something is a member, and iterating over the set.
Instances have the essential quality of a set; an item is a member of the set or
not a member. Unlike an array, an item cannot be an element more than once. In
other words you can avoid using arrays and doing a lot of &lt;code&gt;indexOf&lt;/code&gt; checking.&lt;/p&gt;
&lt;p&gt;For example, abusing an array to act like a set is very common:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; myCollection &#x3D; [];

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Check if element is in the collection.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;collection, element&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; collection.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;indexOf&lt;/span&gt;(element) !&#x3D;&#x3D; -&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;;
}

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Add an element to the collection.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;collection, element&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(collection, element)) {
    collection.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;(element);
  }
}

&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(myCollection, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;123&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(myCollection, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;123&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// returns true&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(myCollection, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;456&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// returns false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using a set is more straight forward:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; myCollection &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;();

myCollection.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;123&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// add an element&lt;/span&gt;
myCollection.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;123&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// returns true&lt;/span&gt;
myCollection.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;456&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// returns false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;#39;s all grand, but a colleague of mine, a programmer dipping a toe into
modern JS, noted that &lt;code&gt;Set&lt;/code&gt; lacks some functions that you might expect it to
have out of the box, such as &lt;em&gt;union&lt;/em&gt;, &lt;em&gt;intersection&lt;/em&gt; and (relative)
&lt;em&gt;complement&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#39;d implement these as static methods of &lt;code&gt;Set&lt;/code&gt;, and shim them in. Hopefully
these methods will be added to the standard in the future. Until then, here are
some possible implementations.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;union&quot;&gt;Union&lt;/h2&gt;
&lt;p&gt;The union of two sets is defined as the set of elements found in either or both
sets.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;union&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; unionSet &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;();

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; element &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; a) {
    unionSet.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(element);
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; element &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; b) {
    unionSet.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(element)
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; union;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function iterates over both sets, adding their elements to a new set, which
is then returned.&lt;/p&gt;
&lt;p&gt;As a one liner:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;union&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-function&quot;&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) &#x3D;&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;([...a, ...b]);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sets are iterable, so the spread operator can be used to expand them as arrays.
Set can also take an array as an argument, filling the constructed set object
with elements from the array. This means that both sets can be spread into an
array literal and fed back into the set constructor. Since repeated elements are
ignored, the resultant set is the union of the two input sets.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;intersection&quot;&gt;Intersection&lt;/h2&gt;
&lt;p&gt;The intersection of two sets is defined as the set of elements they have in
common.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;intersection&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; [small, big] &#x3D; a.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;size&lt;/span&gt; &amp;lt; b.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;size&lt;/span&gt; ? [a, b] : [b, a];
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; intersectionSet &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;();

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; element &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; small) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (big.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(element)) {
      intersectionSet.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(element);
    }
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; intersectionSet;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first line of this one is an optimisation. The largest an intersection can
be is the size of the smaller set. The first line uses destructuring assignment
and a ternary to assign the smaller set to &lt;code&gt;small&lt;/code&gt; and the larger set to &lt;code&gt;big&lt;/code&gt;.
The &lt;code&gt;intersectionSet&lt;/code&gt; is then constructed, and &lt;code&gt;small&lt;/code&gt; looped over to fill it
with elements which are also in the &lt;code&gt;big&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want a one liner for this (and don&amp;#39;t care about the size optimisation):&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;intersection&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-function&quot;&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) &#x3D;&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;([...a].&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;element&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; b.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(element)));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This uses similar tricks to &lt;code&gt;Set.union&lt;/code&gt;. One set is expanded as an array, and
then filtered to an array of elements which are in the other. The filtered array
is then fed to &lt;code&gt;Set&lt;/code&gt; to get the intersection set.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;relative-complement&quot;&gt;Relative complement&lt;/h2&gt;
&lt;p&gt;The relative complement of A in B is the set of those elements in B which are
not in A.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;complement&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; complementSet &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;();

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; element &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; b) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!a.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(element)) {
      complementSet.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(element);
    }
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; complementSet;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function makes a new set and iterates over the second set, filling the new
set with elements of the second set which are not in the first.&lt;/p&gt;
&lt;p&gt;As a one liner:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;complement&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-function&quot;&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) &#x3D;&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;([...b].&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;element&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; !a.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(element)));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This uses similar tricks to the &lt;code&gt;Set.intersection&lt;/code&gt; one liner. It expands the
second set as an array, filters it down to elements not in the first, and then
instantiates a new &lt;code&gt;Set&lt;/code&gt; with the filtered array.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/tip-customizing-npm-version</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/tip-customizing-npm-version"/>
    <title>Tip: customizing npm version</title>
    <published>2016-09-05T20:00:00Z</published>
    <updated>2016-09-05T20:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The npm CLI has a bunch of useful utilities for managing projects. The obvious
one is &lt;code&gt;npm test&lt;/code&gt; but there are others. I particularly like working with
&lt;code&gt;npm version&lt;/code&gt; (the subject of this tip).&lt;/p&gt;
&lt;p&gt;Without customization, &lt;code&gt;npm version&lt;/code&gt; checks that the working git directory is
clean, sets the new version in the package file, and then commits it and tags
the repository with the same new version.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s great, but what if you have a &lt;code&gt;bower.json&lt;/code&gt; file, or some other task which
needs the new version before the tag is made? &lt;code&gt;npm version&lt;/code&gt; updates the version
in &lt;code&gt;package.json&lt;/code&gt; and it misses the version in the &lt;code&gt;bower.json&lt;/code&gt; file. The
&lt;code&gt;bower.json&lt;/code&gt; version is just an example, but one that illustrates this point.&lt;/p&gt;
&lt;p&gt;This is where customizing &lt;code&gt;npm version&lt;/code&gt; comes in. Continuing the &lt;code&gt;bower.json&lt;/code&gt;
example, we can write a script which performs the version update, and
uses &lt;code&gt;git add&lt;/code&gt; on it. In the &lt;code&gt;package.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;{
  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;my lovely app&amp;quot;&lt;/span&gt;,
  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1.0&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;.0&lt;/span&gt;,
  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;: {
    &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;node update-bower-version.js &amp;amp;&amp;amp; git add bower.json&amp;quot;&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To reiterate, &lt;code&gt;npm version&lt;/code&gt; will update the version in the package file, run the
version script above, and then commit the changes and tag the result. That means
that the script needs to do something to the &lt;code&gt;bower.json&lt;/code&gt; file, then &lt;code&gt;git add&lt;/code&gt;
the result.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// update-bower-version.js&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; fs &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;fs&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; bowerJsonPath &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;resolve&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;./bower&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; bowerJson &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(bowerJsonPath);

bowerJson.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;version&lt;/span&gt; &#x3D; process.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;env&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;npm_package_version&lt;/span&gt;; &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// npm injects this&lt;/span&gt;

fs.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;writeFileSync&lt;/span&gt;(bowerJsonPath, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;stringify&lt;/span&gt;(bowerJson, &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This script is going to be run by &lt;code&gt;npm&lt;/code&gt; from the &lt;code&gt;package.json&lt;/code&gt; file, which
handily &lt;a href&#x3D;&quot;https://docs.npmjs.com/misc/scripts#packagejson-vars&quot;&gt;injects some environment variables&lt;/a&gt;. One of these variables is the
version in the &lt;code&gt;package.json&lt;/code&gt; file. This is great because it means that no
matter what the new version will be we have access to it in this script.&lt;/p&gt;
&lt;p&gt;Since the &lt;code&gt;bower.json&lt;/code&gt; file contains JSON it can be read and parsed by &lt;code&gt;require&lt;/code&gt;
in one step. The version is updated, and the result stringified (I&amp;#39;m using two
space indentation here) and written back to the bower file.&lt;/p&gt;
&lt;p&gt;With a script in place, you can use &lt;code&gt;npm version&lt;/code&gt; without further thought. for
example, a major version bump looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-bash&quot;&gt;npm version major
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Enjoy!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/progressive-enhancement-1</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/progressive-enhancement-1"/>
    <title>Progressive enhancement #1</title>
    <published>2016-10-15T18:00:00Z</published>
    <updated>2016-10-15T18:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;When I first put together the CSS for this blog I avoided a fixed header since
the header felt a bit large, and I didn&amp;#39;t want to take up too much space which
could be used for content.&lt;/p&gt;
&lt;p&gt;The solution is a header which shrinks as the reader scrolls down. This gives
back a little space, and maintains access to the navigation bar (which is a
part of the header). To make it look nice, the title animates between the
smaller and larger states using a CSS transition. The CSS looks something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-css&quot;&gt;&lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;h1&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;font-size&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2rem&lt;/span&gt;;
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;margin&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1rem&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;transition&lt;/span&gt;: all &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0.3s&lt;/span&gt;;
}

&lt;span class&#x3D;&quot;hljs-selector-class&quot;&gt;.smaller&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;h1&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;font-size&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1rem&lt;/span&gt;;
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;margin&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0.5rem&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;m a novice when it comes to CSS, so let me know if I&amp;#39;m missing a trick!&lt;/p&gt;
&lt;p&gt;The markup for the top header (which has not changed), looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;header&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;class&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;top-header&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;h1&lt;/span&gt;&amp;gt;&lt;/span&gt;...&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;h1&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;nav&lt;/span&gt;&amp;gt;&lt;/span&gt;...&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;nav&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;header&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To trigger the transition, a little JavaScript is needed to detect when a scroll
event occurs. When the page is not scrolled down the header has no additional
classes added. When the page is scrolled a class is added to make the header
&lt;code&gt;h1&lt;/code&gt; text and margin smaller. When the page is scrolled down and then back to
the top, the header &lt;code&gt;h1&lt;/code&gt; text returns to its original size.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; $header &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;querySelector&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;.top-header&amp;#x27;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;checkHeaderSmallText&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;pageYOffset&lt;/span&gt; &amp;gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
    $header.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;classList&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;smaller&amp;#x27;&lt;/span&gt;);
  } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
    $header.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;classList&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;remove&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;smaller&amp;#x27;&lt;/span&gt;);
  }
}

&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;window&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;scroll&amp;#x27;&lt;/span&gt;, checkHeaderSmallText, &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;checkHeaderSmallText&lt;/span&gt;();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So why is this progressive? Most importantly, no markup (with the exception of
an added script tag in the head) has been added. Browsers with JavaScript
disabled or screen readers will interpret the site as they did before. If the
feature fails for any reason, or example if deferred scripts or &lt;code&gt;classList&lt;/code&gt; are
not supported, then an error will be thrown and the header will fail to shrink.
Should CSS transitions not be supported, the header will immediately go from the
larger to the smaller state on scrolling. These failure modes are acceptable in
my opinion.&lt;/p&gt;
&lt;p&gt;In the future I have a more elaborate header animation in mind. If you&amp;#39;re
interested in such things, watch this space!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/progressive-enhancement-2</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/progressive-enhancement-2"/>
    <title>Progressive enhancement #2</title>
    <published>2016-11-12T03:00:00Z</published>
    <updated>2019-01-17T16:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I recently attended ffconf, and was introduced to &lt;code&gt;position: sticky;&lt;/code&gt;. Support
for it is patchy, but where not available the header will scroll out of view as
it did in the past. Where available, the navbar will stick to the top of the
window when the rest of the header is scrolled out of view.&lt;/p&gt;
&lt;p&gt;This is a pure CSS solution, so this blog once again serves no JavaScript! the
only thing which I wanted that CSS does not provide is a selector for elements
in the stuck state.&lt;/p&gt;
&lt;p&gt;Update: You can view the video of Rachel Andrew&amp;#39;s presentation including
&lt;code&gt;position: sticky;&lt;/code&gt; &lt;a href&#x3D;&quot;https://youtu.be/uXYZbLT0j9c&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/promises-and-nodejs-event-emitters-dont-mix</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/promises-and-nodejs-event-emitters-dont-mix"/>
    <title>Promises and Node.js event emitters don&#x27;t mix</title>
    <published>2016-12-24T17:00:00Z</published>
    <updated>2016-12-24T17:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;To many experienced Node developers, the title of this post will seem
intuitively obvious. Nevertheless, it&amp;#39;s useful to see what unexpected behaviour
can occur when the two are used together. Here&amp;#39;s an example:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt; } &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;events&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; emitter &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt;();

&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Promise&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;reject&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Oh noes!&amp;#x27;&lt;/span&gt;))
  .&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;catch&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;e&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; emitter.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;emit&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;error&amp;#x27;&lt;/span&gt;, e));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you run this code (tested in Node v7.3 from a script, not REPL), what do you
expect to happen? I expected an &lt;code&gt;uncaughtException&lt;/code&gt; event, since an event
emitter is emitting an &lt;a href&#x3D;&quot;https://nodejs.org/dist/latest-v7.x/docs/api/events.html#events_error_events&quot;&gt;error event with no event handler&lt;/a&gt;, but that&amp;#39;s not
what happens at all. Instead you get an &lt;code&gt;UnhandledPromiseRejectionWarning&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;This is bad news. If something genuinely nasty has happened, you might want to
emit an error like this which is either explicitly handled or causes an
&lt;code&gt;uncaughtException&lt;/code&gt; event. Uncaught exceptions &lt;em&gt;should&lt;/em&gt; lead to the process
exiting, and a promise has just stifled it.&lt;/p&gt;
&lt;p&gt;So, what happened? Here&amp;#39;s a hint:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt; } &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;events&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; emitter &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt;();

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;try&lt;/span&gt; {
  emitter.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;emit&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;error&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Oh noes!&amp;#x27;&lt;/span&gt;));
} &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (e) {
  &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;caught&amp;#x27;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What do you expect to happen here? Again, at first glance I would have expected
an &lt;code&gt;uncaughtException&lt;/code&gt; event. &lt;code&gt;uncaughtException&lt;/code&gt; events can happen when one of
two conditions is met. The first is an error is thrown but not caught (thus the
name). The second is when an error event is emitted and the emitter has no
handler to deal with it.&lt;/p&gt;
&lt;p&gt;Did I say there were two conditions? I meant to say one! The second condition is
really the first in disguise. When an emitter lacks an error handler and it
emits an error event, the default error handler takes control and &lt;em&gt;throws&lt;/em&gt; the
error. Since event handlers are called synchronously upon event emissions, this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt; } &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;events&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; emitter &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;EventEmitter&lt;/span&gt;();

emitter.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;emit&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;error&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Oh noes!&amp;#x27;&lt;/span&gt;));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;is equivalent to&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Oh noes!&amp;#x27;&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, the very first snippet is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Promise&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;reject&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Oh noes!&amp;#x27;&lt;/span&gt;))
  .&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;catch&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;e&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; e;
  });
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The promise chain captures the thrown error and wraps it as a rejected promise,
which leads to an &lt;code&gt;UnhandledPromiseRejectionWarning&lt;/code&gt; since a second &lt;code&gt;.catch&lt;/code&gt; is
needed in the chain to handle it.&lt;/p&gt;
&lt;p&gt;If you want to ensure that unhandled error events lead to uncaught exceptions
which aren&amp;#39;t captured by a &lt;code&gt;catch&lt;/code&gt; or a promise chain, then you can wrap the
emission in a &lt;code&gt;setTimeout&lt;/code&gt; or a &lt;code&gt;setImmediate&lt;/code&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/making-arcade-controls-arduino-leonardo-code</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/making-arcade-controls-arduino-leonardo-code"/>
    <title>Making arcade controls: Arduino Leonardo code</title>
    <published>2017-05-01T00:00:00Z</published>
    <updated>2017-05-01T00:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I recently got it into my head that I wanted to build an arcade control panel
from parts. Specifically, an 8 way digital joystick and a bunch of buttons. How
it&amp;#39;ll look when finished isn&amp;#39;t important at the moment. It&amp;#39;s enough now to say
that there&amp;#39;ll be a joystick, six regular buttons, and two buttons for start and
select use.&lt;/p&gt;
&lt;p&gt;I decided to use an Arduino Leonardo to accept inputs from the buttons and
stick. The Leonardo presents itself as a keyboard to the machine you plug it
into, which is perfect for this. The only thing I needed to do is to initialize
the pins on the Arduino as inputs with inline resistance, and bind them to the
desired keys.&lt;/p&gt;
&lt;p&gt;Usually when a programmer thinks &amp;quot;the only thing I need to do&amp;quot;, what follows is
three or four times as much effort as predicted. I was surprised this time that
the effort required was so low, particularly given that I&amp;#39;ve not written any
C/C++ in years. In addition to functions for setting key states, &lt;code&gt;keyboard.h&lt;/code&gt;
provides a bunch of useful constants for special keys.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-cpp&quot;&gt;&lt;span class&#x3D;&quot;hljs-meta&quot;&gt;#&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;include&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;lt;Keyboard.h&amp;gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// MAMEish. An array of pin-key pairs.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; { &lt;span class&#x3D;&quot;hljs-type&quot;&gt;int&lt;/span&gt; pin; &lt;span class&#x3D;&quot;hljs-type&quot;&gt;int&lt;/span&gt; key; } pinkeys[] &#x3D; {
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;,  KEY_LEFT_ARROW  },
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;3&lt;/span&gt;,  KEY_UP_ARROW    },
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;4&lt;/span&gt;,  KEY_RIGHT_ARROW },
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;5&lt;/span&gt;,  KEY_DOWN_ARROW  },
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;6&lt;/span&gt;,  KEY_LEFT_CTRL }, &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Fire 1&lt;/span&gt;
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;7&lt;/span&gt;,  KEY_LEFT_ALT  }, &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Fire 2&lt;/span&gt;
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;8&lt;/span&gt;,  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27; &amp;#x27;&lt;/span&gt; },           &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Fire 3&lt;/span&gt;
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;9&lt;/span&gt;,  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;a&amp;#x27;&lt;/span&gt; },           &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Fire 4&lt;/span&gt;
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;s&amp;#x27;&lt;/span&gt; },           &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Fire 5&lt;/span&gt;
  { &lt;span class&#x3D;&quot;hljs-number&quot;&gt;11&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;q&amp;#x27;&lt;/span&gt; },           &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Fire 6&lt;/span&gt;
  { A0, &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;1&amp;#x27;&lt;/span&gt; },           &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// start&lt;/span&gt;
  { A1, KEY_ESC }        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// select&lt;/span&gt;
};

&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-type&quot;&gt;void&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title&quot;&gt;setup&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Set all used pins to handle input&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// from arcade buttons.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;auto&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-type&quot;&gt;const&lt;/span&gt; &amp;amp;pinkey : pinkeys) {
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;pinMode&lt;/span&gt;(pinkey.pin, INPUT_PULLUP);
  }

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Initialize keyboard.&lt;/span&gt;
  Keyboard.&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;begin&lt;/span&gt;();
}

&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-type&quot;&gt;void&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title&quot;&gt;loop&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// For each pin-key pair, check the&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// state of the pin and set the&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// associated key state to match.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;auto&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-type&quot;&gt;const&lt;/span&gt; &amp;amp;pinkey : pinkeys) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;digitalRead&lt;/span&gt;(pinkey.pin) &#x3D;&#x3D; LOW) {
      Keyboard.&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;press&lt;/span&gt;(pinkey.key);
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
      Keyboard.&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;release&lt;/span&gt;(pinkey.key);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since the Arduino is programmed in a dialect of recent  C++, range-based for
loops are available, as is type inference with &lt;code&gt;auto&lt;/code&gt;. This meant I could use an
array of instances of an anonymous structure to express the pin-key pairs. Not a
&lt;code&gt;sizeof&lt;/code&gt; in sight!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/test-friendly-mixins</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/test-friendly-mixins"/>
    <title>Test friendly mixins</title>
    <published>2017-07-20T13:30:00Z</published>
    <updated>2017-07-20T13:30:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;ve recently been attempting to code a clone of the classic game asteroids
using canvas in the browser. Since this is me, I&amp;#39;ve been distracted by all sorts
of programming detours.&lt;/p&gt;
&lt;p&gt;This post is roughly the process I went through for one such detour.&lt;/p&gt;
&lt;p&gt;I began planning the game by sketching out the objects it would contain. This
lead to a relatively (for the subject matter) deep class hierarchy including an
abstract entity as a base class, a ship class, an asteroid class, a bullet
class, and so on. It quickly became obvious that it was getting convoluted, with
some repetition to avoid artificial seeming relationships between these classes.&lt;/p&gt;
&lt;p&gt;To avoid this sort of convolution, I could eschew classes in favour of plain
objects and mixins, where mixins embody chunks of useful behaviour which are
copied onto host objects. For example, most objects in the game can move, so I&amp;#39;d
write a mixin function to copy a move method onto host objects.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m not a purist though. There&amp;#39;s still value to a base class to encode all
essential properties of all objects in the game. Extending the base class for
other objects in the game makes sense for those behaviours and properties unique
to the child class. For example, the ship can be controlled by the user, so such
control would be defined as part of the &lt;code&gt;Ship&lt;/code&gt; child class. Other behaviours
will not be unique to child classes, so these can go into mixins.&lt;/p&gt;
&lt;p&gt;There are various approaches to mixins in the wild, but I decided to roll my
own. I wrote a function which builds and returns another function. The returned
function applies a mixin. It looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createMixin&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;descriptors&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;mix&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;defineProperties&lt;/span&gt;(obj, descriptors);
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The argument &lt;code&gt;descriptors&lt;/code&gt; is an object with &lt;a href&#x3D;&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties&quot;&gt;property descriptors&lt;/a&gt;. I use
these rather than simple object fields since it allows me to create mixins which
are extremely configurable.&lt;/p&gt;
&lt;p&gt;Using this I can program the bulk of the behaviour of objects in the game. For
example, since all objects can be assumed to have position and velocity, one
such mixin could add a &lt;code&gt;move&lt;/code&gt; method (which I suggested earlier in this post):&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; movable &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createMixin&lt;/span&gt;({
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;move&lt;/span&gt;: {
    &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;value&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;dt&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;position&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;x&lt;/span&gt; +&#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;velocity&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;x&lt;/span&gt; * dt;
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;position&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;y&lt;/span&gt; +&#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;velocity&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;y&lt;/span&gt; * dt;
    },
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;configurable&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// These three properties&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;enumerable&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// are like how class&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;writable&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;      &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// methods are set.&lt;/span&gt;
  }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Consider the ship at the centre of the game. We could code it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; ship &#x3D; {
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;position&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;50&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;50&lt;/span&gt; },
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;velocity&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; }
};

&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;movable&lt;/span&gt;(ship); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Apply the mixin.&lt;/span&gt;

ship.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;move&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Now at x: 51, y: 52&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I want the ship to be a class extending a base class though:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Base class for all game objects.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Entity&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;{ position, velocity }&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;position&lt;/span&gt; &#x3D; { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: position.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;x&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: position.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;y&lt;/span&gt; };
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;velocity&lt;/span&gt; &#x3D; { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: velocity.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;x&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: velocity.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;y&lt;/span&gt; };
  }
}

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_ inherited__&quot;&gt;Entity&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Ship specific behaviour in here.&lt;/span&gt;
}

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Give all Ship instances access to move.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;movable&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;prototype&lt;/span&gt;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; ship &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt;({
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;position&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;50&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;50&lt;/span&gt;},
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;velocity&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; }
});

&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;movable&lt;/span&gt;(ship); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Apply the mixin.&lt;/span&gt;

ship.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;move&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Now at x: 51, y: 52&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All sorts of objects and classes may be composed with mixins.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m a professional JavaScript programmer, which means that each time I write
something there&amp;#39;s a little voice in my head asking me how hard it&amp;#39;ll be to write
tests for it. Applying the mixin to a simple object makes writing tests for the
mixin in isolation possible. In mocha:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;describe&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;movable&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;() &#x3D;&amp;gt;&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;it&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;appends a single method &amp;quot;move&amp;quot;&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;() &#x3D;&amp;gt;&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; entity &#x3D; {
      &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;position&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt; },
      &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;velocity&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt; }
    };

    &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;movable&lt;/span&gt;(entity);

    assert.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;equal&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; entity.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;move&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;function&amp;#x27;&lt;/span&gt;);
  });

  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;it&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;updates the position given a dt&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;() &#x3D;&amp;gt;&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; entity &#x3D; {
      &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;position&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;5&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;6&lt;/span&gt; },
      &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;velocity&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; }
    };

    &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;movable&lt;/span&gt;(entity);

    entity.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;move&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;3&lt;/span&gt;);

    assert.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;deepEqual&lt;/span&gt;(entity.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;position&lt;/span&gt;, { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;8&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;12&lt;/span&gt; });
  });
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;d be more exhaustive in real world tests, but I hope you get the gist.&lt;/p&gt;
&lt;p&gt;What about objects which have mixins applied to them though? How can we test
them? Naïvely you could write the above tests for every class that uses the
mixin. That&amp;#39;s repetitive though. It would be nicer to have some mechanism to ask
a mixin if an object has had the mixin applied to it. Then test for a class can
use a single test for each mixin to check that the mixin has been applied, and
all the repetition can be avoided.&lt;/p&gt;
&lt;p&gt;Revisiting the mixin creating function, the first go at such a mechanism looks
like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createMixin&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;descriptors&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// References to objects the mixin has been&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// applied to.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; mixed &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;WeakSet&lt;/span&gt;();

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;mix&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;defineProperties&lt;/span&gt;(obj, descriptors);
    mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(obj);
  };

  mix.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;isMixed&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(obj);
  };

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; mix;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;ve used a &lt;a href&#x3D;&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/WeakSet&quot;&gt;&lt;code&gt;WeakSet&lt;/code&gt;&lt;/a&gt; here. A &lt;code&gt;WeakSet&lt;/code&gt; contains &lt;em&gt;weak&lt;/em&gt; references to
objects. These are references which the garbage collector ignores. When no
strong references to an object in a &lt;code&gt;WeakSet&lt;/code&gt; remain, the garbage collector can
clear the object. If I used a regular &lt;code&gt;Set&lt;/code&gt; or an array here, the references
contained would be strong, and references could never be cleaned up
automatically by the garbage collector, resulting in a memory leak. This would
be a problem in particular for asteroids, as each time one is destroyed the
object itself would be held in the &lt;code&gt;Set&lt;/code&gt; or array be a strong reference, leading
it to grow as more and more asteroids are spawned.&lt;/p&gt;
&lt;p&gt;Now we can ask the mixin if an object has had the mixin applied to it:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;movable.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;isMixed&lt;/span&gt;(entity); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// false&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;movable&lt;/span&gt;(entity);

movable.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;isMixed&lt;/span&gt;(entity); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What about classes though? This won&amp;#39;t work because the mixin is applied to the
prototype and no reference to an instance will be stored by the mixin. We can
fix this by climbing up the prototype chain and checking each prototype object:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createMixin&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;descriptors&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// References to objects the mixin has been&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// applied to.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; mixed &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;WeakSet&lt;/span&gt;();

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;mix&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;defineProperties&lt;/span&gt;(obj, descriptors);
    mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(obj);
  };

  mix.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;isMixed&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Walk up the prototype chain and check&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// if each step has had the mixin applied&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// to it.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; o &#x3D; obj; o; o &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;getPrototypeOf&lt;/span&gt;(o)) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(o)) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;;
      }
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
  };

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; mix;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works perfectly well now, and any child classes can inherit the method as
expected and the &lt;code&gt;isMixed&lt;/code&gt; check will still work. It is now possible to avoid
duplication of tests. For example, part of a test suite for the ship class might
look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;it&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;is movable&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;() &#x3D;&amp;gt;&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; ship &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt;({
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;position&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt; },
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;velocity&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt; }
  });

  assert.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;ok&lt;/span&gt;(movable.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;isMixed&lt;/span&gt;(ship));
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One final adjustment can be made. It&amp;#39;s a shame that we can use &lt;code&gt;instanceof&lt;/code&gt; for
objects constructed by a class, but not objects which have had the mixin applied
to them. This can be achieved by using &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance&quot;&gt;&lt;code&gt;Symbol.hasInstance&lt;/code&gt;&lt;/a&gt;. A first
attempt:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createMixin&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;descriptors&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; mixed &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;WeakSet&lt;/span&gt;();

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;mix&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;defineProperties&lt;/span&gt;(obj, descriptors);
    mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(obj);
  };

  mix[&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Symbol&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;hasInstance&lt;/span&gt;] &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; o &#x3D; obj; o; o &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;getPrototypeOf&lt;/span&gt;(o)) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(o)) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;;
      }
    }
  };

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; mix;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Unfortunately this doesn&amp;#39;t work because &lt;code&gt;Function.prototype[Symbol.hasInstance]&lt;/code&gt;
is not writable. When appending a property to something, the field you&amp;#39;re
appending it as is checked for writability, and that check propagates up the
prototype chain. Since &lt;code&gt;mix&lt;/code&gt; is a function, it has a non-writable
&lt;code&gt;Symbol.hasInstance&lt;/code&gt; field in its prototype chain, and we need to work around
that.&lt;/p&gt;
&lt;p&gt;We can once again use &lt;code&gt;Object.defineProperty&lt;/code&gt; to do it:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createMixin&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;descriptors&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; mixed &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;WeakSet&lt;/span&gt;();

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;mix&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;defineProperties&lt;/span&gt;(obj, descriptors);
    mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(obj);
  };

  &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;defineProperty&lt;/span&gt;(mix, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Symbol&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;hasInstance&lt;/span&gt;, {
    &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;value&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;obj&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; o &#x3D; obj; o; o &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Object&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;getPrototypeOf&lt;/span&gt;(o)) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (mixed.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(o)) {
          &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;;
        }
      }
    },
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;configurable&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;enumerable&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;writable&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;
  });

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; mix;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally we can do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_ inherited__&quot;&gt;Entity&lt;/span&gt; { &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;/* ... */&lt;/span&gt; }

&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;movable&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;prototype&lt;/span&gt;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; ship &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt;({ &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;/* ... */&lt;/span&gt; });

ship &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Entity&lt;/span&gt;;  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// true&lt;/span&gt;
ship &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt;;    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// true&lt;/span&gt;
ship &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; movable; &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So the test suite for a ship can include tests like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;it&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;is movable&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;() &#x3D;&amp;gt;&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; ship &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Ship&lt;/span&gt;({
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;position&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt; },
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;velocity&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;x&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;y&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt; }
  });

  assert.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;ok&lt;/span&gt;(ship &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;instanceof&lt;/span&gt; movable);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So there you have it! All the test friendliness of classes with the
composability of mixins.&lt;/p&gt;
&lt;p&gt;I wrapped this code up in a module called &lt;a href&#x3D;&quot;https://www.npmjs.com/package/mixomatic&quot;&gt;mixomatic&lt;/a&gt;, which I intend to use
heavily in my gaming codebases.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/my-first-custom-element</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/my-first-custom-element"/>
    <title>My first custom element</title>
    <published>2017-11-16T01:45:00Z</published>
    <updated>2017-11-16T01:45:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;After some years of browser vendors working out what web components should look
like, they&amp;#39;re almost ready for the prime time. The part which I find most
intriguing (custom elements) has finally stabilised. With custom elements, you
can make new HTML elements which have custom behaviour which you define using
JavaScript. In this post I&amp;#39;ll demonstrate a custom element for fuzzy counting.&lt;/p&gt;
&lt;p&gt;Custom elements are created in two parts. Firstly we need to extend an element
with a JavaScript class.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;FuzzyCount&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_ inherited__&quot;&gt;HTMLElement&lt;/span&gt; {

}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So far this describes no custom behaviour. All this is is the extended class,
with identical behaviour to an &lt;code&gt;HTMLElement&lt;/code&gt;. We can customize the constructor
to add the behaviour we want. In order to give the element data to use, it must
be passed in as an attribute. We&amp;#39;ll use an attribute called &lt;code&gt;count&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;FuzzyCount&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_ inherited__&quot;&gt;HTMLElement&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;constructor&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// The parent constructor must be called&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// before using &#x60;this&#x60;.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;super&lt;/span&gt;();

    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// rawCount is a string.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; rawCount &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;getAttribute&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;count&amp;#x27;&lt;/span&gt;);
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; count &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;parseInt&lt;/span&gt;(rawCount, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;);

    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Set text content based on the count.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;none&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;one&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;a couple&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &amp;lt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;5&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;a few&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &amp;lt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;several&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;lots&amp;#x27;&lt;/span&gt;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The element reads and parses the &lt;code&gt;count&lt;/code&gt; attribute, giving itself a text content
accordingly.&lt;/p&gt;
&lt;p&gt;To make the element available to a page, it must be registered. One interesting
restriction placed upon custom elements is that they must contain a hyphen so
that they can be distinguished from built-in elements. We&amp;#39;re going to register
our element as &lt;code&gt;fuzzy-count&lt;/code&gt;, but in the real world you should prefix it with
a namespace. For example, if we&amp;#39;re working at a place called Funky Corp, we
could name the element &lt;code&gt;funkycorp-fuzzy-count&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;customElements.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;define&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;fuzzy-count&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;FuzzyCount&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can use this to make elements on the page!&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;fuzzy-count&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;count&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;3&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;fuzzy-count&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Any matching custom elements which exist before the element is defined will be
upgraded. This means a page can be sent by the server with custom elements
included, and everything will be rendered properly once the custom element is
registered.&lt;/p&gt;
&lt;p&gt;But what if we want to create elements in JS? We can try:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; fuzzyCount &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createElement&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;fuzzy-count&amp;#x27;&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;but an error will be thrown. It turns out that newly constructed elements cannot
contain other stuff (the text content). Even if no error was thrown, we&amp;#39;d
have an element with no &lt;code&gt;count&lt;/code&gt; attribute, and so we&amp;#39;ve missed our chance since
all the logic is in the constructor. It turns out that the constructor is the
wrong place for this stuff.&lt;/p&gt;
&lt;p&gt;Thankfully we can make a few changes to defer the setting of text content.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;FuzzyCount&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_ inherited__&quot;&gt;HTMLElement&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Called when the element is inserted into&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// the document or upgraded.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;connectedCallback&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setTextContent&lt;/span&gt;();
  }

  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setTextContent&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; rawCount &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;getAttribute&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;count&amp;#x27;&lt;/span&gt;);

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!rawCount) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;&amp;#x27;&lt;/span&gt;;
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; count &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;parseInt&lt;/span&gt;(rawCount, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;);

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;none&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;one&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;a couple&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &amp;lt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;5&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;a few&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (count &amp;lt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;several&amp;#x27;&lt;/span&gt;;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
      &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;textContent&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;lots&amp;#x27;&lt;/span&gt;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The logic which sets the &lt;code&gt;textContent&lt;/code&gt; has moved to its own method
&lt;code&gt;setTextContent&lt;/code&gt;, and &lt;code&gt;connectedCallback&lt;/code&gt; calls it when the element is
&amp;quot;connected&amp;quot; to avoid the error when constructing the element in JS. Connected is
called when the element is inserted into the document, or the element is
upgraded by custom element registration.&lt;/p&gt;
&lt;p&gt;This is enough to get the behaviour we want, so long as the count attribute
of a new &lt;code&gt;fuzzy-count&lt;/code&gt; element is set &lt;em&gt;before&lt;/em&gt; it is appended to the document.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; fuzzy &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createElement&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;fuzzy-count&amp;#x27;&lt;/span&gt;);
fuzzy.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setAttribute&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;count&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;body&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;appendChild&lt;/span&gt;(fuzzy);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can take one more step to make the element react to changes to the count
attribute...&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;FuzzyCount&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_ inherited__&quot;&gt;HTMLElement&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Called for each watched attribute when&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// the element is added to the document or&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// upgraded. Also fired for a watched&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// attribute when it is added, updated, or&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// removed.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;attributeChangedCallback&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setTextContent&lt;/span&gt;();
  }

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// A static getter because we can&amp;#x27;t add a&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// static value within a class declaration&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// directly (yet).&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;get&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;observedAttributes&lt;/span&gt;() {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; [&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;count&amp;#x27;&lt;/span&gt;];
  }

  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setTextContent&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Same as before.&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;connectedCallback&lt;/code&gt; has been replaced with &lt;code&gt;attributeChangedCallback&lt;/code&gt;, and a
static getter defines the list of attributes to watch. For each watched
attribute, when an element is inserted, upgraded, or the attribute is added,
updated, or removed, this &lt;code&gt;attributeChangedCallback&lt;/code&gt; is called. These changes
allow us to update elements before appending as before, and also &lt;em&gt;after&lt;/em&gt;
appending.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; fuzzy &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;createElement&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;fuzzy-count&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;body&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;appendChild&lt;/span&gt;(fuzzy);
fuzzy.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setAttribute&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;count&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Overall I&amp;#39;m impressed. I&amp;#39;ve only covered some of the API available here. &lt;em&gt;It is&lt;/em&gt;
a little fiddly, but upon consideration it all seems to make sense so far. It
strikes me that a good way to use it would be for small components, or as a low
level primitive for a front end framework.&lt;/p&gt;
&lt;p&gt;Finally, a note on compatibility. At the time of writing Chrome and Safari
support custom elements with no additional effort. Other browsers must be
&lt;a href&#x3D;&quot;https://github.com/webcomponents/custom-elements&quot;&gt;polyfilled&lt;/a&gt; for support.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/tip-arrayfrom</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/tip-arrayfrom"/>
    <title>Tip: Array.from</title>
    <published>2017-12-01T20:00:00Z</published>
    <updated>2017-12-01T20:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;SPOILER ALERT: If you&amp;#39;re doing the advent of code this year, you may not want to
read onward. This post &lt;em&gt;does not&lt;/em&gt; give any solutions away, but does contain
information about how I approached a part of the first challenge.&lt;/p&gt;
&lt;p&gt;Onward!&lt;/p&gt;
&lt;p&gt;Today I started doing the &lt;a href&#x3D;&quot;https://adventofcode.com&quot;&gt;2017 advent of code&lt;/a&gt; challenges (don&amp;#39;t worry, I
won&amp;#39;t give solutions away). The first challenge involves summing numbers in a
sequence. The sequence itself is too long to express as an integer, and that
wouldn&amp;#39;t be particularly useful for the task anyway. The most convenient form
is probably an array of integers. The sequence is given as raw text, and the
quickest way to get it into JS is as a string.&lt;/p&gt;
&lt;p&gt;There are a few ways to do this which spring to mind. One might argue that you
can loop directly over a string anyway, but getting a list of integers avoids
parsing each character in the string more than once for these tasks, so let&amp;#39;s
ignore that option.&lt;/p&gt;
&lt;p&gt;The first thing to come to mind was a loop:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; numbers &#x3D; [];

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; i &#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;; i &amp;lt; sequence.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;length&lt;/span&gt;; i++) {
  numbers.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;parseInt&lt;/span&gt;(sequence[i], &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;#39;s a slightly nicer to read version of this introduced by ES2015:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; numbers &#x3D; [];

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; character &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; sequence) {
  numbers.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;parseInt&lt;/span&gt;(character, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both suffer from being a bit bulky though. ES2015 also introduced
&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from&quot;&gt;&lt;code&gt;Array.from&lt;/code&gt;&lt;/a&gt; and the &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator&quot;&gt;spread operator (&lt;code&gt;...&lt;/code&gt;)&lt;/a&gt;, which offer a similar
way of creating an array when used in combination with &lt;code&gt;Array.prototype.map&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; numbers &#x3D; [...sequence].&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;char&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;parseInt&lt;/span&gt;(char, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;));
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// or&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; numbers &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;(sequence).&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;char&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;parseInt&lt;/span&gt;(char, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first spreads characters from the sequence into an array, and maps these
characters to a fresh array of numbers. The second uses &lt;code&gt;Array.from&lt;/code&gt; to get an
array of characters from a string, and uses the same mapping to get an array
of integers.&lt;/p&gt;
&lt;p&gt;I prefer the latter of the two (it&amp;#39;s a little easier for humans to parse). It
would be nice to avoid the explicit mapping though, right? The solution I
settled upon was to use the little-known second parameter to &lt;code&gt;Array.from&lt;/code&gt;. It
turns out you can include a function to map elements to be inserted into the
returned array:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; numbers &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;(sequence, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;char&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;parseInt&lt;/span&gt;(char, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;10&lt;/span&gt;));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which is my tip! In essence, don&amp;#39;t do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;[...stuff].&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(func);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;(stuff).&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(func);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;(stuff, func);
&lt;/code&gt;&lt;/pre&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/advent-of-code-2017-day-20-task-2</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/advent-of-code-2017-day-20-task-2"/>
    <title>Advent of Code 2017 day 20 task 2</title>
    <published>2018-01-02T20:50:00Z</published>
    <updated>2018-01-02T20:50:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;SPOILER ALERT: If you&amp;#39;re doing the 2017 Advent of Code, you may not want to read
onward.&lt;/p&gt;
&lt;p&gt;Over the holiday period I found a little time to work on the
&lt;a href&#x3D;&quot;https://adventofcode.com&quot;&gt;2017 advent of code&lt;/a&gt; challenges. The later ones get quite involved, and I
particularly enjoyed day 20. The second part of the task involves a 3D grid
containing accelerating particles. Particles which collide at a grid point
(occupy that point at the same time) are removed, and we&amp;#39;re asked to determine
how many particles remain after all collisions have occurred. This one was
fun for me because the solution I came up with involved a little mathematics to
avoid brute forcing the answer.&lt;/p&gt;
&lt;p&gt;At this point in the challenges, writing a parser for an input file is something
I&amp;#39;ll skip over. The parser gives back an array of objects, each representing a
particle, with &lt;math&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;/math&gt;, &lt;math&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;/math&gt;, and &lt;math&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/math&gt; fields to represent position, velocity, and
acceleration respectively. These quantities are all represented as length three
arrays for the &lt;math&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/math&gt;, &lt;math&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/math&gt;, and &lt;math&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;/math&gt; axes. The input looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-json&quot;&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;p&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;-4897&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;3080&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2133&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;-58&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-15&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-78&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;17&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-7&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;p&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;395&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-997&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;4914&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;-30&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;66&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-69&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;-2&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;-8&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;p&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;-334&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-754&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-567&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;-31&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;15&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;-34&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;3&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-number&quot;&gt;4&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The general algorithm I used was to calculate a collision times for each pair
of particles, order them by time ascending, and then remove all pairs for
each time step when both colliding particles are still in the set of all
particles up to that time (earlier collisions can invalidate later collisions
since collisions remove colliding particles).&lt;/p&gt;
&lt;p&gt;To calculate potential collisions, I used the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; collisions &#x3D; [];

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; i &#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;; i &amp;lt; input.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;length&lt;/span&gt; - &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;; i++) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; j &#x3D; i + &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;; j &amp;lt; input[i].&lt;span class&#x3D;&quot;hljs-property&quot;&gt;length&lt;/span&gt;; j++) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; t &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;collisionTime&lt;/span&gt;(input[i], input[j]);

        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (t !&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;null&lt;/span&gt;) {
            &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!collisions[t]) {
                collisions[t] &#x3D; [];
            }

            collisions[t].&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;([i, j]);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;ve used the index of each particle in the input array as its ID for
convenience. The snippet grabs all pairs and uses the function &lt;code&gt;collisionTime&lt;/code&gt;,
which returns either an integer (time of their first collision from &lt;math&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;/math&gt;), or
&lt;code&gt;null&lt;/code&gt; if they never collide. The &lt;code&gt;collisions&lt;/code&gt; array is indexed by time step,
and populated with arrays of collisions. I&amp;#39;ll come back to &lt;code&gt;collisionTime&lt;/code&gt;
later, because that&amp;#39;s where interesting things happen.&lt;/p&gt;
&lt;p&gt;Next I create a set containing the IDs of all particles.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; particles &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;({ &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;length&lt;/span&gt;: input.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;length&lt;/span&gt; }, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;v, i&lt;/span&gt;) &#x3D;&amp;gt;&lt;/span&gt; i));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;See &lt;a href&#x3D;&quot;/blog/tip-arrayfrom&quot;&gt;my &lt;code&gt;Array.from&lt;/code&gt; tip&lt;/a&gt; for a partial explanation of this, and the
&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set&quot;&gt;MDN article on &lt;code&gt;Set&lt;/code&gt;&lt;/a&gt; for the rest.&lt;/p&gt;
&lt;p&gt;The rest is looping over collision times to discover which collisions are valid
(both particles have not yet collided) and remove the particles which collide
from the particles set. The size of the set after all collisions have been
checked is the number of remaining particles, which is the goal of this problem.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; collisionsAtTime &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; collisions) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!collisionsAtTime) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;continue&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; toDelete &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;();

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; [a, b] &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; collisionsAtTime) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (particles.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(a) &amp;amp;&amp;amp; particles.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;has&lt;/span&gt;(b)) {
            toDelete.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(a);
            toDelete.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;add&lt;/span&gt;(b);
        }
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; index &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; toDelete) {
        particles.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;delete&lt;/span&gt;(index);
    }
}

&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(particles.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;size&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I don&amp;#39;t care about the time of each set of collisions; I only care about the
order. Since &lt;code&gt;collisions&lt;/code&gt; is a sparse array (indexed by time step), I do have to
check for &lt;code&gt;undefined&lt;/code&gt; elements and &lt;code&gt;continue&lt;/code&gt; to skip those.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve modelled all collisions as pairs, so a collision between three or more
particles will look like multiple collisions of pairs at the same time. In order
to properly count these, particle IDs to be removed are held in the &lt;code&gt;toDelete&lt;/code&gt;
set and removed after all collisions at a given time step have been checked.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s go back to the function &lt;code&gt;collisionTime&lt;/code&gt;. For a given pair of particle
objects, it determines when their first collision is beginning at &lt;math&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;/math&gt;. Here&amp;#39;s
a naïve implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Calculates the Manhatten distance between two particles.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;manhatten&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;abs&lt;/span&gt;(a[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;] - b[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;]) + &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;abs&lt;/span&gt;(a[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;] - b[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;]) + &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;abs&lt;/span&gt;(a[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;] - b[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;]);
}

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;vectorAdd&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; [a[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;] + b[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;], a[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;] + b[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;], a[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;] + b[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;]];
}

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Returns a new particle which represents the evolution of a given particle by&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// one time step.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;tick&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;particle&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; a &#x3D; particle.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;a&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;slice&lt;/span&gt;();
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; v &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;vectorAdd&lt;/span&gt;(particle.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;v&lt;/span&gt;, a);
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; p &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;vectorAdd&lt;/span&gt;(particle.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;p&lt;/span&gt;, v);

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; { p, v, a };
}

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;collisionTime&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; nextD &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;manhatten&lt;/span&gt;(a.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;p&lt;/span&gt;, b.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;p&lt;/span&gt;);

    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Trivial case in which particles are initially in the same place.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (nextD &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
    }

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; t &#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; d;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; v;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; nextV &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;manhatten&lt;/span&gt;(a.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;v&lt;/span&gt;, b.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;v&lt;/span&gt;);
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; particle0 &#x3D; a;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; particle1 &#x3D; b;

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt; {
        d &#x3D; nextD;
        v &#x3D; nextV;
        particle0 &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;tick&lt;/span&gt;(particle0);
        particle1 &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;tick&lt;/span&gt;(particle1);
        t++;
        nextD &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;manhatten&lt;/span&gt;(particle0.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;p&lt;/span&gt;, particle1.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;p&lt;/span&gt;);

        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (nextD &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
            &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; t;
        }

        nextV &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;manhatten&lt;/span&gt;(particle0.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;v&lt;/span&gt;, particle1.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;v&lt;/span&gt;);

        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Continue while the particles are moving together, or failing that,&lt;/span&gt;
        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// the rate at which they&amp;#x27;re moving apart is slowing (indicating that&lt;/span&gt;
        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// they will move toward each other in the future).&lt;/span&gt;
    } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; (nextD &amp;lt; d || nextV &amp;lt; v);

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;null&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This implementation takes a pair of particles, and evolves both while either
they are approaching each other, or accelerating toward each other. If the two
particles are at zero distance during the evolution, they have collided. It
works, but it&amp;#39;s slow.&lt;/p&gt;
&lt;p&gt;All I really need is the location of each particle at a given time. Since the
particles move under a very simple set of rules, I &lt;em&gt;should&lt;/em&gt; be able to predict
where a particle will be at any time without needing to resort to loops. If I
can predict the location of two particles, I can calculate the relative location
of one particle with respect to another, and ultimately I can solve an equation
for which that location is the same (zero distance).&lt;/p&gt;
&lt;p&gt;First I have to predict the location of a particle though. We are told that at
each time, the acceleration vector is added to the velocity vector, and then the
updated velocity vector is added to the position vector. The position of a
particle is thus (in one dimension, where zeros denote initial values):&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-1&quot; aria-label&#x3D;&quot;p_t &#x3D; p_0 + v_0 t + a\sum_{n&#x3D;0}^t n&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mrow&gt;&lt;munderover&gt;&lt;mo movablelimits&#x3D;&quot;false&quot;&gt;∑&lt;/mo&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/munderover&gt;&lt;/mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;p_t &#x3D; p_0 + v_0 t + a\sum_{n&#x3D;0}^t n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;That sum can be swapped out for an expression (high schoolers will know this but
I, with the full weight of two degrees in physics, had forgotten and had to look
it up). This is equivalent:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-2&quot; aria-label&#x3D;&quot;p_t &#x3D; p_0 + v_0 t + a\frac{(t + 1)t}{2}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;p_t &#x3D; p_0 + v_0 t + a\frac{(t + 1)t}{2}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;Armed with this equation, I need to calculate the difference in position
between two particles:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-3&quot; aria-label&#x3D;&quot;\Delta p_t &#x3D; \Delta p_0 + \Delta v_0 t + \Delta a\frac{(t + 1)t}{2}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;\Delta p_t &#x3D; \Delta p_0 + \Delta v_0 t + \Delta a\frac{(t + 1)t}{2}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;Where the delta signifies a difference (the above is the equation of one
particle subtracted from the equation for a another particle). The solutions for
&lt;math&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/math&gt; I want are when the left hand side (difference in position at time step &lt;math&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/math&gt;)
is zero. To avoid a division and retain integers as long as possible, I multiply
both sides by two and rewrite slightly to collect powers of &lt;math&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/math&gt;.&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-4&quot; aria-label&#x3D;&quot;0 &#x3D; \Delta a t^2 + \left(2\Delta v_0 + \Delta a\right) t + 2\Delta p_0&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;msup&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mn class&#x3D;&quot;tml-med-pad&quot;&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;true&quot;&gt;(&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;true&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;0 &#x3D; \Delta a t^2 + \left(2\Delta v_0 + \Delta a\right) t + 2\Delta p_0&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;This is a &lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/Quadratic_equation&quot;&gt;quadratic equation&lt;/a&gt;! When the acceleration is zero this collapses
down to a linear equation with the solution:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-5&quot; aria-label&#x3D;&quot;t &#x3D; -\frac{\Delta p_0}{\Delta v_0}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mo form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;−&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;t &#x3D; -\frac{\Delta p_0}{\Delta v_0}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;When acceleration is non-zero, I have to solve a quadratic equation:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-6&quot; aria-label&#x3D;&quot;t &#x3D; \frac{-b\pm\sqrt{b^2-4ac}}{2a}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mo form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot; lspace&#x3D;&quot;0em&quot; rspace&#x3D;&quot;0em&quot;&gt;−&lt;/mo&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mo&gt;±&lt;/mo&gt;&lt;msqrt&gt;&lt;mrow&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;4&lt;/mn&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;/mrow&gt;&lt;/msqrt&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;t &#x3D; \frac{-b\pm\sqrt{b^2-4ac}}{2a}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;where&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-7&quot; aria-label&#x3D;&quot;\begin{align*}
a &amp;amp;&#x3D; \Delta a \\
b &amp;amp;&#x3D; 2\Delta v_0 + \Delta a \\
c &amp;amp;&#x3D; 2\Delta p_0
\end{align*}&quot;&gt;&lt;semantics&gt;&lt;mtable displaystyle&#x3D;&quot;true&quot; class&#x3D;&quot;tml-jot&quot;&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;msub&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;/mtable&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;\begin{align*}
a &amp;amp;&#x3D; \Delta a \\
b &amp;amp;&#x3D; 2\Delta v_0 + \Delta a \\
c &amp;amp;&#x3D; 2\Delta p_0
\end{align*}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;I&amp;#39;m interested in solutions which are &lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/Natural_number&quot;&gt;natural numbers&lt;/a&gt; (real positive
integers). To get natural number solutions to quadratic equations, I wrote these
functions:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;isNaturalNumber&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;num&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; num &amp;gt;&amp;gt;&amp;gt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt; &#x3D;&#x3D;&#x3D; num;
}

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Provides only solutions which are natural numbers.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;solveQuadratic&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b, c&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (a &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; solution &#x3D; -c / b;

    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;isNaturalNumber&lt;/span&gt;(solution) ? [solution] : [];
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; rootDiscriminant &#x3D; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;sqrt&lt;/span&gt;(b * b - &lt;span class&#x3D;&quot;hljs-number&quot;&gt;4&lt;/span&gt; * a * c);

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; [
    (-b + rootDiscriminant) / (&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; * a),
    (-b - rootDiscriminant) / (&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; * a)
  ].&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter&lt;/span&gt;(isNaturalNumber);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The function &lt;code&gt;isNaturalNumber&lt;/code&gt; uses the right bit shift operator. This is a
trick to get an integer out of a number. Positive numbers will be floored,
negative numbers will overflow (become large positive integers), and &lt;code&gt;NaN&lt;/code&gt; will
become zero. Only positive integers will pass the strict equality check.
&lt;code&gt;solveQuadratic&lt;/code&gt; itself encodes the solution to both the linear case
(acceleration of zero) and the quadratic case. JavaScript is on our side here
with how it represents numbers.&lt;/p&gt;
&lt;p&gt;This solution is in one dimension. For collisions in three dimensions, this
equation must be solved for each, and only when all three dimensions have a
solution which is the same can I say that the two particles are projected to
collide. At last, here is the improved implementation of &lt;code&gt;collisionTime&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;collisionTime&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;a, b&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; commonSolutions;

    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Calculate the solutions for each axis and initialise or whittle down&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// commonSolutions.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; i &#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;; i &amp;lt; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;3&lt;/span&gt;; i++) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; dp &#x3D; a.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;p&lt;/span&gt;[i] - b.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;p&lt;/span&gt;[i];
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; dv &#x3D; a.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;v&lt;/span&gt;[i] - b.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;v&lt;/span&gt;[i];
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; da &#x3D; a.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;a&lt;/span&gt;[i] - b.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;a&lt;/span&gt;[i];

        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; axisSolutions &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;solveQuadratic&lt;/span&gt;(da, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; * dv + da, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; * dp);

        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Most of the time there are no solutions, so this is a good&lt;/span&gt;
        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// optimisation.&lt;/span&gt;
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (axisSolutions.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;length&lt;/span&gt; &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
          &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;null&lt;/span&gt;;
        }

        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!commonSolutions) {
            &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Initialise the set with solutions for the first axis.&lt;/span&gt;
            commonSolutions &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Set&lt;/span&gt;(axisSolutions);
        } &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {
            &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Remove solutions for other axes which this axis does&amp;#x27;t have.&lt;/span&gt;
            &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; solution &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;of&lt;/span&gt; commonSolutions) {
                &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!axisSolutions.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;includes&lt;/span&gt;(solution)) {
                    commonSolutions.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;delete&lt;/span&gt;(solution);
                }
            }
        }

        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// If the set of common solutions is ever empty, there will be no&lt;/span&gt;
        &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// collision.&lt;/span&gt;
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (commonSolutions.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;size&lt;/span&gt; &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;) {
            &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;null&lt;/span&gt;;
        }
    }

    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// I only want the earliest collision.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;min&lt;/span&gt;(...commonSolutions);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function first calculates solutions for the x-axis. Solutions which are not
in common with both the y-axis and z-axis are subsequently removed. If the set
of common solutions is ever empty, the function returns &lt;code&gt;null&lt;/code&gt;. If any axis has
no solutions I can take a shortcut and immediately return &lt;code&gt;null&lt;/code&gt;. If the set has
remaining entries, these represent times when the particles will occupy the same
coordinates in all three dimensions (a collision). The lowest (earliest) is
plucked out as the solution!&lt;/p&gt;
&lt;p&gt;On my machine, the naïve implementation takes ~3.2 seconds with the input I&amp;#39;m
given. With the improved implementation, it takes less than 0.1 seconds. The
full solution can be found &lt;a href&#x3D;&quot;https://gist.github.com/qubyte/d43432e0e1716bc5efca4426ee762071&quot;&gt;in this gist&lt;/a&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/essential-tools-for-javascript-beginners</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/essential-tools-for-javascript-beginners"/>
    <title>Essential tools for JavaScript beginners</title>
    <published>2018-02-11T17:00:00Z</published>
    <updated>2018-02-11T17:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’ve noticed when helping people to learn JS is that I’m happy to let them learn
without any tools. In hindsight this is very strange. I wouldn’t dream of
programming like this! I make mistakes all the time, and tools help me to catch
them early. Tools also help me to streamline repetitive tasks.&lt;/p&gt;
&lt;p&gt;In this post I&amp;#39;ll talk a minimal set of tools common to all projects I use. This
consists of &lt;a href&#x3D;&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt;, &lt;a href&#x3D;&quot;https://nodejs.org/en/&quot;&gt;&lt;code&gt;Node.js&lt;/code&gt;&lt;/a&gt;, &lt;a href&#x3D;&quot;https://eslint.org/&quot;&gt;ESLint&lt;/a&gt;,
and &lt;a href&#x3D;&quot;http://editorconfig.org/&quot;&gt;Editorconfig files&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;visual-studio-code&quot;&gt;Visual Studio Code&lt;/h2&gt;
&lt;p&gt;There are many editors out there, and which one you use is a personal
preference. Consider this section optional, but recommended.&lt;/p&gt;
&lt;p&gt;&lt;a href&#x3D;&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; is my current editor of choice. It integrates well with tools,
feels uncluttered, and doesn’t lag. It&amp;#39;s useful out of the box. Many editors can
integrate with tools, but none quite as nicely. I recommend installing it and
using its extensions system to install the ESLint extension and the Editorconfig
extension (more on this in the sections below). The installation of ESLint into
your project itself is covered in a later section.&lt;/p&gt;
&lt;p&gt;Tip: Open the project directory in VS Code, not just a single file. It’ll
present you with a file explorer on the left so you can quickly open other files
in your project without leaving the editor. You’ll also be able to see files
which are usually hidden.&lt;/p&gt;
&lt;p&gt;Tip: You can use VS Code to view git diffs, compose git commits, and push and
pull from a repository. If you prefer not to use git from the terminal, you may
find VS Code good enough most of the time.&lt;/p&gt;
&lt;p&gt;Tip: If you do want to use git (or npm etc.) from the terminal, you can open a
terminal a panel in VS Code. From the view menu, select &amp;quot;Integrated Terminal&amp;quot;.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;nodejs&quot;&gt;Node.js&lt;/h2&gt;
&lt;p&gt;&lt;a href&#x3D;&quot;https://nodejs.org/en/&quot;&gt;Node&lt;/a&gt; is a JavaScript environment without the browser. It is often used
to write servers (most of my day job), but that’s not what we need it for here.&lt;/p&gt;
&lt;p&gt;All modern JavaScript tools are built to be executed by Node and installable
with &lt;a href&#x3D;&quot;https://www.npmjs.com/&quot;&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/a&gt;, the JavaScript package manager, and executable with
&lt;a href&#x3D;&quot;https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b&quot;&gt;&lt;code&gt;npx&lt;/code&gt;&lt;/a&gt;, the JavaScript package runner, which are both installed along with
Node.&lt;/p&gt;
&lt;p&gt;With Node installed, when you are starting a new project or want to add packages
or tooling to an existing one, open a terminal (on a Mac or Linux machine, or on
windows the Node.js prompt) and navigate to the new project directory (make a
new directory if you need to). When you&amp;#39;re in there, run&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-shell&quot;&gt;npm init
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and answer the questions. You can use the defaults for most of the fields. This
initialises your project with a &lt;a href&#x3D;&quot;https://docs.npmjs.com/files/package.json&quot;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt; file. This file
is very powerful because it can be used to describe your project and its
dependencies (other libraries and tools it requires). If you&amp;#39;re unsure, run&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-shell&quot;&gt;npm help init
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;for some helpful information.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;eslint&quot;&gt;ESLint&lt;/h2&gt;
&lt;p&gt;The most important tool (after the editor) I use is &lt;a href&#x3D;&quot;https://eslint.org/&quot;&gt;ESLint&lt;/a&gt;. It scans
your code, and tries to tell you when you’ve made a mistake or done something
you probably didn’t intend. ESLint can also be used to enforce style. Enforcing
style might seem dictatorial, but consistent code is beautiful, and it helps a
great deal when collaborating. Install with ESLint and a configuration with:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-shell&quot;&gt;npm install —-save-dev eslint eslint-config-qubyte
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;m suggesting my own ESLint config here, but there are lots of them to be
&lt;a href&#x3D;&quot;https://www.npmjs.com/browse/keyword/eslintconfig&quot;&gt;found on npm&lt;/a&gt;. You can create your own too!&lt;/p&gt;
&lt;p&gt;When the command has finished, take a look in &lt;code&gt;package.json&lt;/code&gt;. You’ll see a
&lt;code&gt;devDependencies&lt;/code&gt; section with the installed packages and version numbers.
You’ll also see a new folder called &lt;code&gt;node_modules&lt;/code&gt;. If you delete
&lt;code&gt;node_modules&lt;/code&gt;, you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-shell&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and npm will read &lt;code&gt;package.json&lt;/code&gt; to know what to install. There may also be a
&lt;code&gt;package-lock.json&lt;/code&gt;. This lock file makes sure that exactly the same version of
dependencies are installed each time. This is useful for sharing your project
without having to include all the dependencies (which can easy amount to
megabytes). If you&amp;#39;re using git, add &lt;code&gt;node_modules&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file so
that git ignores that directory.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;devDependencies&lt;/code&gt; section is for packages used in development, so it&amp;#39;s the
natural place for tools like ESLint. the &lt;code&gt;--save-dev&lt;/code&gt; flag of &lt;code&gt;npm install&lt;/code&gt;
tells &lt;code&gt;npm&lt;/code&gt; to install as a development dependency.&lt;/p&gt;
&lt;p&gt;With ESLint and an ESLint config installed, ESLint it must be told to use the
config. Create a file in your project directory called &lt;code&gt;.eslintrc.json&lt;/code&gt; (the
leading &lt;code&gt;.&lt;/code&gt; is important) and put the following into it:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-json&quot;&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;extends&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;qubyte&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;browser&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This minimal configuration tells ESLint that you&amp;#39;re &amp;quot;extending&amp;quot; the config from
&lt;code&gt;eslint-config-qubyte&lt;/code&gt;. You can find out more about configuring ESLint
&lt;a href&#x3D;&quot;https://eslint.org/docs/user-guide/configuring&quot;&gt;here&lt;/a&gt;. If you&amp;#39;re using newer JavaScript features, try setting
&lt;code&gt;extends&lt;/code&gt; to &lt;code&gt;&amp;quot;qubyte/ES2017&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;VSCode has an &amp;quot;extension&amp;quot; for ESLint. On the left of a VS code window there are
several icons. Click on the one for extensions (hover to see) and search for
&amp;quot;ESLint&amp;quot; to find it, and install. From the view menu, click on &amp;quot;Problems&amp;quot; to
open a panel which shows you problems ESLint finds.&lt;/p&gt;
&lt;p&gt;If you want to run ESLint from the terminal, navigate to your project directory
and run&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-shell&quot;&gt;npx eslint .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note the trailing dot, and that the command here is
&lt;a href&#x3D;&quot;https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b&quot;&gt;&lt;code&gt;npx&lt;/code&gt;&lt;/a&gt; not &lt;code&gt;npm&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;editorconfig&quot;&gt;Editorconfig&lt;/h2&gt;
&lt;p&gt;&lt;a href&#x3D;&quot;http://editorconfig.org/&quot;&gt;Editorconfig&lt;/a&gt; is not really a tool. It’s a standard for a file
which describes how code should look in each code file. Make a file called
&lt;code&gt;.editorconfig&lt;/code&gt; (all lower case with a leading &lt;code&gt;.&lt;/code&gt; as the first character in the
name). Put the following text into it and save:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-toml&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Apply settings to all text file&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# types.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-section&quot;&gt;[*]&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Windows likes to choose weird&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# encodings sometimes. Use this to make&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# sure that wherever the file is saved,&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# it&amp;#x27;s in the expected encoding.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;charset&lt;/span&gt; &#x3D; utf-&lt;span class&#x3D;&quot;hljs-number&quot;&gt;8&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Use unix line endings everywhere for&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# similar reasons as the charset above.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;end_of_line&lt;/span&gt; &#x3D; lf

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Wise to end on a new line character&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# for unix.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;insert_final_newline&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Indent using two spaces at a time.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;indent_style&lt;/span&gt; &#x3D; space
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;indent_size&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Trims redundant spaces from the end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# of lines.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;trim_trailing_whitespace&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tip: If you prefer another editor, Editorconfig has broad support. It&amp;#39;s always
worth having this file in your projects.&lt;/p&gt;
&lt;p&gt;VS Code has an extension for Editorconfig files. To install it, open
&amp;quot;Extensions&amp;quot; via the icon on the left or via the view menu, and search for
&amp;quot;Editorconfig&amp;quot;. The most downloaded (probably the top hit) is the one you want.
Once this is installed, there&amp;#39;s nothing left to do. VSCode will honour
Editorconfig files whenever it finds them. It does this by applying Editorconfig
rules to a file when it is saved.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;finally&quot;&gt;Finally&lt;/h2&gt;
&lt;p&gt;There are lots of tools out there. If you have a mentor they may have some
suggestions! These are the essential tools I use as a career programmer though.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/about-this-blog-3</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/about-this-blog-3"/>
    <title>About this blog 3</title>
    <published>2018-02-17T02:40:00Z</published>
    <updated>2018-02-17T02:40:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It&amp;#39;s been a while since &lt;a href&#x3D;&quot;/blog/about-this-blog-2&quot;&gt;the last entry&lt;/a&gt; about how I&amp;#39;ve
built this blog. Since it is constantly evolving, now seems as good a time as
ever to write about some of the changes I&amp;#39;ve made.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;netlify&quot;&gt;Netlify&lt;/h3&gt;
&lt;p&gt;The biggest shift has been the move from a DigitalOcean droplet, with a linux
install managed by me, to &lt;a href&#x3D;&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;. The service they
provides hooks into GitHub, and builds when new things are pushed. It also lets
me try new things out on hard-to-guess subdomains by opening pull requests
(useful for trying things out which might otherwise be risky, like updated
security headers).&lt;/p&gt;
&lt;h3 id&#x3D;&quot;webmentions&quot;&gt;Webmentions&lt;/h3&gt;
&lt;p&gt;I&amp;#39;ve said before that I&amp;#39;m not a fan of comments. They&amp;#39;re complex to manage and
open to abuse. However, I do like the IndieWeb convention of using
&lt;a href&#x3D;&quot;https://indieweb.org/Webmention&quot;&gt;webmentions&lt;/a&gt; from site a to site b to notify b that a links to it.
I use a Netlify deploy hook to &lt;a href&#x3D;&quot;https://glitch.com/edit/#!/send-webmentions&quot;&gt;call a glitch&lt;/a&gt;. This little
service scans the updated blog for new links to other sites, and sends those
sites webmentions. This is currently disabled and I&amp;#39;m monitoring the logs to
make sure it doesn&amp;#39;t do anything unintentional. It&amp;#39;ll probably go active this
weekend once I&amp;#39;ve enabled signature checking.&lt;/p&gt;
&lt;p&gt;On the receiving side, my blog has a webmention form which Netlify provides a
simple backend to. I can act upon each web mention from there. This form also
allows me to include a manual webmention form at the bottom of each blog post.
Other blogs can discover the form by looking for a link tag with
&lt;code&gt;rel&#x3D;&amp;quot;webmention&amp;quot;&lt;/code&gt;, which is found in the head:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;href&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/webmention&amp;quot;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;webmention&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since I don&amp;#39;t expect a large volume of mentions, I&amp;#39;ll add each to the
front matter of the blog post being mentioned. When a post has mentions, a
section listing them is added at the end of the post.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;payments&quot;&gt;Payments&lt;/h3&gt;
&lt;p&gt;A nice CLI utility called &lt;a href&#x3D;&quot;https://feross.org/introducing-thanks/&quot;&gt;&lt;code&gt;thanks&lt;/code&gt;&lt;/a&gt; was recently published. It scans
your project for all the modules it uses and lists, in order of most relied
upon, collectives and authors along with their payment links. At the time of
writing, it contains a little database of module authors and collectives, and
links to ways to pay them.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s an obvious scaling problem which is addressed in
&lt;a href&#x3D;&quot;https://github.com/feross/thanks/issues/2&quot;&gt;this issue&lt;/a&gt;. The issue suggests a new field in the
&lt;code&gt;package.json&lt;/code&gt; file of a module which thanks can discover, shifting the burden
onto the module authors.&lt;/p&gt;
&lt;p&gt;Since I&amp;#39;m keen on IndieWeb stuff, the issue led me to wonder if there is an
IndieWeb &lt;code&gt;rel&lt;/code&gt; attribute for payments (much like that used for discovering
webmention endpoints). It turns out that there is and it&amp;#39;s &lt;code&gt;rel&#x3D;&amp;quot;payment&amp;quot;&lt;/code&gt;. It
doesn&amp;#39;t see too much use in the wild, and it should not be the primary means of
discovering payment methods by &lt;code&gt;thanks&lt;/code&gt;, but as a fallback it could be useful.
&lt;code&gt;thanks&lt;/code&gt; would check for payment links on the project home page (from the
&lt;code&gt;homepage&lt;/code&gt; property of the &lt;code&gt;package.json&lt;/code&gt; file), followed by author URLs if
provided.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve added a link tag with this attribute to the blog as well. I don&amp;#39;t expect it
to get used, but one can always hope! Perhaps I&amp;#39;ll become a professional
blogger.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;better-links&quot;&gt;Better links&lt;/h3&gt;
&lt;p&gt;I write my posts in markdown, and use &lt;a href&#x3D;&quot;https://www.npmjs.com/package/marked&quot;&gt;marked&lt;/a&gt; to process them. Marked
doesn&amp;#39;t know which anchors will link out to external domains, so it compiles
then to &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags with only an &lt;code&gt;href&lt;/code&gt; attribute. When linking to external
sources, the &lt;a href&#x3D;&quot;https://jakearchibald.com/2016/performance-benefits-of-rel-noopener/&quot;&gt;current best practice&lt;/a&gt; is to add a &lt;code&gt;rel&#x3D;&amp;quot;noopener&amp;quot;&lt;/code&gt;
attribute. To do this, I&amp;#39;ve slightly customised the anchor renderer of marked to
add these in:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; marked &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;marked&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; urlResolve &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;url&amp;#x27;&lt;/span&gt;).&lt;span class&#x3D;&quot;hljs-property&quot;&gt;resolve&lt;/span&gt;;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; renderer &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; marked.&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Renderer&lt;/span&gt;();
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; oldLinkRenderer &#x3D; renderer.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;link&lt;/span&gt;;

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// In the actual code, this is fed in to a function, not a module-scoped const.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; baseUrl &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;https://qubyte.codes&amp;#x27;&lt;/span&gt;;

renderer.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;link&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-function&quot;&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;href, title, text&lt;/span&gt;) &#x3D;&amp;gt;&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; fullyQualified &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;urlResolve&lt;/span&gt;(baseUrl, href);
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; rendered &#x3D; oldLinkRenderer.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;call&lt;/span&gt;(renderer, href, title, text);

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (fullyQualified.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;startsWith&lt;/span&gt;(baseUrl)) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; rendered;
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; rendered.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;replace&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;&amp;lt;a &amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;&amp;lt;a target&#x3D;&amp;quot;_blank&amp;quot; rel&#x3D;&amp;quot;noopener&amp;quot; &amp;#x27;&lt;/span&gt;);
};

marked.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setOptions&lt;/span&gt;({
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Some unrelated stuff skipped.&lt;/span&gt;
  renderer
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;#39;s a hack for sure, but it works!&lt;/p&gt;
&lt;h3 id&#x3D;&quot;mathematics&quot;&gt;Mathematics&lt;/h3&gt;
&lt;p&gt;Toward the end of last year, I &lt;a href&#x3D;&quot;/blog/advent-of-code-2017-day-20-task-2&quot;&gt;wrote a post&lt;/a&gt; that required some
typeset mathematics. I used to write a lot in LaTeX, and so I already knew of
&lt;a href&#x3D;&quot;https://www.mathjax.org/&quot;&gt;MathJax&lt;/a&gt; (and was saddened to find that MathML in the browser failed
to catch on). I knew I needed to convince marked to use it, but marked currently
lacks extensibility so I couldn&amp;#39;t define new blocks. Instead I adjusted the
behaviour of code blocks to treat blocks labelled as &lt;code&gt;mathematics&lt;/code&gt; differently:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; mathjax &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;mathjax-node&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; xml2js &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;xml2js&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; marked &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;marked&amp;#x27;&lt;/span&gt;);
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; highlight &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;highlight.js&amp;#x27;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; renderer &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; marked.&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Renderer&lt;/span&gt;();
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; codeRenderer &#x3D; renderer.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;code&lt;/span&gt;;

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// This function is synchronous, so I couldn&amp;#x27;t call MathJax&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// in here (it&amp;#x27;s async), and must do in the highlight method.&lt;/span&gt;
renderer.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;code&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-params&quot;&gt;code, lang, escaped&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (lang &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;mathematics&amp;#x27;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; code;
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; codeRenderer.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;call&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;, code, lang, escaped);
};

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;highlight&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;code, language, callback&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Non-mathematics code is syntax highlighted.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (language !&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;mathematics&amp;#x27;&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;callback&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, highlight.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;highlight&lt;/span&gt;(language, code).&lt;span class&#x3D;&quot;hljs-property&quot;&gt;value&lt;/span&gt;);
  }

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Render with MathJax.&lt;/span&gt;
  mathjax.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;typeset&lt;/span&gt;({ &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;math&lt;/span&gt;: code, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;format&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;TeX&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;svg&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt; }, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;{ errors, svg }&lt;/span&gt;) &#x3D;&amp;gt;&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (errors) {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;callback&lt;/span&gt;(errors);
    }

    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// MathJax puts some unnecessary inline style in the output&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// which my server response headers disagree with. The below&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// strips those out, and adds a &amp;quot;mathematics&amp;quot; classname to&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// the resultant SVG for styling.&lt;/span&gt;
    xml2js.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;parseString&lt;/span&gt;(rendered, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;err, xmlobj&lt;/span&gt;) &#x3D;&amp;gt;&lt;/span&gt; {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (err) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;callback&lt;/span&gt;(err);
      }

      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;delete&lt;/span&gt; xmlobj.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;svg&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;$&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;style&lt;/span&gt;;

      xmlobj.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;svg&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;$&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;class&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;mathematics&amp;#x27;&lt;/span&gt;;

      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; builder &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; xml2js.&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Builder&lt;/span&gt;({ &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;headless&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt; });

      &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;callback&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, builder.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;buildObject&lt;/span&gt;(xmlobj));
    });
  });
},

marked.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setOptions&lt;/span&gt;({
  highlight,
  renderer
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;m very happy with how it turned out, but I do look forward to a more direct
means to add functionality to marked.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;not-a-progressive-web-app&quot;&gt;Not a progressive web app&lt;/h3&gt;
&lt;p&gt;For a while this blog had a service worker and an app manifest. You could even
&amp;quot;install&amp;quot; it on android phones. The caching strategy I was using was a bit wonky
though. Any time hashes changed (common when I updated CSS, since each update to
the CSS gets a new filename for caching reasons), a visit to any page would lead
to the entire blog being downloaded. In the end I came to the conclusion that
being a progressive web app was not good for readers bandwidth in this case and
the only reason for me to have it was vanity.&lt;/p&gt;
&lt;p&gt;A gutted service worker remains purely to clear the cache, so that a new script
can be loaded which will uninstall the service worker for good.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;refactoring&quot;&gt;Refactoring&lt;/h3&gt;
&lt;p&gt;Since the blog software (the static site generator) was built, async-await
became a feature of JavaScript. The generator has been substantially revised
to make use of promises and async-await, and is far more compact and readable
as a result. The code comes in at a little under 300 source lines, not counting
templates.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/putting-back-the-service-worker</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/putting-back-the-service-worker"/>
    <title>Putting back the service worker</title>
    <published>2018-03-08T01:20:00Z</published>
    <updated>2018-03-08T01:20:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;In the &lt;a href&#x3D;&quot;/blog/about-this-blog-3&quot;&gt;last post about this blog&lt;/a&gt; I wrote about why
I removed the service worker which made this blog a progressive web application.&lt;/p&gt;
&lt;p&gt;The way my blog handles CSS predates the wide availability of service workers.
Since CSS link tags are blocking, it&amp;#39;s good to give CSS a long cache time. In
order to do this, but still deliver fresh CSS without readers having to wait for
it, I give the CSS file a URI including a hash of its content. If the CSS
is updated, its URI changes, as does the &lt;code&gt;href&lt;/code&gt; of the CSS link tag in each page
of this blog.&lt;/p&gt;
&lt;p&gt;The server sends this header along with CSS it serves:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-properties&quot;&gt;&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;Cache-Control&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;max-age&#x3D;315360000, public, immutable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;immutable&lt;/code&gt; tells the browser the file will never change while within the
&lt;code&gt;max-age&lt;/code&gt; (so I make it very long). Chrome doesn&amp;#39;t support &lt;code&gt;immutable&lt;/code&gt;, but does
exhibit similar behaviour. The &lt;code&gt;public&lt;/code&gt; allows proxies to similarly cache the
response.&lt;/p&gt;
&lt;p&gt;I instructed the browser not to cache HTML at all. Since the HTML was always
fresh, updated CSS would be loaded at most once on each change. The server
sends this header along with HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-properties&quot;&gt;&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;Cache-Control&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;no-cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which forces it to make a fresh request for HTML each time. These headers are
still in place now (even since the move to Netlify).&lt;/p&gt;
&lt;p&gt;Unfortunately this didn&amp;#39;t mesh well with the caching strategy of the service
worker I added. The worker would download the entire blog, HTML and CSS, the
first time a user navigated to it. The service worker was generated by
&lt;a href&#x3D;&quot;https://www.npmjs.com/package/sw-precache&quot;&gt;sw-precache&lt;/a&gt;, which I had set up to be generated every time the
blog was updated. It worked out what to add and remove from its cache based on
file hashes. Since an update to CSS&lt;sup&gt;†&lt;/sup&gt; meant changing the address in a
link header in every HTML page, it removed not only the CSS, but all the HTML
any time the CSS changed. Worse, it proactively downloaded all the updated
files.&lt;/p&gt;
&lt;p&gt;The net effect was minor CSS changes triggering a mass download of my blog. This
wasn&amp;#39;t a good use of server or browser resources, so I removed the worker.&lt;/p&gt;
&lt;p&gt;I recently attended a &lt;a href&#x3D;&quot;https://indieweb.org/Homebrew_Website_Club&quot;&gt;Homebrew Website Club&lt;/a&gt; held by
Jeremy Keith. By chance he&amp;#39;d written &lt;a href&#x3D;&quot;https://adactio.com/journal/13540&quot;&gt;a blog post&lt;/a&gt; on this
issue the day before, in which he provides a &lt;em&gt;minimum viable service worker&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This service worker caches everything lazily (no precache). When an HTML page is
requested, it always tries the network first for a fresh copy, and falls back to
the cache when necessary. For everything else it hits the cache first, but gets
an update via the network in the background.&lt;/p&gt;
&lt;p&gt;This resolves the CSS issue very nicely. Old CSS and HTML will be cached, so if
your Southern Rail train is stuck in a tunnel, you can still read
&lt;a href&#x3D;&quot;/blog/test-friendly-mixins&quot;&gt;that blog entry about mixins&lt;/a&gt; you saw earlier and
are now bored enough to take another look at. If you&amp;#39;re stuck &lt;em&gt;outside&lt;/em&gt; of a
tunnel and I&amp;#39;ve updated the CSS&lt;sup&gt;‡&lt;/sup&gt;, the request for fresh HTML will
succeed, which will also bring in and cache the fresh CSS! For things like
images, which will probably never change, this also works well. If a change is
urgent to any file, it can still be given a fresh URL to cache bust it (though I
doubt this&amp;#39;ll ever be necessary).&lt;/p&gt;
&lt;p&gt;I&amp;#39;ll still need to do &lt;em&gt;some&lt;/em&gt; work though. As mentioned in that post, cleanup
isn&amp;#39;t addressed. I&amp;#39;m happy for HTML to be cached indefinitely, but for the
CSS one way to clean it up might be to remove it once it is older than every
HTML entry in the cache. For particularly large resources such as images, a
relatively short cache time and good alt-text might be a good approach...&lt;/p&gt;
&lt;p&gt;In the meantime, I&amp;#39;ve borrowed the service worker code from that post mostly
unchanged (most changes are just to align it with the style enforced by my
ESLint config). The one small addition is to allow server sent events
(&lt;code&gt;EventSource&lt;/code&gt;) connections. I use these in development to hot-reload my blog
when changes are made. The original script ignores all but GET requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (request.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;method&lt;/span&gt; !&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;GET&amp;#x27;&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;ve extended this to also ignore server sent event connections:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; acceptHeader &#x3D; request.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;headers&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Accept&amp;#x27;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (request.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;method&lt;/span&gt; !&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;GET&amp;#x27;&lt;/span&gt; || acceptHeader.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;includes&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;text/event-stream&amp;#x27;&lt;/span&gt;)) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see the current &lt;a href&#x3D;&quot;/sw.js&quot;&gt;service worker code I&amp;#39;m using here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;†&lt;/sup&gt; I had to add some CSS to handle superscripts, such as the one used
for this footnote.&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;‡&lt;/sup&gt; Perhaps the blog will be a different shade of beige...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/a-brighter-shade-of-beige</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/a-brighter-shade-of-beige"/>
    <title>A brighter shade of beige</title>
    <published>2018-04-02T22:00:00Z</published>
    <updated>2018-04-02T22:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;When I originally built this blog, I gave it a very simple &lt;em&gt;no nonsense&lt;/em&gt; theme.
One colour (beige) for the background and black for text and the odd horizontal
rule. After a couple of minor iterations I added a sticky navigation bar (in
CSS, no JS).&lt;/p&gt;
&lt;p&gt;To be frank though, it looked dated from the start. The rules I used were mostly
borrowed from my days as an academic. It lacked playfulness, so I decided to
revisit it. While the style is still a work in progress, I&amp;#39;ve published some
changes, and this post is about those.&lt;/p&gt;
&lt;p&gt;I took this as an opportunity to overhaul my CSS tooling along with the style. I
switched from &lt;a href&#x3D;&quot;https://www.npmjs.com/package/clean-css&quot;&gt;clean-css&lt;/a&gt; to &lt;a href&#x3D;&quot;http://postcss.org/&quot;&gt;PostCSS&lt;/a&gt; with
&lt;a href&#x3D;&quot;https://www.npmjs.com/package/postcss-import&quot;&gt;import&lt;/a&gt;, &lt;a href&#x3D;&quot;http://cssnext.io/&quot;&gt;cssnext&lt;/a&gt;, and &lt;a href&#x3D;&quot;http://cssnano.co/&quot;&gt;cssnano&lt;/a&gt; plugins. These do
the import inlining and minification I got from clean-css, but also allowed me
to compile out some &lt;code&gt;calc&lt;/code&gt; custom properties the style overhaul introduces. To
make full use of CSS custom properties as variables, I turned off CSS
compilation while in development mode.&lt;/p&gt;
&lt;p&gt;For the palette, I opted to use HSL with CSS custom properties for the the
individual parameters, and &lt;code&gt;calc&lt;/code&gt; to apply offsets to these base value for
derived colours. PostCSS compiles all this out for production, but during
development they can be tweaked in the browser with no compilation. This
approach was inspired by a &lt;a href&#x3D;&quot;http://v6.robweychert.com/blog/2018/02/v6-color/&quot;&gt;recent post by Rob Weychert&lt;/a&gt; on colours
for his blog, but I decided to stick with CSS rather than move to Sass.&lt;/p&gt;
&lt;p&gt;The result doesn&amp;#39;t appear to be a large departure from what was there before.
I&amp;#39;ve brightened the background colour, added distinct colour to the navigation
bar and made it span the width of the page, removed text justification, and
lightened text to a blue complimentary to the background. The look as been
&lt;a href&#x3D;&quot;https://twitter.com/cassiecodes/status/980818410010562560&quot;&gt;described as &lt;em&gt;Battenberg chic&lt;/em&gt;&lt;/a&gt;, which I rather like! If you&amp;#39;d like to
compare, the previous style can be seen &lt;a href&#x3D;&quot;https://web.archive.org/web/20180104055846/https://qubyte.codes&quot;&gt;on the Wayback Machine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are the custom properties at the time of writing:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-css&quot;&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:root&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--base-background-hue&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;45&lt;/span&gt;;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--base-background-sat&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;100%&lt;/span&gt;;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--base-background-lum&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;90%&lt;/span&gt;;

  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--base-foreground-hue&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;calc&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-hue) + &lt;span class&#x3D;&quot;hljs-number&quot;&gt;180&lt;/span&gt;);
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--base-foreground-sat&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;100%&lt;/span&gt;;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--base-foreground-lum&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;30%&lt;/span&gt;;

  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--background-color-main&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;hsl&lt;/span&gt;(
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-hue),
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-sat),
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-lum)
  );

  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--background-color-alt&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;hsl&lt;/span&gt;(
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;calc&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-hue) - &lt;span class&#x3D;&quot;hljs-number&quot;&gt;30&lt;/span&gt;),
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-sat),
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-lum)
  );

  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--standout-color-main&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;hsl&lt;/span&gt;(
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-foreground-hue),
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-foreground-sat),
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-foreground-lum)
  );
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Where &lt;code&gt;--background-color-main&lt;/code&gt; is the background colour of the body,
&lt;code&gt;--background-color-alt&lt;/code&gt; is the background colour of the navigation bar (and
probably other things later), and &lt;code&gt;--standout-color-main&lt;/code&gt; is the colour of the
text.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m looking forward to CSS color module level 4, which will introduce
&lt;code&gt;color-mod&lt;/code&gt; to obtain one colour based on another (I found out about this via
&lt;a href&#x3D;&quot;https://www.lottejackson.com/learning/css-color-module-level-4&quot;&gt;Charlotte Jackson&amp;#39;s blog&lt;/a&gt;). For example, the background colours
could be defined much more tersely as&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-css&quot;&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:root&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--background-color-main&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;hsl&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;45&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;100%&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-number&quot;&gt;90%&lt;/span&gt;);
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--background-color-alt&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;color-mod&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--background-color-main) &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;hue&lt;/span&gt;(-&lt;span class&#x3D;&quot;hljs-number&quot;&gt;30&lt;/span&gt;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;so there would be less need for separate values for hue, saturation, and
lightness. I &lt;em&gt;could&lt;/em&gt; use it now since a PostCSS plugin can compile it out, but
then I lose the ability to adjust the theme in development without compilation.
I prefer to avoid using syntax which isn&amp;#39;t ratified yet anyway. I&amp;#39;m using
PostCSS solely to make the CSS of my blog available as a single, minified, and
backwards-compatible file.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/update-on-webmentions</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/update-on-webmentions"/>
    <title>Update on webmentions</title>
    <published>2018-04-04T19:10:00Z</published>
    <updated>2018-04-04T19:10:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;In &lt;a href&#x3D;&quot;/blog/about-this-blog-3&quot;&gt;a recent post&lt;/a&gt; I wrote that I had integrated
webmentions, and some of that has since changed. Time for an update.&lt;/p&gt;
&lt;p&gt;I was using Netlify form handling as an easy way to handle webmentions, but
unfortunately they inject an additional input into forms. This input is
required by Netlify to name the form, and &lt;code&gt;POST&lt;/code&gt; requests which lack it get a
404 response. This in turn meant that the manual mention form at the bottom of
each post worked, but regular webmentions (which only have &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;target&lt;/code&gt;
parameters) did not.&lt;/p&gt;
&lt;p&gt;I replaced this functionality by pointing the form and webmention link to
&lt;a href&#x3D;&quot;https://webmention.io&quot;&gt;webmention.io&lt;/a&gt;, which was simple since I already have
&lt;a href&#x3D;&quot;https://indieauth.com&quot;&gt;IndieAuth&lt;/a&gt; working with this blog. I may make a custom receiver for
webmentions later, but this is an excellent stopgap.&lt;/p&gt;
&lt;p&gt;On to more exciting stuff... I&amp;#39;ve been enjoying &lt;a href&#x3D;&quot;https://mastodon.social&quot;&gt;Mastodon&lt;/a&gt; lately. An
&lt;a href&#x3D;&quot;https://github.com/tootsuite/mastodon/issues/6074&quot;&gt;issue was recently opened&lt;/a&gt; about having Mastodon dispatch
webmentions for links in status updates. Unfortunately (but I think for good
reasons), it didn&amp;#39;t work out and the issue is now closed. I&amp;#39;m happy for my own
status updates to dispatch webmentions though, so I
&lt;a href&#x3D;&quot;https://glitch.com/edit/#!/mastodon-webmention-relay&quot;&gt;put together a glitch&lt;/a&gt; to do this for me! Take a look, and
remix if you like the idea.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/content-security-policy-and-service-workers</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/content-security-policy-and-service-workers"/>
    <title>Content-Security-Policy and service workers</title>
    <published>2018-04-23T23:00:00Z</published>
    <updated>2018-04-23T23:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I was recently tripped over by a subtlety in how service worker fetch events
and fetch works in conjunction with content security policy (CSP). This happened
while adding an image to the &lt;a href&#x3D;&quot;/about&quot;&gt;about&lt;/a&gt; page. This post is the result of
&lt;a href&#x3D;&quot;https://twitter.com/jaffathecake/status/988402162312114177&quot;&gt;a conversation I had with Jake Archibald&lt;/a&gt; on twitter (with thanks for
helping me to understand what was going on).&lt;/p&gt;
&lt;p&gt;When I originally built this blog I set the content security policy (omitting
policies which aren&amp;#39;t pertinent):&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-properties&quot;&gt;&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;Content-Security-Policy&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;default-src &amp;#x27;self&amp;#x27;; img-src *;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This sets the policy for all requests to be limited to the same domain, except
for images which may come from anywhere. I set it like this since images may
come from a content delivery network (CDN). This means other domain names could
be used, even for my own images.&lt;/p&gt;
&lt;p&gt;Until now this blog has had no images at all, so no issues with this content
security policy with respect to images were obvious.&lt;/p&gt;
&lt;p&gt;When I added an image the browser refused to load it. Firefox wasn&amp;#39;t much help
here, but Chrome gave me a useful error message in the console (the URL is
omitted for brevity):&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;Refused to connect to &amp;#x27;&amp;lt;URL&amp;gt;&amp;#x27; because it violates the following Content Security Policy directive: &amp;quot;default-src &amp;#x27;self&amp;#x27;&amp;quot;. Note that &amp;#x27;connect-src&amp;#x27; was not explicitly set, so &amp;#x27;default-src&amp;#x27; is used as a fallback.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This error was being thrown from a fetch performed by the service worker. With
the worker bypassed, the image loaded as expected. The error above was trying to
tell me that the request for the image within the worker was happening under
a different security policy to that expected. Specifically, the worker is using
the &lt;code&gt;connect-src&lt;/code&gt; policy when performing the request for the image, and not the
&lt;code&gt;image-src&lt;/code&gt; policy I expected. &lt;code&gt;connect-src&lt;/code&gt; is the policy used by &lt;em&gt;scripts&lt;/em&gt;
making requests. Since I don&amp;#39;t define a &lt;code&gt;connect-src&lt;/code&gt; policy, the fallback is
&lt;code&gt;default-src&lt;/code&gt;, which is limited to the domain of the site, and does not allow an
image to be downloaded from a CDN.&lt;/p&gt;
&lt;p&gt;There is a quick solution, which is to add a &lt;code&gt;connect-src *;&lt;/code&gt; policy. By
limiting this policy to the service worker, no other scripts will get to make
requests to anywhere. The Netlify config for this looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-toml&quot;&gt;&lt;span class&#x3D;&quot;hljs-section&quot;&gt;[[headers]]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;for&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/sw.js&amp;quot;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-section&quot;&gt;[headers.values]&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;Content-Security-Policy&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;connect-src *;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But what&amp;#39;s actually going on here? I was confused because I had expected
the fetch performed inside the worker to be subject to the &lt;code&gt;image-src&lt;/code&gt; policy.
I even checked that the &lt;a href&#x3D;&quot;https://fetch.spec.whatwg.org/#concept-request-initiator&quot;&gt;initiator&lt;/a&gt; and &lt;a href&#x3D;&quot;https://fetch.spec.whatwg.org/#concept-request-destination&quot;&gt;destination&lt;/a&gt; of
the request in the fetch event handler were for an image.&lt;/p&gt;
&lt;p&gt;The fetch event and handler looks something like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;fetch&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;fetchEvent&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; responseFromFetch &#x3D; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(fetchEvent.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;request&lt;/span&gt;);

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Other stuff omitted...&lt;/span&gt;
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;m effectively proxying the request. I&amp;#39;ve omitted a bunch of stuff to do with
caching.&lt;/p&gt;
&lt;p&gt;When fetch is called with a request object, the URL of the request is used to
make an entirely &lt;a href&#x3D;&quot;https://fetch.spec.whatwg.org/#fetch-method&quot;&gt;new request before processing&lt;/a&gt; it. This new request
lacks information about the initiator and destination, and so it is subject to
the &lt;code&gt;connect-src&lt;/code&gt; policy.&lt;/p&gt;
&lt;p&gt;This seemed bad to me at first. CSP being different with and without a service
worker would be bad because you&amp;#39;d have to test both each time a new resource
type is added.&lt;/p&gt;
&lt;p&gt;Fortunately, it turns out that the browser also performs CSP checks on the
request &lt;em&gt;before&lt;/em&gt; the service worker receives the fetch event and on the response
it receives from the service worker (important if the worker changes a URL,
which I&amp;#39;m not doing). Restating for the example of an image, these three checks
are made:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The initial request for is checked for &lt;code&gt;image-src&lt;/code&gt; violation.&lt;/li&gt;
&lt;li&gt;A request with the same URL is checked in the  service worker for &lt;code&gt;connect-src&lt;/code&gt; violation.&lt;/li&gt;
&lt;li&gt;The response from the service worker is checked for &lt;code&gt;image-src&lt;/code&gt; violation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means that I&amp;#39;m safe with the &lt;code&gt;connect-src *;&lt;/code&gt; policy for the service worker
mentioned above, since the browser was already applying the &lt;code&gt;image-src&lt;/code&gt; policy
to image requests before the service worker saw them!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/a-battenberg-in-svg</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/a-battenberg-in-svg"/>
    <title>A Battenberg in SVG</title>
    <published>2018-06-10T18:14:20Z</published>
    <updated>2018-06-10T18:14:20Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;In celebration of the Battenberg theme, here is an animated Battenberg! It&amp;#39;s
made of two SVG paths composed of lines and arcs. These are calculated using
three angles and a bucket load of trigonometry (I&amp;#39;m not as good as I used to be
at trig). A &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop updates these angles and the paths.
Click on start to begin the animation. You can tweak the angular speeds using
the three number inputs.&lt;/p&gt;
&lt;p&gt;There are better ways to do this. I chose SVG to challenge myself!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/automatic-announcement-of-new-blog-entries</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/automatic-announcement-of-new-blog-entries"/>
    <title>Automatic announcement of new blog entries</title>
    <published>2018-08-01T18:40:00Z</published>
    <updated>2018-08-01T18:40:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It occurred to me a couple of days ago that it&amp;#39;d be neat to build a glitch to
announce new blog posts. Since I deploy this blog by pushing to a master branch
on GitHub, creation of a blog post is somewhat less obvious than when publishing
on a platform like wordpress or medium, so I needed to figure out another
approach.&lt;/p&gt;
&lt;p&gt;As part of the build process this blog generates a sitemap. All blog entries
hang off of the &lt;code&gt;/blog&lt;/code&gt; path, so it&amp;#39;s not difficult to filter the sitemap down
to only blog entires. By comparing a sitemap before and after deployment, it&amp;#39;s
possible to know when one or more entries have been added, and should be
tweeted!&lt;/p&gt;
&lt;p&gt;Glitch is a natural fit for this. It gives you a little persistent storage (a
directory called &lt;code&gt;.data&lt;/code&gt;), which can be used to stash the sitemap after each
time it gets triggered.&lt;/p&gt;
&lt;p&gt;In depth...&lt;/p&gt;
&lt;h2 id&#x3D;&quot;step-1-commits-are-pushed&quot;&gt;Step 1: Commits are pushed&lt;/h2&gt;
&lt;p&gt;As was the case before this enhancement, I push to deploy. This process starts
with me creating or editing a post, committing it, and then pushing it to the
master branch on GitHub.&lt;/p&gt;
&lt;p&gt;GitHub then dispatches a notification to Netlify.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;step-2-netlify-receives-a-notification&quot;&gt;Step 2: Netlify receives a notification&lt;/h2&gt;
&lt;p&gt;Netlify is configured to build and deploy the blog each time it gets a
notification from GitHub that the master branch has changed. It builds and
deploys the blog.&lt;/p&gt;
&lt;p&gt;The new part is that a &lt;a href&#x3D;&quot;https://www.netlify.com/docs/webhooks/#outgoing-webhooks-and-notifications&quot;&gt;notification&lt;/a&gt; is configured to send a POST to glitch
when a deploy succeeds.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;step-3-a-glitch-app-receives-a-notification&quot;&gt;Step 3: A glitch app receives a notification&lt;/h2&gt;
&lt;p&gt;I built &lt;a href&#x3D;&quot;https://glitch.com/~tweet-new-blog-posts&quot;&gt;this glitch app&lt;/a&gt; to receive and verify POSTs from Netlify. Netlify
uses a JSON web token, and validation is done by shared secret. When the request
is validated, its context (a field on the JSON body of the request) is checked.
This is so that only the production deploy is acted upon, and not branch
deploys.&lt;/p&gt;
&lt;p&gt;For valid POSTs with a production context, the app makes a request for the
&lt;a href&#x3D;&quot;/sitemap.txt&quot;&gt;sitemap&lt;/a&gt; of this blog, and loads the previous sitemap from its
&lt;code&gt;.data&lt;/code&gt; directory. Both are filtered down to only blog posts, and compared for
new entries. The new sitemap is saved in place of the old one.&lt;/p&gt;
&lt;p&gt;Finally, each new blog post is formatted as a tweet, and posted to twitter!&lt;/p&gt;
&lt;h2 id&#x3D;&quot;lessons&quot;&gt;Lessons&lt;/h2&gt;
&lt;p&gt;While it&amp;#39;s pretty cool to automate things using webhooks (glitch in particular
shines for this use-case), the thing which really stood out was how the sitemap
made it all fairly straight forward. Originally I added the sitemap almost as an
afterthought, since it was cheap to do with a template and is good for search
engines. I&amp;#39;m glad I made it a newline separated list rather than an XML
monstrosity because it&amp;#39;s so easy to parse this way.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/routes-of-restful-services-with-nested-resources</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/routes-of-restful-services-with-nested-resources"/>
    <title>Routes of RESTful services with nested resources</title>
    <published>2018-10-19T19:00:30Z</published>
    <updated>2018-10-19T19:00:30Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Nested resources are common in real life, and when you&amp;#39;re a programmer working
on RESTful APIs you may have noticed that there are often trade-offs when it
comes to formatting the routes of nested resources.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s say we&amp;#39;re building an API for carpenters. Their guild wants a way to help
the carpenters manage their clients, and the items which they&amp;#39;re creating for
their clients (jobs). A set of routes for this might look like&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-properties&quot;&gt;&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/clients&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/clients&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/clients/:clientId&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/jobs&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/jobs&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/jobs/:jobId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;where segments preceded by a colon are placeholders for a UUID. The first route
of each group gets a collection, the second creates a new member, and the third
gets a member by its ID. There may well be other operations like the third which
act on individual members of a collection, such as PUT for replacing and DELETE
for removal.&lt;/p&gt;
&lt;p&gt;The problem with this layout is that, as a carpenter, you&amp;#39;re rarely going to
want to see clients which aren&amp;#39;t &lt;em&gt;your&lt;/em&gt; clients. There may also be lots of jobs,
and making a request for a list of jobs could select a huge number of them.
Usually you&amp;#39;ll want to filter it by the client. We can use query parameters to
supply additional context, but this seems a bit clumsy. Let&amp;#39;s have another go&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-properties&quot;&gt;&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients/:clientId&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients/:clientId/jobs&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients/:clientId/jobs&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients/:clientId/jobs/:jobId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;#39;s pretty clear now that these are nested resources. It&amp;#39;s no longer possible
to select a collection without refining it by its parent resources.&lt;/p&gt;
&lt;p&gt;The problem now is that it&amp;#39;s a bit annoying to have to use &lt;em&gt;all&lt;/em&gt; the parent IDs
of a list or a member. I recently realised (and I&amp;#39;m probably late to the party)
that a good solution is a compromise between the two&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-properties&quot;&gt;&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/carpenters/:carpenterId/clients&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/clients/:clientId&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/clients/:clientId/jobs&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;POST&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/clients/:clientId/jobs&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;GET&lt;/span&gt;  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;/jobs/:jobId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this arrangement, we need to use a route with a parent ID when listing a
resource or creating a new member. When we already have an ID of a resource we
don&amp;#39;t need to include its parent in the path. This includes other singular
operations such as PUT and DELETE.&lt;/p&gt;
&lt;p&gt;Finally, a word on nesting. The implicit assertion made above is that a child
resource has a single parent, which may not be the case. Think of the solution
above as a filtering mechanism. i.e. a client may have contracted more than one
carpenter. You may also want to list jobs for a carpenter, rather than for a
client. If you find yourself writing too many routes, using query parameters may
be the lesser evil. Be pragmatic!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/parsing-input-from-stdin-to-structures-in-rust</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/parsing-input-from-stdin-to-structures-in-rust"/>
    <title>Parsing input from stdin to structures in Rust</title>
    <published>2019-01-01T23:30:00Z</published>
    <updated>2019-04-22T13:40:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;ve been working on this post for a while, but about a month ago my firstborn
arrived, and he&amp;#39;s been getting the lion&amp;#39;s share of my attention. I start work
again tomorrow, so I decided to just publish this post and be done with it.
Apologies if it&amp;#39;s a little rough around the edges!&lt;/p&gt;
&lt;p&gt;In a post a while back I described in depth my solution to an
&lt;a href&#x3D;&quot;https://adventofcode.com/&quot;&gt;Advent of Code&lt;/a&gt; challenge. This year I took part again, but rather than
using Node.js to write programs to solve challenges I decided to use Rust.&lt;/p&gt;
&lt;p&gt;The input for these challenges fell into one of three broad categories; input
short enough to provide as an argument to the solver program, longer input as
a single line in a file, and input over many lines in a file. In the last case,
each line usually represents an individual structure of some kind in a
collection. In all cases, I chose to keep the input separate. I&amp;#39;ll be focussing
on how I approached the last case for this article.&lt;/p&gt;
&lt;p&gt;Rust has excellent IO support, but parsing of strings into structures is more
difficult than for a language like JavaScript. As a meta-challenge, I found
parsing input interesting, and I learnt a lot about iterators, the &lt;code&gt;Option&lt;/code&gt; and
&lt;code&gt;Result&lt;/code&gt; types, and type casting in the process. I&amp;#39;ll use the challenge of
&lt;a href&#x3D;&quot;https://adventofcode.com/2018/day/10&quot;&gt;day 10&lt;/a&gt; for illustrative purposes.&lt;/p&gt;
&lt;p&gt;To be unix-y, I decided to read input from stdin rather than reading from a
file.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-shell&quot;&gt;cargo run &amp;lt; input.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On day 10 the data looked like&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;position&#x3D;&amp;lt;-10427, -42253&amp;gt; velocity&#x3D;&amp;lt; 1,  4&amp;gt;
position&#x3D;&amp;lt; 21343,  42515&amp;gt; velocity&#x3D;&amp;lt;-2, -4&amp;gt;
position&#x3D;&amp;lt;-10417, -52846&amp;gt; velocity&#x3D;&amp;lt; 1,  5&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each line represents what I will call a particle, including its initial position
and velocity.&lt;/p&gt;
&lt;p&gt;Rust ships with some nice functionality for working with input from stdin. The
&lt;code&gt;main&lt;/code&gt; function for my solution to Day 10 looks like&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-rust&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; std::io::{stdin, BufRead};

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// ...&lt;/span&gt;

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;main&lt;/span&gt;() {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;mut &lt;/span&gt;&lt;span class&#x3D;&quot;hljs-variable&quot;&gt;particles&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;Vec&lt;/span&gt;&amp;lt;Particle&amp;gt; &#x3D; &lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;stdin&lt;/span&gt;()
    .&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;lock&lt;/span&gt;()
    .&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;lines&lt;/span&gt;()
    .&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;filter_map&lt;/span&gt;(|line_result| line_result.&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;ok&lt;/span&gt;()) &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// #1&lt;/span&gt;
    .&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;filter_map&lt;/span&gt;(|line| line.&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;parse&lt;/span&gt;().&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;ok&lt;/span&gt;())       &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// #2&lt;/span&gt;
    .&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;collect&lt;/span&gt;();                                 &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// #3&lt;/span&gt;

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// use particles&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I like the readability of this chain. &lt;code&gt;stdin().lock().lines()&lt;/code&gt; returns an
iterator which yields lines, each of which is a &lt;a href&#x3D;&quot;https://doc.rust-lang.org/std/result/enum.Result.html&quot;&gt;&lt;code&gt;Result&lt;/code&gt;&lt;/a&gt; containing
either a string or an error. The rest of this chain filters out errors (&lt;code&gt;#1&lt;/code&gt;),
parses line strings into a custom &lt;code&gt;Particle&lt;/code&gt; type (&lt;code&gt;#2&lt;/code&gt;), and finally collects
elements into a vector of particles (&lt;code&gt;#3&lt;/code&gt;)&#x60;.&lt;/p&gt;
&lt;p&gt;Steps &lt;code&gt;#1&lt;/code&gt; and &lt;code&gt;#2&lt;/code&gt; both make use of &lt;a href&#x3D;&quot;https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map&quot;&gt;filter_map&lt;/a&gt;. This is a method of
iterators which takes a closure. The closure takes an element and returns an
&lt;code&gt;Option&lt;/code&gt;. When the option is &lt;code&gt;None&lt;/code&gt; the element is filtered out. When it returns
&lt;code&gt;Some(value)&lt;/code&gt; the element is mapped to &lt;code&gt;value&lt;/code&gt; in the ongoing stream. Since
elements of the line iterator are &lt;code&gt;Result&lt;/code&gt;s, I call &lt;code&gt;ok&lt;/code&gt; on them, which returns
an &lt;a href&#x3D;&quot;https://doc.rust-lang.org/std/option/enum.Option.html&quot;&gt;&lt;code&gt;Option&lt;/code&gt;&lt;/a&gt; type which discards an &lt;code&gt;Err(error)&lt;/code&gt; for a &lt;code&gt;None&lt;/code&gt;.
&lt;code&gt;filter_map&lt;/code&gt; then unwraps the value for the next step in the chain, or filters
out the &lt;code&gt;None&lt;/code&gt; (so line errors are effectively ignored).&lt;/p&gt;
&lt;p&gt;Early on in my journey into Rust I found &lt;code&gt;Result&lt;/code&gt;s and &lt;code&gt;Options&lt;/code&gt; quite annoying.
I wrote a lot of &lt;code&gt;if&lt;/code&gt;s and &lt;code&gt;unwraps&lt;/code&gt; to force values out of them. After I while
I came across things like &lt;code&gt;filter_map&lt;/code&gt;, &lt;code&gt;if let&lt;/code&gt;, and &lt;code&gt;match&lt;/code&gt; for working with
them more fluently, I realised that they&amp;#39;re really neat.&lt;/p&gt;
&lt;p&gt;The next step (&lt;code&gt;#2&lt;/code&gt;) is to parse the line string into a &lt;code&gt;Particle&lt;/code&gt; structure
wrapped in a result, and again discard errors and unwrap. Finally (&lt;code&gt;#3&lt;/code&gt;) the
results are collected into a vector for easy use in the rest of the program.&lt;/p&gt;
&lt;p&gt;You might have noticed that I&amp;#39;ve skipped a big chunk of what&amp;#39;s going on in &lt;code&gt;#2&lt;/code&gt;.
How do I parse lines into a structure which I&amp;#39;ve defined? By implementing
&lt;code&gt;std::str::FromStr&lt;/code&gt; for the &lt;code&gt;Particle&lt;/code&gt; structure, &lt;code&gt;parse&lt;/code&gt; gains the
ability to parse to &lt;code&gt;Particle&lt;/code&gt;. In the chain above, the type of &lt;code&gt;particles&lt;/code&gt; is
&lt;code&gt;Vec&amp;lt;Particle&amp;gt;&lt;/code&gt;, which is enough for Rust to infer that the call to &lt;code&gt;parse&lt;/code&gt;
means to parse to &lt;code&gt;Particle&lt;/code&gt;. This type annotation is also used by collect to
determine that we want &lt;code&gt;Particles&lt;/code&gt; to be collected into a vector.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s the Particle definition:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-rust&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Particle&lt;/span&gt; {
  position_x: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;,
  position_y: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;,
  velocity_x: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;,
  velocity_y: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;#39;s not a lot to see here. It&amp;#39;s really just a wrapper for &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;
positions and velocities as read from the input. I assumed that &lt;code&gt;isize&lt;/code&gt; will
be sufficient to contain any values read (though this probably isn&amp;#39;t the correct
integer type to use).&lt;/p&gt;
&lt;p&gt;It&amp;#39;s possible for a string to &lt;em&gt;not&lt;/em&gt; parse to a &lt;code&gt;Particle&lt;/code&gt;. For this, a new type
of error needs to be defined. I&amp;#39;m just going to put it below here since there&amp;#39;s
a lot I don&amp;#39;t understand about defining errors in Rust yet.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-rust&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; std::{
  fmt::{&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;self&lt;/span&gt;, Display, Formatter},
  error::Error,
  num::ParseIntError
};

&lt;span class&#x3D;&quot;hljs-meta&quot;&gt;#[derive(Debug, Clone)]&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;ParseParticleError&lt;/span&gt;;

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;impl&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Display&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;ParseParticleError&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;fmt&lt;/span&gt;(&amp;amp;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;self&lt;/span&gt;, f: &amp;amp;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;mut&lt;/span&gt; Formatter) &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; fmt::&lt;span class&#x3D;&quot;hljs-type&quot;&gt;Result&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;write!&lt;/span&gt;(f, &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;Unable to parse to a Particle.&amp;quot;&lt;/span&gt;)
  }
}

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;impl&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;ParseParticleError&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;description&lt;/span&gt;(&amp;amp;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;self&lt;/span&gt;) &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; &amp;amp;&lt;span class&#x3D;&quot;hljs-type&quot;&gt;str&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;Unable to parse to a Particle.&amp;quot;&lt;/span&gt;
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;cause&lt;/span&gt;(&amp;amp;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;self&lt;/span&gt;) &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-type&quot;&gt;Option&lt;/span&gt;&amp;lt;&amp;amp;Error&amp;gt; {
    &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;None&lt;/span&gt;
  }
}

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;impl&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;From&lt;/span&gt;&amp;lt;ParseIntError&amp;gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;ParseParticleError&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;from&lt;/span&gt;(_error: ParseIntError) &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;Self&lt;/span&gt; {
    ParseParticleError
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The one part I will dwell on is the final implementation. It allows
&lt;code&gt;ParseIntError&lt;/code&gt; errors to be cast to &lt;code&gt;ParseParticleError&lt;/code&gt;. This becomes handy in
the &lt;code&gt;FromStr&lt;/code&gt; implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-rust&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; regex::Regex;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; lazy_static::lazy_static;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; std::&lt;span class&#x3D;&quot;hljs-type&quot;&gt;str&lt;/span&gt;::FromStr;

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; PARTICLE_REGEX: &amp;amp;&lt;span class&#x3D;&quot;hljs-type&quot;&gt;str&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;r&amp;quot;&amp;lt;\s*(-?\d+),\s*(-?\d+)&amp;gt;.*&amp;lt;\s*(-?\d+),\s*(-?\d)&amp;gt;&amp;quot;&lt;/span&gt;;

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;impl&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;FromStr&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Particle&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Err&lt;/span&gt; &#x3D; ParseParticleError;

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;from_str&lt;/span&gt;(particle_str: &amp;amp;&lt;span class&#x3D;&quot;hljs-type&quot;&gt;str&lt;/span&gt;) &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-type&quot;&gt;Result&lt;/span&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;Self&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;Self&lt;/span&gt;::&lt;span class&#x3D;&quot;hljs-literal&quot;&gt;Err&lt;/span&gt;&amp;gt; {
    lazy_static! {
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;ref&lt;/span&gt; regex: Regex &#x3D; Regex::&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;new&lt;/span&gt;(PARTICLE_REGEX).&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;unwrap&lt;/span&gt;();
    }

    regex.&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;captures&lt;/span&gt;(particle_str)
      .&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;ok_or&lt;/span&gt;(ParseParticleError)
      .&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;and_then&lt;/span&gt;(|cap| &lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;Ok&lt;/span&gt;(Particle {
        position_x: cap[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;].&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;parse&lt;/span&gt;()?,
        position_y: cap[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;].&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;parse&lt;/span&gt;()?,
        velocity_x: cap[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;3&lt;/span&gt;].&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;parse&lt;/span&gt;()?,
        velocity_y: cap[&lt;span class&#x3D;&quot;hljs-number&quot;&gt;4&lt;/span&gt;].&lt;span class&#x3D;&quot;hljs-title function_ invoke__&quot;&gt;parse&lt;/span&gt;()?
      }))
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While Rust doesn&amp;#39;t come with a library for working with regular expressions,
there is the excellent &lt;a href&#x3D;&quot;https://crates.io/crates/regex&quot;&gt;&lt;code&gt;regex&lt;/code&gt;&lt;/a&gt; crate on &lt;a href&#x3D;&quot;hhttps://crates.io&quot;&gt;crates.io&lt;/a&gt;. I used this
and &lt;a href&#x3D;&quot;https://crates.io/crates/lazy_static&quot;&gt;&lt;code&gt;lazy_static&lt;/code&gt;&lt;/a&gt; to match and extract numbers (as strings) from each line
of the input. This is a quick-and-dirty solution to parsing input. Rust has
other crates for parsing input which may be a better fit for other purposes. I
found their APIs difficult to comprehend and laborious though and given that
the source of the input here is low risk and the &lt;a href&#x3D;&quot;https://regexper.com/#%2F%3C%5Cs*%28-%3F%5Cd%2B%29%2C%5Cs*%28-%3F%5Cd%2B%29%3E.*%3C%5Cs*%28-%3F%5Cd%2B%29%2C%5Cs*%28-%3F%5Cd%29%3E%2F&quot;&gt;regular expression
unsophisticated&lt;/a&gt; I decided it was a reasonable course.&lt;/p&gt;
&lt;p&gt;The creation of the regular expression is wrapped in &lt;code&gt;lazy_static&lt;/code&gt;, which means
that this only occurs once, and subsequent calls to &lt;code&gt;from_str&lt;/code&gt; will reuse it.
The documentation for the &lt;code&gt;regex&lt;/code&gt; library suggests the use of &lt;code&gt;lazy_static&lt;/code&gt;, so
there was no ingenuity on my part.&lt;/p&gt;
&lt;p&gt;The regular expression matches lines which look like lines of the above input
sample, capturing the numbers (including a leading &lt;code&gt;-&lt;/code&gt; if there is one).&lt;/p&gt;
&lt;p&gt;Onto the chain! The regular expression is applied to &lt;code&gt;particle_str&lt;/code&gt;. The call
to &lt;code&gt;captures&lt;/code&gt; returns an option. The &lt;code&gt;ok_or&lt;/code&gt; step in the chain turns a &lt;code&gt;None&lt;/code&gt;
into a &lt;code&gt;ParseParticleError&lt;/code&gt;. When the regular expression finds no match in a
string then it cannot be parsed to a particle!&lt;/p&gt;
&lt;p&gt;The next step is an &lt;code&gt;and_then&lt;/code&gt;. This unwraps &lt;code&gt;Some(value)&lt;/code&gt; to &lt;code&gt;value&lt;/code&gt; and passes
the value to its closure. Errors are waved past by this step. The return value
of the closure is a &lt;code&gt;Result&lt;/code&gt;. There are four integers to parse. The regular
expression already filters out most issues, but an integer might still be too
large to be represented as an &lt;code&gt;isize&lt;/code&gt;. &lt;a href&#x3D;&quot;https://rust-lang-nursery.github.io/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html&quot;&gt;The &lt;code&gt;?&lt;/code&gt; operator&lt;/a&gt; unwraps result so
that a &lt;code&gt;Particle&lt;/code&gt; structure can be composed. If any isize parse results in an
error, the error is immediately returned (and cast to a &lt;code&gt;ParseParticleError&lt;/code&gt;).
Since the return value of this closure must be a &lt;code&gt;Result&lt;/code&gt;, the particle is
wrapped in an &lt;code&gt;Ok&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;All of this effort to implement string parsing for &lt;code&gt;Particle&lt;/code&gt; might seem
redundant given that errors are filtered out by the main function anyway, but I
enjoyed exploring this functionality of Rust. If you&amp;#39;re interested in the full
code of my solution to day 10, you can find it &lt;a href&#x3D;&quot;https://gist.github.com/qubyte/f5a68779d4c7f14f2d722a5d13815bb4&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;update-2019-04-22&quot;&gt;Update 2019-04-22&lt;/h2&gt;
&lt;p&gt;I was told today about an interesting crate which automates a lot of the parsing
work in my code through use of a macro. The &lt;a href&#x3D;&quot;https://crates.io/crates/recap&quot;&gt;recap&lt;/a&gt; crate neatly avoids
the need to manually apply a regular expression and extract elements from it,
parsing each individually.&lt;/p&gt;
&lt;p&gt;Adding parsing of lines to particle structures now looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-rust&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; recap::Recap;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; serde::Deserialize;

&lt;span class&#x3D;&quot;hljs-meta&quot;&gt;#[derive(Debug, Clone, Deserialize, Recap)]&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-meta&quot;&gt;#[recap(regex &#x3D; r&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;&amp;lt;\s*(?P&amp;lt;position_x&amp;gt;-?\d+),\s*(?P&amp;lt;position_y&amp;gt;-?\d+)&amp;gt;.*&amp;lt;\s*(?P&amp;lt;velocity_x&amp;gt;-?\d+),\s*(?P&amp;lt;velocity_y&amp;gt;-?\d)&amp;gt;&amp;quot;&lt;/span&gt;)]&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Particle&lt;/span&gt; {
    position_x: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;,
    position_y: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;,
    velocity_x: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;,
    velocity_y: &lt;span class&#x3D;&quot;hljs-type&quot;&gt;isize&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;#39;s it! Errors produced (or lack thereof) might be quite different, I&amp;#39;ve not
checked it out in detail yet. For the purposes of the task though, it works
well. Named capture groups isolate elements by name, and the corresponding type
of the field in the particle &lt;code&gt;struct&lt;/code&gt; is used to parse each. A gist containing
the complete updated solution can be found &lt;a href&#x3D;&quot;https://gist.github.com/qubyte/d259676b4927a6fbe8fd5f99e61d2e62&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/my-new-year-resolution</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/my-new-year-resolution"/>
    <title>My new year resolution</title>
    <published>2019-01-16T23:27:12Z</published>
    <updated>2019-01-16T23:27:12Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;My resolution this year was to work on my Japanese speaking ability.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve already failed.&lt;/p&gt;
&lt;p&gt;The keys to making a good resolution are to make it measurable, and incremental.
To achieve it you need to define what &amp;quot;progress&amp;quot; and &amp;quot;done&amp;quot; mean for it. A vague
promise to get better at something does neither.&lt;/p&gt;
&lt;p&gt;So, how do I rescue it?&lt;/p&gt;
&lt;p&gt;It&amp;#39;s never too late! I&amp;#39;m only 16 days into the year. Lots of time to achieve my
goal, as long as I can define it! I also have to be realistic about my spare
time. My son was born at the end of last year, and while he&amp;#39;s the biggest reason
for me to improve my Japanese (we want him to be bilingual), he&amp;#39;s also a time
sink (in a good way!)&lt;/p&gt;
&lt;p&gt;First I must ask myself what&amp;#39;s been holding me back. I&amp;#39;ve been learning Japanese
on and off since around 2001, and I still can&amp;#39;t hold a conversation. My
listening ability by far outstrips my ability to speak, and that imbalance is
revealing. I think the problem stems from three things; perfectionism, laziness,
and role-play.&lt;/p&gt;
&lt;p&gt;Perfectionism is easy to explain. I&amp;#39;m a lapsed physicist and a programmer. Both
fields emphasise precision. I dislike making mistakes, even though I know that
making them is the faster path to learning.&lt;/p&gt;
&lt;p&gt;Laziness is also easy. As a programmer, I automate things. With a little code, I
can optimise away monotonous or repetitive tasks. A language is a bulky thing
though. I can&amp;#39;t write some code to learn it for me. Not yet anyway.&lt;/p&gt;
&lt;p&gt;Role-play is more difficult to explain. A language isn&amp;#39;t just words. It has a
personality, and you have to act it as much as you speak it. I&amp;#39;m bad at
role-play. It terrifies me.&lt;/p&gt;
&lt;p&gt;These three issues are holding me back, and I don&amp;#39;t think there&amp;#39;s a solution to
that. I&amp;#39;m going to have to push through. I think that defining what progress
looks like for learning to speak more Japanese will help with these issues, and
also rescue my resolution.&lt;/p&gt;
&lt;p&gt;So, how should I define progress? What does measurable look like for the ability
to speak a language? I&amp;#39;d love to know your thoughts!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1548020291155</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1548020291155"/>
    <title>Note 0</title>
    <published>2019-01-20T21:38:11Z</published>
    <updated>2019-01-20T21:38:11Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Testing, 123.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1548025460840</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1548025460840"/>
    <title>Note 1</title>
    <published>2019-01-20T23:04:20Z</published>
    <updated>2019-01-20T23:04:20Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Testing again, 456.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1548026606345</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1548026606345"/>
    <title>Note 2</title>
    <published>2019-01-20T23:23:26Z</published>
    <updated>2019-01-20T23:23:26Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Notes are live! 👍&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1548026962646</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1548026962646"/>
    <title>Note 3</title>
    <published>2019-01-20T23:29:22Z</published>
    <updated>2019-01-20T23:29:22Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I implemented #indieweb notes on my blog using a Netlify function and the GitHub content API. I&amp;#39;m using Keith J Grant&amp;#39;s omnibear browser extension (in FireFox) to post. Not much to see yet, and it&amp;#39;s a very basic implementation, but not bad for a couple of hours of work.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1548030006035</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1548030006035"/>
    <title>Note 4</title>
    <published>2019-01-21T00:20:06Z</published>
    <updated>2019-01-21T00:20:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Now I need to reconsider my landing page. It&amp;#39;d be nice to have the notes there, and some information about me.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1548106744061</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1548106744061"/>
    <title>Note 5</title>
    <published>2019-01-21T21:39:04Z</published>
    <updated>2019-01-21T21:39:04Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;One of the cool things about micropub is that I can use a third party client really easily. This post was created with micropublish.net&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1549021276090</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1549021276090"/>
    <title>Note 6</title>
    <published>2019-02-01T11:41:16Z</published>
    <updated>2019-02-01T11:41:16Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;ve come to refer to my preferrred way of creating microservices as &amp;quot;calving&amp;quot;. Start with a monolith, and move bits into microservices where the coupling is weak (like a big iceberg calving off a smaller iceberg).&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1549332045086</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1549332045086"/>
    <title>Note 7</title>
    <published>2019-02-05T02:00:45Z</published>
    <updated>2019-02-05T02:00:45Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Testing alternative micropub server implementation.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1549383755373</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1549383755373"/>
    <title>Note 8</title>
    <published>2019-02-05T16:22:35Z</published>
    <updated>2019-02-05T16:22:35Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;All micropub client implementations appear to be a bit lacking (none seem to support a media endpoint very well). I&amp;#39;m considering making my own client and embedding it in my blog...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1550834545215</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1550834545215"/>
    <title>Note 9</title>
    <published>2019-02-22T11:22:25Z</published>
    <updated>2019-02-22T11:22:25Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Reminder to myself to write a post about my recent contribution to Node.js.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/a-recent-contribution-i-made-for-nodejs</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/a-recent-contribution-i-made-for-nodejs"/>
    <title>A recent contribution I made for Node.js</title>
    <published>2019-02-27T23:05:00Z</published>
    <updated>2019-02-27T23:05:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;ve made a couple of small contributions to Node.js in the past. These were
quite esoteric, and unlikely to be discovered or noticed by most developers.
Recently I made &lt;a href&#x3D;&quot;https://github.com/nodejs/node/pull/25974&quot;&gt;a contribution&lt;/a&gt; which might be noticed though.&lt;/p&gt;
&lt;p&gt;Most Node developers are familiar with &lt;a href&#x3D;&quot;http://expressjs.com/&quot;&gt;Express&lt;/a&gt;. When responding to a client
request in Express, &lt;em&gt;chaining&lt;/em&gt; makes setting the status and sending a body
fluent. It is possible to chain the call to &lt;code&gt;send&lt;/code&gt; after the call to &lt;code&gt;status&lt;/code&gt;
because &lt;code&gt;status&lt;/code&gt; returns &lt;code&gt;res&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Express version.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;requestHandler&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) {
  res.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;200&lt;/span&gt;).&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Hello, world!&amp;#x27;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The vanilla Node analogue is a little more verbose because &lt;code&gt;writeHead&lt;/code&gt; had no
explicit return (which means it returned &lt;code&gt;undefined&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Vanilla Node.&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;requestHandler&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) {
  res.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;writeHead&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;200&lt;/span&gt;);
  res.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;end&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Hello, world!&amp;#x27;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I find the repetition a little annoying, and frequently tried to chain the call
to &lt;code&gt;end&lt;/code&gt; onto the call to &lt;code&gt;writeHead&lt;/code&gt; by mistake.&lt;/p&gt;
&lt;p&gt;My contribution was to make &lt;code&gt;writeHead&lt;/code&gt; return the response object &lt;code&gt;res&lt;/code&gt;, to
enable method chaining.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Node 11.10.0+&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;requestHandler&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) {
  res.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;writeHead&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;200&lt;/span&gt;).&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;end&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;Hello, world!&amp;#x27;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This makes the vanilla Node API for server responses just a little more
friendly.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/weeknotes-1</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/weeknotes-1"/>
    <title>Weeknotes #1</title>
    <published>2019-03-03T14:00:00Z</published>
    <updated>2019-03-03T14:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This is my first go at writing a weeknotes entry, and this week has been a busy
one!&lt;/p&gt;
&lt;p&gt;On Monday the Brighton Rust User Group held it&amp;#39;s first meeting since November
(it&amp;#39;s a small group and two of us had babies arrive a day apart), and the first
held in the evening. We were shown some advanced code for handling game assets,
and some hardware which can run embedded Rust. I&amp;#39;m looking forward to seeing
more!&lt;/p&gt;
&lt;p&gt;On the topic of Rust, I &lt;a href&#x3D;&quot;https://twitter.com/cori/status/1101133644196917253?s&#x3D;20&quot;&gt;discovered this week&lt;/a&gt; that
&lt;a href&#x3D;&quot;https://glitch.com&quot;&gt;glitch&lt;/a&gt; can deploy Rust! If I find the time, I&amp;#39;ll reimplement one or
two of my personal bots (which are hosted by glitch in Node.js) in Rust.&lt;/p&gt;
&lt;p&gt;On Thursday Codebar Brighton held their
&lt;a href&#x3D;&quot;http://www.codebar.io/events/brighton-talks-1&quot;&gt;first edition of Codebar Talks&lt;/a&gt;. &lt;a href&#x3D;&quot;https://adactio.com&quot;&gt;Jeremy Keith&lt;/a&gt;
gave a presentation on metaphores we use as programmers both for ourselves and
the tools we use, and the implications of them (I&amp;#39;m keen on this topic).
&lt;a href&#x3D;&quot;http://katebeard.co/&quot;&gt;Kate Beard&lt;/a&gt; gave a presentation Morse Code and the
&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API&quot;&gt;Web Audio API&lt;/a&gt;. I love this approach to using browser
technologies such as Web Audio API to reproduce old tech like Morse code or the
&lt;a href&#x3D;&quot;https://youtu.be/lQMcZtiaD0A&quot;&gt;ZX Spectrum loading screen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In non-web news, I&amp;#39;ve been thinking about books and records. I have lots of
books. I&amp;#39;m thinking of donating the fiction ones, but some of the text books I&amp;#39;d
like to keep around and have handy when the whim to pick one up takes me.
There&amp;#39;s a perfect little space in my home office to place a small book case, but
I don&amp;#39;t want the usual sort of bookcase, I want one with angled shelves so you
can actually see what&amp;#39;s on them while still being a short piece of furniture.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s a mid-century modern design that caught my eye, but they&amp;#39;re rare in the
UK (they appear to have been more popular in the US). Since I can&amp;#39;t find one, I
figured I&amp;#39;d come up with a flat-pack version which slots together made of birch
plywood, and outsource the machining. If I decide to do it I&amp;#39;ll post pictures
here!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1551622515057</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1551622515057"/>
    <title>Note 10</title>
    <published>2019-03-03T14:15:15Z</published>
    <updated>2019-03-03T14:15:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I have a glitch which tweets out new blog entries. Right now it just looks at changes in the sitemap. Reminder to improve it to include tags.&lt;/p&gt;
&lt;p&gt;The selector to use is &lt;code&gt;&amp;#39;[rel&#x3D;&amp;quot;tag&amp;quot;]&amp;#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href&#x3D;&quot;https://tweet-new-blog-posts.glitch.me/&quot;&gt;https://tweet-new-blog-posts.glitch.me/&lt;/a&gt;&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1552581066319</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1552581066319"/>
    <title>Note 11</title>
    <published>2019-03-14T16:31:06Z</published>
    <updated>2019-03-14T16:31:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I can think of no better tribute (albeit late) than to wish the World Wide Web a happy 30th birthday on my own weird little personal site.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1552830027094</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1552830027094"/>
    <title>Note 12</title>
    <published>2019-03-17T13:40:27Z</published>
    <updated>2019-03-17T13:40:27Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Tickets booked for the next trip to Japan. Our first with the little one. I can&amp;#39;t wait for his grandparents to meet him!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/weeknotes-2</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/weeknotes-2"/>
    <title>Weeknotes #2</title>
    <published>2019-03-17T23:45:11Z</published>
    <updated>2019-03-17T23:45:11Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This edition really covers the last two weeks.&lt;/p&gt;
&lt;p&gt;The highlight of last week was &lt;a href&#x3D;&quot;https://www.bytesconf.co.uk&quot;&gt;Bytes Conf&lt;/a&gt;. It was held on the Thursday evening, with four excellent talks. I was also one of the raffle winners! There were lots of familiar faces attending, so I took the opportunity to catch up with folk. These last few months I’ve not been doing much extracurricular stuff because of the little one.&lt;/p&gt;
&lt;p&gt;I redeemed my winning raffle ticket for £50 off a print from &lt;a href&#x3D;&quot;https://www.theprivatepress.org&quot;&gt;The Private Press&lt;/a&gt;. I put it towards a beautiful print of the Welbeck Street Car Park (a soon to be demolished brutalist multi-storey car park) by &lt;a href&#x3D;&quot;https://www.paulcatherall.com&quot;&gt;Paul Catherall&lt;/a&gt;. I can’t wait to get it framed.&lt;/p&gt;
&lt;p&gt;The final two talks of the evening were particularly interesting to me. They were both themed on limitation promoting creativity. The first talk was from a technical perspective, with a focus on CSS and working within the confines of advertisements and of daily challenges. I’ve often found that imposing limitations on myself leads to my most creative work. The latter talk took a historical angle, and covered everything from French protest posters to the Futura typeface. I’ll link the talks once the videos have been uploaded.&lt;/p&gt;
&lt;p&gt;Last week I began to design the bookshelves I mentioned in Weeknotes #1. When I was in secondary school I was pretty handy with CAD software, so I decided to try to make the schematic in FreeCAD. Unfortunately it seems my understanding has entirely elapsed. While I &lt;em&gt;could&lt;/em&gt; spend some time learning how to use it, for a single piece of furniture it seemed like overkill. I decided to use SVG instead (via Boxy SVG with some manual tweaks). I spoke to a local wood store and they stock sheets of birch plywood and can machine it to shape for me. This week I decided to make two versions of the shelves. One will be for books and the other for CDs and LPs. I’ll take the designs to them next week to see what they think.&lt;/p&gt;
&lt;p&gt;Finally, we booked tickets to Japan this weekend. It’ll be the little one’s first time to meet his grandparents, and I can’t wait!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1552943829855</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1552943829855"/>
    <title>Note 13</title>
    <published>2019-03-18T21:17:09Z</published>
    <updated>2019-03-18T21:17:09Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Homebrew Website Club is this Thursday, so I need to either think of a blog post I want to write, or think of an enhancement to my blog. I’d quite like to integrate an editor, but that’s a big task. Something smaller, or an enhancement to an existing feature might have more impact...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/weeknotes-3</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/weeknotes-3"/>
    <title>Weeknotes #3</title>
    <published>2019-03-24T23:10:00Z</published>
    <updated>2019-03-24T23:10:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This week started off a little boring. On the Thursday though, the rebooted Brighton &lt;a href&#x3D;&quot;https://indieweb.org/Homebrew_Website_Club&quot;&gt;Homebrew Website Club&lt;/a&gt; met at &lt;a href&#x3D;&quot;https://clearleft.com/&quot;&gt;Clearleft&lt;/a&gt;. After catching up with the folk there, I worked on an experiment to theme this blog, with the theme determined by a toggle in &lt;code&gt;localStorage&lt;/code&gt;. By placing an inline script in the head right after the stylesheet link tag, I demonstrated that it&amp;#39;s possible to select a theme before a paint, avoiding an awkward flash of the wrong theme. This may not news to anyone but me. I&amp;#39;ve not implemented anything just yet.&lt;/p&gt;
&lt;p&gt;On the topic of themes, I wanted to do this because I want to add a dark theme. To celebrate it (when it’s finished), I will make a &lt;em&gt;dark mode Battenberg&lt;/em&gt; cake. At the moment the plan is to make it out of chocolate and red velvet cakes for the pattern, and cover it in chocolate for the outside. I&amp;#39;ve baked a red velvet cake in a tray, but it came out as a regular chocolate cake so I&amp;#39;m going to use more red colouring for the next try. I&amp;#39;ll post a picture if I ever actually make it.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve noticed folks I know are starting up, or returning to, their personal sites. Just like this blog, the majority of them are being reborn as static sites deployed by &lt;a href&#x3D;&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;. It&amp;#39;s good to know that this solution appeals to more folk than just me. It&amp;#39;s even better to see more unique stuff on the web, away from the silos owned by giants like Facebook.&lt;/p&gt;
&lt;p&gt;On Sunday evening I enhanced my &lt;a href&#x3D;&quot;https://glitch.com/edit/#!/tweet-new-blog-posts&quot;&gt;new blog post tweet bot&lt;/a&gt; to include tags. Each post in this blog includes tags, and each of these is a link to a tag page with a &lt;a href&#x3D;&quot;http://microformats.org/wiki/rel-tag&quot;&gt;&lt;code&gt;rel&#x3D;&amp;quot;tag&amp;quot;&lt;/code&gt;&lt;/a&gt; attribute. I updated the bot to download the new entry and select &lt;code&gt;rel&#x3D;&amp;quot;tag&amp;quot;&lt;/code&gt; elements for their text content. The selected text is then appended to the tweet as hash-tags.&lt;/p&gt;
&lt;p&gt;On the home life side of things, the little one had his third set of &lt;a href&#x3D;&quot;https://www.nhs.uk/conditions/vaccinations/childhood-vaccines-timeline/?tabname&#x3D;babies-and-toddlers&quot;&gt;vaccinations&lt;/a&gt; on Friday. A cold delayed the vaccines from the week before (which he weathered far better than my partner and I). The third set of vaccines like the first set. They&amp;#39;re painful, and can lead to a fever. We&amp;#39;ve spent most of this weekend trying to keep him comfortable, the fever low, and the injection sites as pain free as possible. He&amp;#39;s almost back to his usual cheerful self now. It should go without saying that these vaccinations are well worth a short period of discomfort.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1553686244521</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1553686244521"/>
    <title>Note 14</title>
    <published>2019-03-27T11:30:44Z</published>
    <updated>2019-03-27T11:30:44Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I might make the repository for this blog open soon. It&amp;#39;s embarrassing in places, but I&amp;#39;d still like to show what I&amp;#39;ve done and compare notes...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/the-code-for-this-blog-is-now-public</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/the-code-for-this-blog-is-now-public"/>
    <title>The code for this blog is now public</title>
    <published>2019-03-28T19:26:08Z</published>
    <updated>2019-03-28T19:26:08Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I made the source code for this site public! You can find it
&lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/&quot;&gt;here&lt;/a&gt;. I&amp;#39;ve
&lt;a href&#x3D;&quot;/tags/aboutthisblog&quot;&gt;written at length&lt;/a&gt; about how I&amp;#39;ve built this, and having
the code makes it easier to point to particular lines. I hope it&amp;#39;ll
inspire &lt;em&gt;you&lt;/em&gt; to do the same!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1556718123822</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1556718123822"/>
    <title>Note 15</title>
    <published>2019-05-01T13:42:03Z</published>
    <updated>2019-05-01T13:42:03Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Another possible set of articles I could write is on tips for writing kubernetes configuration files. I&amp;#39;ve accumulated a number of them over the last few years.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/how-i-schedule-posts-using-github-actions</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/how-i-schedule-posts-using-github-actions"/>
    <title>How I schedule posts using GitHub Actions</title>
    <published>2019-06-15T22:00:00Z</published>
    <updated>2019-06-15T22:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;In the past &lt;a href&#x3D;&quot;/blog/how-i-schedule-posts-using-atd&quot;&gt;I used atd&lt;/a&gt; to schedule the
publication of my blog posts. When I moved to Netlify I lost the ability to
schedule posts, and didn&amp;#39;t think about it until
&lt;a href&#x3D;&quot;https://twitter.com/qubyte/status/1139904277894369281&quot;&gt;a recent conversation on twitter&lt;/a&gt; with &lt;a href&#x3D;&quot;https://remysharp.com/&quot;&gt;Remy Sharp&lt;/a&gt;. Remy asked
how to schedule blog posts for static sites and it got me thinking.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve been using GitHub actions recently, and one workflow trigger it provides
is a &lt;a href&#x3D;&quot;https://developer.github.com/actions/managing-workflows/creating-and-cancelling-a-workflow/#scheduling-a-workflow&quot;&gt;cron-like schedule&lt;/a&gt;. I wrote a little npm script to read a
directory full of scheduled posts. Each post has its publication date checked,
and if it&amp;#39;s in the past, the script moves the post to the published posts
directory (at which point Netlify kicks in). The script takes advantage of the
token provided by the &lt;a href&#x3D;&quot;https://developer.github.com/actions/creating-github-actions/accessing-the-runtime-environment/#environment-variables&quot;&gt;actions environment&lt;/a&gt; to make two requests,
one to recreate the file in the posts directory, and one to delete the scheduled
file using the GitHub &lt;a href&#x3D;&quot;https://developer.github.com/v3/repos/contents/&quot;&gt;Contents API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sadly, there&amp;#39;s no way to &lt;em&gt;move&lt;/em&gt; a file using the Contents API, so the
create-then-delete is necessary. This leads to two commits rather than just
one. It may be possible to do it in a single commit using the
&lt;a href&#x3D;&quot;https://developer.github.com/v3/git/trees/&quot;&gt;Trees API&lt;/a&gt;, but that&amp;#39;s a good deal more involved and I don&amp;#39;t mind
a little noise in the commit history.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;You can see the workflow using this script &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/master/.github/main.workflow&quot;&gt;here&lt;/a&gt;, and the publisher
&lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/master/publish-scheduled.js&quot;&gt;here&lt;/a&gt;&lt;/del&gt;. These scripts have  been replaced since GitHub actions have
changed! See &lt;a href&#x3D;&quot;/blog/cleaner-scheduled-posts-publication&quot;&gt;the later post&lt;/a&gt; for updated specifics.&lt;/p&gt;
&lt;p&gt;Of course, the scheduler published this post. ;)&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/a-new-service-to-handle-webmention-dispatch-for-you</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/a-new-service-to-handle-webmention-dispatch-for-you"/>
    <title>A new service to handle webmention dispatch for you</title>
    <published>2019-06-18T23:45:00Z</published>
    <updated>2019-06-18T23:45:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I really like webmentions. They provide a way to let folk know that you&amp;#39;re
writing about their blog posts. I see them as an alternative to comments which
encourages better discourse.&lt;/p&gt;
&lt;p&gt;Webmentions can be a pain to manage though, especially if you have a statically
generated site like this. For receiving mentions there&amp;#39;s the venerable
&lt;a href&#x3D;&quot;https://webmention.io&quot;&gt;webmention.io&lt;/a&gt; service. I use this to collect mentions
and then I check periodically and copy mentions over to the metadata of the
targeted post.&lt;/p&gt;
&lt;p&gt;To dispatch webmentions I
&lt;a href&#x3D;&quot;https://glitch.com/edit/#!/send-webmentions&quot;&gt;made a glitch&lt;/a&gt;, which I&amp;#39;ve
&lt;a href&#x3D;&quot;/blog/about-this-blog-3&quot;&gt;written about before&lt;/a&gt;. It&amp;#39;s closely coupled to the URL
structure of my blog and how it compiles webmentions into pages, so I&amp;#39;ll stick
with it but it&amp;#39;s not so useful for others except as a reference.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;ve just decided to include webmentions on your blog, then Remy Sharp
created &lt;a href&#x3D;&quot;https://webmention.app&quot;&gt;webmention.app&lt;/a&gt; to automate searching for links
and dispatching webmentions for a page. If I were starting now I&amp;#39;d use it! You
can read more about it &lt;a href&#x3D;&quot;https://remysharp.com/2019/06/18/send-outgoing-webmentions&quot;&gt;in Remy&amp;#39;s own words&lt;/a&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1561110065925</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1561110065925"/>
    <title>Note 16</title>
    <published>2019-06-21T09:41:05Z</published>
    <updated>2019-06-21T09:41:05Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Idea: Set up a web hook for webmention.io which posts to a glitch which in turn opens an issue on the GitHub repository for this blog.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1561404761906</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1561404761906"/>
    <title>Note 17</title>
    <published>2019-06-24T19:32:41Z</published>
    <updated>2019-06-24T19:32:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Now I’ve got a handle on the GitHub contents API, it’s tempting to build an editor into my blog. Could be a rabbit hole though...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1561508529254</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1561508529254"/>
    <title>Note 18</title>
    <published>2019-06-26T00:22:09Z</published>
    <updated>2019-06-26T00:22:09Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It’s possible using sed, jq, and date to read timestamps from scheduled posts and git mv and push them. This would be superior to the current Contents API approach because the content of the post will be properly attributed. It’s also lighter and agnostic with respect to git hosting.&lt;/p&gt;
&lt;p&gt;The command to get a time stamp will be something like: &lt;code&gt;sed -n ‘/^---$/,/^---$/p’ &amp;lt; post.md | sed ‘1d;$d’ | jq ‘.datetime’ | date -f -&lt;/code&gt;&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/cleaner-scheduled-posts-publication</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/cleaner-scheduled-posts-publication"/>
    <title>Cleaner scheduled posts publication</title>
    <published>2019-06-27T17:00:00Z</published>
    <updated>2019-06-27T17:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;In &lt;a href&#x3D;&quot;/blog/how-i-schedule-posts-using-github-actions&quot;&gt;a previous post&lt;/a&gt; I talked about an npm script I had written to be executed by a GitHub action:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sadly, there&amp;#39;s no way to move a file using the Contents API, so the create-then-delete is necessary. This leads to two commits rather than just one. It may be possible to do it in a single commit using the Trees API, but that&amp;#39;s a good deal more involved and I don&amp;#39;t mind a little noise in the commit history.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was fibbing! The additional commit does bother me, so I decided to rewrite the publisher to avoid noise in the git history.&lt;/p&gt;
&lt;p&gt;My next move was to actually try using the Trees API. It was mostly working before I read &lt;a href&#x3D;&quot;https://remysharp.com/2019/06/26/scheduled-and-draft-11ty-posts&quot;&gt;Remy Sharp&amp;#39;s post on the same topic&lt;/a&gt;. Remy uses the metadata of a post to determine if it should be published or not, rather than the directory it resides in.&lt;/p&gt;
&lt;p&gt;Using the same idea, the only thing which differentiates a published post and a scheduled post now is that the former has a timestamp in the past. By putting both kinds of post in the posts directory and tweaking the static site builder to filter out future posts, the generator takes care of what to render.&lt;/p&gt;
&lt;p&gt;The scheduler now does its thing just by checking the published sitemap against what it expects to see published. When the sitemap is outdated, the scheduler sends a build hook request to Netlify to refresh the rendered content. This means that the scheduler entirely avoids interacting with the GitHub API! No commits necessary to publish. No trees to map through.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;update-2019-08-11&quot;&gt;Update 2019-08-11&lt;/h2&gt;
&lt;p&gt;GitHub actions have been revised and are now written in YAML. The &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/publish-scheduled-posts.yml&quot;&gt;workflow&lt;/a&gt; which handles publications has been updated accordingly, and the publisher script &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/main/scripts/publish-scheduled.js&quot;&gt;moved to a scripts folder&lt;/a&gt; to help keep the project directory clean.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/updating-webmention-dispatch</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/updating-webmention-dispatch"/>
    <title>Updating webmention dispatch</title>
    <published>2019-07-04T18:00:00Z</published>
    <updated>2019-07-04T18:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I mentioned &lt;a href&#x3D;&quot;https://webmention.app&quot;&gt;a new service which handles webmentions&lt;/a&gt; in a
&lt;a href&#x3D;&quot;/blog/a-new-service-to-handle-webmention-dispatch-for-you&quot;&gt;previous post&lt;/a&gt;. I decided to replace
&lt;a href&#x3D;&quot;https://glitch.com/~send-webmentions&quot;&gt;the glitch I&amp;#39;ve been using&lt;/a&gt; for
&lt;a href&#x3D;&quot;https://glitch.com/~lean-send-webmentions&quot;&gt;one which is much leaner&lt;/a&gt;. It uses
&lt;a href&#x3D;&quot;https://github.com/remy/wm&quot;&gt;the library which powers webmention.app&lt;/a&gt; to handle webmention (and also
older technologies like pingback) endpoint discovery and mention dispatch so I
took this opportunity to ditch my own discovery code.&lt;/p&gt;
&lt;p&gt;I also decided to leave out some features. The earlier glitch would check old
posts for new mentions, filter out certain URLs like rendered webmentions from
others and so on. This all required a database and meant that posts got checked
more than once, which took a long time (beyond the timeout of a glitch run.&lt;/p&gt;
&lt;p&gt;The new glitch keeps a list of posts it&amp;#39;s already sent mentions for. This means
that each post is only scanned once after publication. That avoids the issue of
filtering out certain URLs (the rendered webmentions of others for example)
since a newly published post won&amp;#39;t have any mentions yet. It&amp;#39;s also a little
more portable. If you like the idea of using a sitemap.txt to drive a record of
which posts have been handled, then &lt;a href&#x3D;&quot;https://glitch.com/~lean-send-webmentions&quot;&gt;take a look&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;P.S. All the glitches I use to add functionality to my blog can be found
&lt;a href&#x3D;&quot;https://glitch.com/@qubyte/qubyte-codes&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1562766320160</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1562766320160"/>
    <title>Note 19</title>
    <published>2019-07-10T13:45:20Z</published>
    <updated>2019-07-10T13:45:20Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I think it&amp;#39;s time I started writing some tests for the static site generator.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1563274950728</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1563274950728"/>
    <title>Note 20</title>
    <published>2019-07-16T11:02:30Z</published>
    <updated>2019-07-16T11:02:30Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I think I&amp;#39;ll work on putting some of the sharing stuff at the bottom of each post into a &lt;details&gt; element at the next homebrew website club. That might mean splitting the webmention stuff up from sharing links. I&amp;#39;m not sure yet...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1563976504384</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1563976504384"/>
    <title>Note 21</title>
    <published>2019-07-24T13:55:04Z</published>
    <updated>2019-07-24T13:55:04Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I like the idea of a links page for things I&amp;#39;ve read and think others might enjoy. I&amp;#39;ll probably build one soon.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1564517844324</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1564517844324"/>
    <title>Link to https://www.cassie.codes/posts/creating-my-logo-animation/</title>
    <published>2019-07-30T20:17:24Z</published>
    <updated>2019-07-30T20:17:24Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&lt;a href&#x3D;&quot;https://qubyte.codes/links/1564517844324&quot;&gt;Creating my logo animation - cassie.codes&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1564613724849</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1564613724849"/>
    <title>Link to http://newadventuresconf.com/2019/coverage/jeremy/</title>
    <published>2019-07-31T22:55:24Z</published>
    <updated>2019-07-31T22:55:24Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I saw a preview of this talk back at the first edition of Codebar Brighton Talks. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1564613724849&quot;&gt;Building - Jeremy Keith&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1564652847401</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1564652847401"/>
    <title>Note 22</title>
    <published>2019-08-01T09:47:27Z</published>
    <updated>2019-08-01T09:47:27Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Idea for later: I need to markup each publication in the publications page as an &lt;a href&#x3D;&quot;http://microformats.org/wiki/h-cite&quot;&gt;h-cite&lt;/a&gt;.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1564665094733</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1564665094733"/>
    <title>Link to https://timkadlec.com/remembers/2019-01-09-the-ethics-of-performance/</title>
    <published>2019-08-01T13:11:34Z</published>
    <updated>2019-08-01T13:11:34Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I think about the ethics of performance and compatibility on the web a lot. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1564665094733&quot;&gt;The Ethics of Web Performance - TimKadlec.com&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1564967464837</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1564967464837"/>
    <title>Note 23</title>
    <published>2019-08-05T01:11:04Z</published>
    <updated>2019-08-05T01:11:04Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Next up, I’m considering micropub for posting full article entries. Hosting an editor and managing storage for drafts is a bigger task, but I can increment on that. Posts would be in markdown though. I wonder if that’s considered bad...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1565014451187</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1565014451187"/>
    <title>Link to https://resilientwebdesign.com/</title>
    <published>2019-08-05T14:14:11Z</published>
    <updated>2019-08-05T14:14:11Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A book you can read in your browser! Much of the infomation found in a talk I bookmarked here, for if you prefer to read (as I do) than to watch. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1565014451187&quot;&gt;Resilient Web Design - Jeremy Keith&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1565277896538</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1565277896538"/>
    <title>Link to https://tools.ietf.org/html/rfc822</title>
    <published>2019-08-08T15:24:56Z</published>
    <updated>2019-08-08T15:24:56Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I bang on about this a lot, but it still amazes me that headers, as used by HTTP/1.1 (which is still a current standard, not obsoleted by HTTP/2) are only a little different from the message format defined in this RFC, which is older than me! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1565277896538&quot;&gt;RFC 822 - STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1565372951377</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1565372951377"/>
    <title>Link to https://adactio.com/journal/15612</title>
    <published>2019-08-09T17:49:11Z</published>
    <updated>2019-08-09T17:49:11Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#x27;m going. Come along and find out more about the IndieWeb movement! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1565372951377&quot;&gt;Register for Indie Web Camp Brighton 2019 - Jeremy Keith&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1565645253946</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1565645253946"/>
    <title>Link to https://www.theatlantic.com/science/archive/2019/08/interlocking-puzzle-allowed-life-emerge/595945/</title>
    <published>2019-08-12T21:27:33Z</published>
    <updated>2019-08-12T21:27:33Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;quot;I find that utterly magical. It means that two of the essential components of life, a protocell’s membrane and its proteins, provided the conditions for each other to exist. By sticking to the fatty acids, the amino acids gave them stability.&amp;quot; &lt;a href&#x3D;&quot;https://qubyte.codes/links/1565645253946&quot;&gt;A New Clue to the Origins of Life - The Atlantic&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1565713489180</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1565713489180"/>
    <title>Link to https://mxb.dev/blog/indieweb-link-sharing/</title>
    <published>2019-08-13T16:24:49Z</published>
    <updated>2019-08-13T16:24:49Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It&amp;#x27;s great to see how other folk approach automate note and link sharing with their static sites. I use glitch and omnibear, but I rather like the idea of having a custom solution like Max does here for the posting side. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1565713489180&quot;&gt;IndieWeb Link Sharing | Max Böck&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1565730344621</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1565730344621"/>
    <title>Link to https://xclacksoverhead.org/</title>
    <published>2019-08-13T21:05:44Z</published>
    <updated>2019-08-13T21:05:44Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A custom response header to serve as a memorial. The name is inspired by The Clacks (semaphore towers) from Terry Pratchett&amp;#x27;s Discworld novels. His name is by far the most frequently memorialized with it. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1565730344621&quot;&gt;XClacksOverhead.org&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1565908695479</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1565908695479"/>
    <title>Link to https://mcfunley.com/choose-boring-technology</title>
    <published>2019-08-15T22:38:15Z</published>
    <updated>2019-08-15T22:38:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A bit of a classic now. On rereading this, I found the links out particularly fascinating. I&amp;#x27;d consider Node and (though I dislike it and reach for an even more boring SQL option when allowed) Mongo to be pretty boring these days. Of course, I agreed with the general message then and I still do. That&amp;#x27;s why this blog is served as plain old HTML and CSS! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1565908695479&quot;&gt;Choose Boring Technology - Dan McKinley&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1567686088263</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1567686088263"/>
    <title>Note 24</title>
    <published>2019-09-05T12:21:28Z</published>
    <updated>2019-09-05T12:21:28Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I have a real completionist streak to me, which is a lot of fun as it interacts with IndieWeb stuff like microformats. The next thing on my radar is WebSub. I&amp;#39;m keen to implement both the hub and publisher in one as a glitch.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1567774456225</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1567774456225"/>
    <title>Link to https://remysharp.com/2019/09/05/offline-listings</title>
    <published>2019-09-06T12:54:16Z</published>
    <updated>2019-09-06T12:54:16Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This is a extremely cool use of service workers. I also had no idea that DOMParser existed. Remy uses the service worker of his site to render an offline page with a list of cached posts whenever a browser is offline. It uses the cache managed by the service worker and parsing of responses in it to build a list of links. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1567774456225&quot;&gt;Offline listings - Remy Sharp&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1568711648282</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1568711648282"/>
    <title>Link to https://howoldami.glitch.me/</title>
    <published>2019-09-17T09:14:08Z</published>
    <updated>2019-09-17T09:14:08Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I tend to forget how old I am, so I made a little tool to tell me. The calculation is done in the browser without sending anything to a server (your data is safe). &lt;a href&#x3D;&quot;https://qubyte.codes/links/1568711648282&quot;&gt;How old am I? - qubyte&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1568837341195</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1568837341195"/>
    <title>Link to https://sallylait.com/blog/2019/09/18/exploring-web-monitization/</title>
    <published>2019-09-18T20:09:01Z</published>
    <updated>2019-09-18T20:09:01Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Perhaps it&amp;#x27;s time to replace my rel&amp;#x3D;payment anchors... &lt;a href&#x3D;&quot;https://qubyte.codes/links/1568837341195&quot;&gt;Exploring Web Monetization 💸 - Sally Lait&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1568888653222</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1568888653222"/>
    <title>Link to https://www.w3.org/TR/websub/</title>
    <published>2019-09-19T10:24:13Z</published>
    <updated>2019-09-19T10:24:13Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I don&amp;#x27;t use WebSub, yet... &lt;a href&#x3D;&quot;https://qubyte.codes/links/1568888653222&quot;&gt;WebSub - w3c&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1569081701808</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1569081701808"/>
    <title>Note 25</title>
    <published>2019-09-21T16:01:41Z</published>
    <updated>2019-09-21T16:01:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I wrote a while back about how promises and event emitters in Node.js can play badly together. Since then, Node and JS have changed enough that I should write a follow up post.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1570464551656</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1570464551656"/>
    <title>Note 26</title>
    <published>2019-10-07T16:09:11Z</published>
    <updated>2019-10-07T16:09:11Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Originally I was going to do a sort of dark-battenberg theme for dark mode on my blog, but now I&amp;#39;m rethinking. Since more and more devices are beginning to use OLED screens, I want dark mode to mean &amp;quot;mostly completely black&amp;quot; to save some energy on those devices.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/dark-mode</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/dark-mode"/>
    <title>Dark mode</title>
    <published>2019-10-12T01:30:00Z</published>
    <updated>2019-10-12T01:30:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;That&amp;#39;s right! After more than a year of talking about adding a dark mode I
finally did it. The wider support for
&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt; is what pushed me over the edge.
I&amp;#39;m also a slave to fashion.&lt;/p&gt;
&lt;p&gt;Initially I wanted to create a sort of dark-mode-battenberg theme, but after a
while using dark mode site I came to realise that my favourites are the ones
which use as much black as possible. It&amp;#39;s becoming more common to see OLED
screens in the wild, and these use less energy for black pixels since OLED
pixels are self illuminating. I settled on using black for the background, and
borrowing my light mode background colour for text and borders.&lt;/p&gt;
&lt;p&gt;The additions to the CSS aren&amp;#39;t too dramatic, but I learnt a few things along
the way&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-css&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;@media&lt;/span&gt; (&lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;prefers-color-scheme&lt;/span&gt;: dark) {
  &lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:root&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--background-color-main&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;#000&lt;/span&gt;;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--background-color-alt&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;#000&lt;/span&gt;;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--standout-color-main&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;hsl&lt;/span&gt;(
      &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-hue),
      &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-sat),
      &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-lum)
    );
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;--standout-color-alt&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;hsl&lt;/span&gt;(
      &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;calc&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-hue) - &lt;span class&#x3D;&quot;hljs-number&quot;&gt;30&lt;/span&gt;),
      &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-sat),
      &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;var&lt;/span&gt;(--base-background-lum)
    );
  }

  &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;img&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:not&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-selector-attr&quot;&gt;[src*&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;.svg&amp;quot;&lt;/span&gt;]&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;filter&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;brightness&lt;/span&gt;(.&lt;span class&#x3D;&quot;hljs-number&quot;&gt;8&lt;/span&gt;) &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;contrast&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;1.2&lt;/span&gt;);
  }

  pre, &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;input&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;button&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;background-color&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;#000&lt;/span&gt;;
  }

  &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;code&lt;/span&gt; {
    &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;filter&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;invert&lt;/span&gt;();
  }
}

&lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;body&lt;/span&gt; &amp;gt; &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;header&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;z-index&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-number&quot;&gt;1&lt;/span&gt;; &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;/* works around stacking context issue introduced by filters */&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The media query applies the content only when the OS is in dark mode and the
browser supports the media query. The &lt;code&gt;:root&lt;/code&gt; swaps the background colors to
standout colours, and makes the background black.&lt;/p&gt;
&lt;p&gt;Non-SVG images have their brightness and contrast adjusted with a filter. I
borrowed a dark mode filter from &lt;a href&#x3D;&quot;https://melanie-richards.com/&quot;&gt;Melanie Richards&lt;/a&gt; as
&lt;a href&#x3D;&quot;https://adactio.com/journal/15941&quot;&gt;suggested by Jeremy Keith&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Code listings are more difficult. I plan to do a bit more on these later, but
for now, since the regular code is on a white background, I give dark mode code
containers a black background and apply an invert filter to the code. I also set
the background color of inputs and buttons to black to fit.&lt;/p&gt;
&lt;p&gt;Finally, the filters had an unintended side effect. Filters place the elements
they apply to above positioned elements in the stacking context, which led to
images and code listings scrolling above the nav bar (which is part of a sticky
positioned header). To address this issue I resorted to using &lt;code&gt;z-index&lt;/code&gt;. While
the rule of thumb seems to be to avoid use of &lt;code&gt;z-index&lt;/code&gt;, I believe it&amp;#39;s
appropriate in this case.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1570967948506</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1570967948506"/>
    <title>Note 27</title>
    <published>2019-10-13T11:59:08Z</published>
    <updated>2019-10-13T11:59:08Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;m not a huge webpack fan, but I&amp;#39;m wondering if I might change my own mind my porting my static site generator to it. As it stands my generator tries to be as efficient as it can be, but only for a single shot compilation. It doesn&amp;#39;t cache artefacts. It looks like webpack, with a custom loader and plugins, might provide the necessary mechanics to make incremental builds while writing a blog post much faster.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1571477426347</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1571477426347"/>
    <title>Link to https://adactio.com/notes/15986</title>
    <published>2019-10-19T09:30:26Z</published>
    <updated>2019-10-19T09:30:26Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Getting started at IndieWebCamp Brighton 2019! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1571477426347&quot;&gt;Kicking off Indie Web Camp Brighton! - Adactio&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1571567063549</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1571567063549"/>
    <title>Like of https://adactio.com/notes/15986</title>
    <published>2019-10-20T10:24:23Z</published>
    <updated>2019-10-20T10:24:23Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/notes/15986&quot;&gt;https://adactio.com/notes/15986&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571572175617</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571572175617"/>
    <title>Reply to https://example.com</title>
    <published>2019-10-20T11:49:35Z</published>
    <updated>2019-10-20T11:49:35Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;Test reply again.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571576046012</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571576046012"/>
    <title>Reply to https://webmention.rocks/test/1</title>
    <published>2019-10-20T12:54:06Z</published>
    <updated>2019-10-20T12:54:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;Testing a reply.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571576744522</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571576744522"/>
    <title>Reply to https://adactio.com/notes/15986</title>
    <published>2019-10-20T13:05:44Z</published>
    <updated>2019-10-20T13:05:44Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;I’m hiding just out of frame to the right, which is good because I was probably blinking or gurning.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571577187224</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571577187224"/>
    <title>Reply to https://webmention.rocks/test/1</title>
    <published>2019-10-20T13:13:07Z</published>
    <updated>2019-10-20T13:13:07Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;Testing another reply.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571577372949</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571577372949"/>
    <title>Reply to https://adactio.com/notes/15986</title>
    <published>2019-10-20T13:16:12Z</published>
    <updated>2019-10-20T13:16:12Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;I&amp;amp;#39;m hiding just out of frame on the right, which is good because I was probably blinking or gurning.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571579274076</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571579274076"/>
    <title>Reply to https://webmention.rocks/test/1</title>
    <published>2019-10-20T13:47:54Z</published>
    <updated>2019-10-20T13:47:54Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;This is a test reply from an Apple shortcut.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571583835929</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571583835929"/>
    <title>Reply to https://webmention.rocks/test/1</title>
    <published>2019-10-20T15:03:55Z</published>
    <updated>2019-10-20T15:03:55Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;Pre show and tell test reply.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1571586181225</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1571586181225"/>
    <title>Reply to https://webmention.rocks/test/2</title>
    <published>2019-10-20T15:43:01Z</published>
    <updated>2019-10-20T15:43:01Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;This is a demo reply.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1571630601976</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1571630601976"/>
    <title>Link to https://brid.gy/about#publish</title>
    <published>2019-10-21T04:03:21Z</published>
    <updated>2019-10-21T04:03:21Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Bridgy looks like an interesting way to handle syndication. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1571630601976&quot;&gt;Bridgy looks like an interesting way to handle syndication.&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1571656054793</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1571656054793"/>
    <title>Like of https://adactio.com/notes/15990</title>
    <published>2019-10-21T11:07:34Z</published>
    <updated>2019-10-21T11:07:34Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/notes/15990&quot;&gt;https://adactio.com/notes/15990&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/indiewebcamp-brighton-2019</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/indiewebcamp-brighton-2019"/>
    <title>IndieWebCamp Brighton 2019</title>
    <published>2019-10-21T16:20:00Z</published>
    <updated>2019-10-21T16:20:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I had a great time last weekend at IndieWebCamp (IWC) Brighton. The first day
was &lt;a href&#x3D;&quot;https://indieweb.org/2019/Brighton/Schedule&quot;&gt;filled with discussions&lt;/a&gt; on various IndieWeb related topics. I
attended discussions on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How IndieWebCamp should respond to the climate crisis.&lt;/li&gt;
&lt;li&gt;Storage and display of personal tracking data.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Local first&lt;/em&gt; web and how to define the term.&lt;/li&gt;
&lt;li&gt;License detection.&lt;/li&gt;
&lt;li&gt;Integrating Apple shortcuts (scripts) into your IndieWeb workflows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Outcomes were interesting for some of these. For the first there was discussion
around downplaying more carbon intensive modes of transport, and attending
IWC before or after another event to make journeys more worthwhile (this is
already often the case). Another idea was to estimate the greenhouse gas output
of travel to and from IWC for all attendees, but there was disagreement over how
to measure this when attending other events too.&lt;/p&gt;
&lt;p&gt;Storage and display of tracking data was interesting, but perhaps less relevant
to me. I&amp;#39;m not so interested in tracking where I go, my weight, and so forth,
though that might change in the future. The discussion eventually focussed on
how data should be summarized.&lt;/p&gt;
&lt;p&gt;The local first discussion was one I was note taking for. I&amp;#39;m using my old
MacBook Air since I just finished a job and haven&amp;#39;t started my new one yet, so
I&amp;#39;m between work laptops. The MBA is nearly 10 years old and still going strong,
but it has a Japanese keyboard layout, and after being on a British layout for
the last few years my ability to touch type is a little hampered. I mostly
managed to keep up but missed the discussion since my focus was on taking notes.
Where local first seems to shine is in content creation. For example, I could
write a post or a note on a train with bad mobile coverage, and my device would
stash the content and POST it whenever it next connects. &lt;a href&#x3D;&quot;https://adactio.com&quot;&gt;Jeremy Keith&lt;/a&gt;
mentioned &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/API/SyncManager&quot;&gt;background sync&lt;/a&gt; as an emerging option to do this (I
must look into this).&lt;/p&gt;
&lt;p&gt;License detection was interesting. There was some discussion over whether a
microformat would be the right option, or a rel-bookmark style rel, since in
that unusual case the rel isn&amp;#39;t singular in a page. The latter has a potentially
confusing resolution algorithm though. The issue of actually parsing licenses
was avoided. We were mostly concerned with how to use multiple licenses within
a page.&lt;/p&gt;
&lt;p&gt;The final session on the day was about integrating micropub with Apple
shortcuts. I found this extremely interesting. I&amp;#39;d considered it in passing, but
&lt;a href&#x3D;&quot;https://rosemaryorchard.com/&quot;&gt;Rosemary Orchard&lt;/a&gt; had a comprehensive set of actions to automate
IndieAuth and posting to &lt;a href&#x3D;&quot;https://indieweb.org/Micropub&quot;&gt;micropub&lt;/a&gt; endpoints. This would be the
inspiration for my hack day work.&lt;/p&gt;
&lt;p&gt;The second day was for hacking on your own stuff, and helping each other out
when possible. My &lt;a href&#x3D;&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; knowledge came in handy a few times so I was
glad to be helpful!&lt;/p&gt;
&lt;p&gt;I decided to update this blog to handle likes and replies. At the time of
writing you can see these as their own sections in the navigation bar. I am
considering collecting everything into a single filterable stream though, so
these may eventually go away. Likes and replies are created by
&lt;a href&#x3D;&quot;https://glitch.com/~micropub-server&quot;&gt;my micropub endpoint&lt;/a&gt;, so the first part of this task was to update
that glitch to understand and handle payloads of those kinds. As with notes and
links/bookmarks, it uses the &lt;a href&#x3D;&quot;https://developer.github.com/v3/repos/contents/&quot;&gt;GitHub contents API&lt;/a&gt; to create JSON
files which are compiled to pages of my site. My static site generator gained
a couple more templates to render these new types of content.&lt;/p&gt;
&lt;p&gt;This made it possible to post likes and replies using existing micropub clients.
I use &lt;a href&#x3D;&quot;https://omnibear.com/&quot;&gt;Omnibear&lt;/a&gt; as a Firefox extension in my laptop browser. This
client uses the current page to fill in details to make creating likes and
replies much simpler. On mobile, I decided that a new set of shortcuts to be
used from the sharing panel in iOS/iPadOS. Like Omnibear, these benefit from
the context they&amp;#39;re used in. What I implemented is fairly primitive. The
shortcuts only use the URL of the current page. There&amp;#39;s no elegant way to get
hold of the title of a page like Omnibear can, but I&amp;#39;ll work on this later as an
enhancement.&lt;/p&gt;
&lt;p&gt;I implemented shortcuts for liking, replying, and bookmarking. The latter two
prompt me for additional text for my part of the reply or bookmark. I decided
not to make these shortcuts use IndieAuth. Instead I updated my glitch to accept
authentication using a shared secret when a special &lt;code&gt;short-circuit-auth&lt;/code&gt; header
is truthy. This means that existing clients continue to use IndieAuth, while my
own software can take a shortcut if I so choose. I might revisit this in the
future.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m enjoying the fruits of my work on the second day already. Likes and replies
are more social than the notes and bookmarks I had previously, so I hope this
leads to more interaction with other IndieWeb folk.&lt;/p&gt;
&lt;p&gt;With micropub fresh in my mind, I may enhance my micropub endpoint to accept
multipart bodies. This allows POSTS with media in to be sent all in one request
which shortcuts can make use of. Then I&amp;#39;ll be able to post pictures and not just
walls of text!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1571747474208</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1571747474208"/>
    <title>Note 28</title>
    <published>2019-10-22T12:31:14Z</published>
    <updated>2019-10-22T12:31:14Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The sea.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1571747469674.jpeg&quot; alt&#x3D;&quot;The sea.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1571748847109</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1571748847109"/>
    <title>Note 29</title>
    <published>2019-10-22T12:54:07Z</published>
    <updated>2019-10-22T12:54:07Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Hideous gnome.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1571748845724.jpeg&quot; alt&#x3D;&quot;Hideous gnome.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1571758295477</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1571758295477"/>
    <title>Note 30</title>
    <published>2019-10-22T15:31:35Z</published>
    <updated>2019-10-22T15:31:35Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Versioning edge case discovered on the Node.js website.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1571758293668.png&quot; alt&#x3D;&quot;A screenshot of the Node.js website showing the latest version behind the LTS version.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1571836887827</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1571836887827"/>
    <title>Note 31</title>
    <published>2019-10-23T13:21:27Z</published>
    <updated>2019-10-23T13:21:27Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Another nightmarish thing my partner bought.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1571836886275.jpeg&quot; alt&#x3D;&quot;A nightmarish pair of felt jester shoes for a baby.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1571865056303</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1571865056303"/>
    <title>Note 32</title>
    <published>2019-10-23T21:10:56Z</published>
    <updated>2019-10-23T21:10:56Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;An interesting local mural.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1571864988105.jpeg&quot; alt&#x3D;&quot;The concrete front garden of a local house is a beautiful mural of fish.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1571967276213</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1571967276213"/>
    <title>Link to https://www.jvt.me/posts/2019/10/20/indieweb-talk/</title>
    <published>2019-10-25T01:34:36Z</published>
    <updated>2019-10-25T01:34:36Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A nice introduction to the IndieWeb movement, with lots of motivation and enough depth to get a feel for how it works. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1571967276213&quot;&gt;The IndieWeb Movement: Owning Your Data and Being the Change You Want to See in the Web · Jamie Tanna | Software (Quality) Engineer&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1572040436848</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1572040436848"/>
    <title>Like of https://indieweb.org/this-week/2019-10-25.html</title>
    <published>2019-10-25T21:53:56Z</published>
    <updated>2019-10-25T21:53:56Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://indieweb.org/this-week/2019-10-25.html&quot;&gt;https://indieweb.org/this-week/2019-10-25.html&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1572097068236</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1572097068236"/>
    <title>Note 33</title>
    <published>2019-10-26T13:37:48Z</published>
    <updated>2019-10-26T13:37:48Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Between Lewes and Newhaven by train.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1572097010283.jpeg&quot; alt&#x3D;&quot;A view of fields seen from the train between Lewes and Newhaven.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1572217565280</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1572217565280"/>
    <title>Note 34</title>
    <published>2019-10-27T23:06:05Z</published>
    <updated>2019-10-27T23:06:05Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Testing out syndication of a note from my blog.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1572272955384</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1572272955384"/>
    <title>Note 35</title>
    <published>2019-10-28T14:29:15Z</published>
    <updated>2019-10-28T14:29:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p lang&#x3D;&quot;ja&quot;&gt;（日本語で）最初のノート。&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1572276226823</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1572276226823"/>
    <title>Link to https://hacks.mozilla.org/2018/10/dweb-identity-for-the-decentralized-web-with-indieauth/</title>
    <published>2019-10-28T15:23:46Z</published>
    <updated>2019-10-28T15:23:46Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;An article by Aaron Parecki introducing IndieAuth. Nice primer. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1572276226823&quot;&gt;Dweb: Identity for the Decentralized Web with IndieAuth - Mozilla Hacks&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1572354072541</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1572354072541"/>
    <title>Like of https://adactio.com/notes/16034</title>
    <published>2019-10-29T13:01:12Z</published>
    <updated>2019-10-29T13:01:12Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/notes/16034&quot;&gt;https://adactio.com/notes/16034&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1572367258698</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1572367258698"/>
    <title>Link to https://richard.dallaway.com/2019/10/24/rust-embedded-1.html</title>
    <published>2019-10-29T16:40:58Z</published>
    <updated>2019-10-29T16:40:58Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The Brighton (UK) Rust User Group is doing an embedded project to program Christmas tree lights in Rust. I had to leave a bit early, But Richard documented the session. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1572367258698&quot;&gt;Brighton Rust: Embedded systems, day 1 - Richard Dallaway&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1572466651337</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1572466651337"/>
    <title>Like of https://www.jhsheridan.com/officially-indieweb/</title>
    <published>2019-10-30T20:17:31Z</published>
    <updated>2019-10-30T20:17:31Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://www.jhsheridan.com/officially-indieweb/&quot;&gt;https://www.jhsheridan.com/officially-indieweb/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1572525134921</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1572525134921"/>
    <title>Note 36</title>
    <published>2019-10-31T12:32:14Z</published>
    <updated>2019-10-31T12:32:14Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Work in progress.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1572525111247.jpeg&quot; alt&#x3D;&quot;A partly painted room.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1572620965183</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1572620965183"/>
    <title>Like of https://aaronparecki.com/2019/11/01/6/indiewebcamp-brighton</title>
    <published>2019-11-01T15:09:25Z</published>
    <updated>2019-11-01T15:09:25Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://aaronparecki.com/2019/11/01/6/indiewebcamp-brighton&quot;&gt;https://aaronparecki.com/2019/11/01/6/indiewebcamp-brighton&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1572716949161</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1572716949161"/>
    <title>Note 37</title>
    <published>2019-11-02T17:49:09Z</published>
    <updated>2019-11-02T17:49:09Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Next thing to do: add handling for h-reviews so I can rate mince pies.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1572852290796</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1572852290796"/>
    <title>Note 38</title>
    <published>2019-11-04T07:24:50Z</published>
    <updated>2019-11-04T07:24:50Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Morning mist over the Sussex Countryside.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1572852242959.jpeg&quot; alt&#x3D;&quot;Morning mist over the Sussex countryside, viewed from a railway viaduct.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1573118796275</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1573118796275"/>
    <title>Note 39</title>
    <published>2019-11-07T09:26:36Z</published>
    <updated>2019-11-07T09:26:36Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Reminder for later... Paths are predicable enough in my setup that I can improve the netlify config to apply headers only to HTML by batching routes with wildcards.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1573216120251</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1573216120251"/>
    <title>Note 40</title>
    <published>2019-11-08T12:28:40Z</published>
    <updated>2019-11-08T12:28:40Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;No context conference slide.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1573216080511.jpeg&quot; alt&#x3D;&quot;Conference slide reading “why”.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/ffconf-2019</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/ffconf-2019"/>
    <title>ffconf 2019</title>
    <published>2019-11-15T13:16:33Z</published>
    <updated>2019-11-15T13:16:33Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The day was packed with interesting and diverse talks, and there&amp;#39;s just too much
to talk about in a single post. It&amp;#39;s definitely worth searching around for other
blog posts to see other&amp;#39;s takes on the day!&lt;/p&gt;
&lt;p&gt;The talk which I find myself thinking about the most was
&lt;a href&#x3D;&quot;https://charlottedann.com/&quot;&gt;Charlotte Dann&amp;#39;s&lt;/a&gt;. The journey from exploration in generative
art to producing &lt;a href&#x3D;&quot;https://hexatope.io/&quot;&gt;custom real-world jewelery&lt;/a&gt; was inspiring! It seems
to have inspired others too, since it precipitated the creation of
&lt;a href&#x3D;&quot;https://mobile.twitter.com/GeneratorBtn&quot;&gt;a creative coding meetup&lt;/a&gt; here in brighton, which I hope to find
some time to get involved with.&lt;/p&gt;
&lt;p&gt;In the spirit of that talk, here&amp;#39;s a quick Conway&amp;#39;s Game of Life demo (requires
JavaScript to be enabled). It loops around (so the world looks square but it&amp;#39;s
really a torus). It&amp;#39;s pretty basic! In the spirit of the talk, I hope to apply
similar rules to different tilings. Refresh to restart.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/generative-art-piece-domains-of-points-with-spokes</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/generative-art-piece-domains-of-points-with-spokes"/>
    <title>Generative art piece: domains of points with spokes</title>
    <published>2019-11-30T21:15:00Z</published>
    <updated>2019-11-30T21:15:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The graphic below is generated randomly and rendered as an SVG. Occasionally it
glitches, but that&amp;#39;s all part of the fun! It was inspired by a graphic seen in
&lt;a href&#x3D;&quot;https://charlottedann.com/&quot;&gt;Charlotte Dann&amp;#39;s&lt;/a&gt; &lt;a href&#x3D;&quot;https://www.youtube.com/watch?v&#x3D;BZNKLvqh8ts&amp;list&#x3D;PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&quot;&gt;ffconf 2019 talk&lt;/a&gt; at about &lt;a href&#x3D;&quot;https://www.youtube.com/watch?v&#x3D;BZNKLvqh8ts&amp;list&#x3D;PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&amp;t&#x3D;385&quot;&gt;6:25&lt;/a&gt;
in. See &lt;a href&#x3D;&quot;/blog/the-maths-of-domains-of-points-with-spokes&quot;&gt;the next post&lt;/a&gt; for a description of the maths I used to do this.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/the-maths-of-domains-of-points-with-spokes</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/the-maths-of-domains-of-points-with-spokes"/>
    <title>The maths of Domains of points with spokes</title>
    <published>2019-12-02T00:00:00Z</published>
    <updated>2019-12-02T00:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It&amp;#39;s been a while since &lt;a href&#x3D;&quot;/blog/advent-of-code-2017-day-20-task-2&quot;&gt;my last maths heavy article&lt;/a&gt;. I
enjoy writing these but struggle to find the time to write many.&lt;/p&gt;
&lt;p&gt;This article is on the generative art piece in &lt;a href&#x3D;&quot;/blog/generative-art-piece-domains-of-points-with-spokes&quot;&gt;my last post&lt;/a&gt;. I based
this on my memory of an image of a piece in &lt;a href&#x3D;&quot;https://www.youtube.com/watch?v&#x3D;BZNKLvqh8ts&amp;list&#x3D;PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&amp;t&quot;&gt;a talk by&lt;/a&gt;
&lt;a href&#x3D;&quot;https://charlottedann.com/&quot;&gt;Charlotte Dann&lt;/a&gt;. It was a piece of art actually drawn using a
pen and paper plotter (which I love), but I lack these tools at present, so I
chose to use the browser instead (lucky you).&lt;/p&gt;
&lt;p&gt;In hindsight, I should have realized that the domains around points are like a
&lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/Voronoi_diagram&quot;&gt;Voronoi diagram&lt;/a&gt;. I forged ahead having forgotten all about them, so
please excuse my unorthodox terminology below. Looking back on &lt;a href&#x3D;&quot;https://www.youtube.com/watch?v&#x3D;BZNKLvqh8ts&amp;list&#x3D;PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&amp;t&quot;&gt;the talk&lt;/a&gt;
that inspired this, my memory of the drawing wasn&amp;#39;t quite correct. See the real
deal at about &lt;a href&#x3D;&quot;https://www.youtube.com/watch?v&#x3D;BZNKLvqh8ts&amp;list&#x3D;PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&amp;t&#x3D;385&quot;&gt;6:25&lt;/a&gt; in.&lt;/p&gt;
&lt;p&gt;Based on my memory, the image was of a number of points. From each point lines
(I&amp;#39;ll call these spokes) radiated outwards until they reached the edge of the
domain of a point. The &amp;quot;domain&amp;quot; as I call it is an area around a point bounded
by the half way lines between it and neighbouring points, or the bounding box of
the image.&lt;/p&gt;
&lt;p&gt;The case of neighbouring points looks like:&lt;/p&gt;
&lt;div class&#x3D;&quot;diagram&quot;&gt;
  &lt;svg role&#x3D;&quot;img&quot; aria-labelledby&#x3D;&quot;diagram-title-1&quot; width&#x3D;&quot;200&quot; height&#x3D;&quot;200&quot; viewBox&#x3D;&quot;0 0 100 100&quot;&gt;
    &lt;title id&#x3D;&quot;diagram-title-1&quot;&gt;Spokes radiating from a point to the halfway line between the point and another point.&lt;/title&gt;
    &lt;circle cx&#x3D;&quot;5&quot; cy&#x3D;&quot;50&quot; r&#x3D;&quot;2&quot;/&gt;
    &lt;circle cx&#x3D;&quot;95&quot; cy&#x3D;&quot;50&quot; r&#x3D;&quot;2&quot;/&gt;
    &lt;path d&#x3D;&quot;M 5 50 h 45 M 5 50 l 45 -9.95 M 5 50 l 45 -20.71 M 5 50 l 45 -33.41 M 5 50 l 45 -50 M 5 50 l 45 33.41 M 5 50 l 45 20.71 M 5 50 l 45 9.95 M 5 50 l 45 50&quot;/&gt;
  &lt;/svg&gt;
&lt;/div&gt;

&lt;p&gt;I&amp;#39;ve drawn spokes radiating from the point on the left up to the domain edge
shared by both points. A way of looking at this is to draw the domain boundary.&lt;/p&gt;
&lt;div class&#x3D;&quot;diagram&quot;&gt;
  &lt;svg role&#x3D;&quot;img&quot; aria-labelledby&#x3D;&quot;diagram-title-2&quot; width&#x3D;&quot;200&quot; height&#x3D;&quot;200&quot; viewBox&#x3D;&quot;0 0 100 100&quot;&gt;
    &lt;title id&#x3D;&quot;diagram-title-2&quot;&gt;The boundary between two points.&lt;/title&gt;
    &lt;circle cx&#x3D;&quot;5&quot; cy&#x3D;&quot;50&quot; r&#x3D;&quot;2&quot;/&gt;
    &lt;circle cx&#x3D;&quot;95&quot; cy&#x3D;&quot;50&quot; r&#x3D;&quot;2&quot;/&gt;
    &lt;path d&#x3D;&quot;M 50 0 v 100&quot; stroke-dasharray&#x3D;&quot;5, 5&quot;/&gt;
  &lt;/svg&gt;
&lt;/div&gt;

&lt;p&gt;What I need is, given a point and an angle of a spoke radiating from it, how
long will that spoke be to reach the closest boundary? Remember, points are
randomly located!&lt;/p&gt;
&lt;p&gt;The equation of the boundary line is related to the equation of a line between
the two points, which is:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-1&quot; aria-label&#x3D;&quot;y(x) - y_0 &#x3D; \frac{x_0 - x_1}{y_0 - y_1} (x - x_0)&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;y(x) - y_0 &#x3D; \frac{x_0 - x_1}{y_0 - y_1} (x - x_0)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;Where the numeric subscripts &lt;math&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/math&gt; and &lt;math&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/math&gt; are for the two points, and we&amp;#39;re
interested in the spokes radiating from point &lt;math&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/math&gt;.&lt;/p&gt;
&lt;p&gt;Any two fixed points on the line can be used to place the line. For example,
it&amp;#39;s just as valid to use the coordinates of the second point (note the
subscripts):&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-2&quot; aria-label&#x3D;&quot;y(x) - y_1 &#x3D; \frac{x_0 - x_1}{y_0 - y_1} (x - x_1)&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;y(x) - y_1 &#x3D; \frac{x_0 - x_1}{y_0 - y_1} (x - x_1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;The equation for the boundary line is perpendicular to the point-to-point line
and halfway along it. Since the lines cross half-way along the point-to-point
line, we can use that location to fix the perpendicular line:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-3&quot; aria-label&#x3D;&quot;\left(\frac{x_0 + x_1}{2}, \frac{y_0 + y_1}{2}\right)&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;true&quot;&gt;(&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;mo separator&#x3D;&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;true&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;\left(\frac{x_0 + x_1}{2}, \frac{y_0 + y_1}{2}\right)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;The slope is perpendicular to the slope of the point-to-point line:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-4&quot; aria-label&#x3D;&quot;\frac{x_1 - x_0}{y_0 - y_1}&quot;&gt;&lt;semantics&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;\frac{x_1 - x_0}{y_0 - y_1}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;Putting these together and rearranging gave me the equation for a boundary
between two points:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-5&quot; aria-label&#x3D;&quot;y(x) &#x3D; \frac{x_1 - x_0}{y_0 - y_1}\left(x - \frac{x_0 + x_1}{2}\right) + \frac{y_0 + y_1}{2}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;true&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;true&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;y(x) &#x3D; \frac{x_1 - x_0}{y_0 - y_1}\left(x - \frac{x_0 + x_1}{2}\right) + \frac{y_0 + y_1}{2}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;With the equation for a boundary, I needed an equation for each spoke. My intent
was to use these simultaneous equations to find the solution for the length of
the spoke.&lt;/p&gt;
&lt;p&gt;The equation for the boundary line is in Cartesian coordinates &lt;math&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo separator&#x3D;&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/math&gt;, but
given that we have an angle, and we&amp;#39;re looking for a length, it&amp;#39;s more natural
to think of a spoke in polar coordinates &lt;math&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mo separator&#x3D;&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant&#x3D;&quot;normal&quot;&gt;Θ&lt;/mi&gt;&lt;mspace&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/math&gt;. In fact, taking the point as
the origin, &lt;math&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/math&gt; is the length we&amp;#39;re actually looking for.&lt;/p&gt;
&lt;p&gt;A line in polar coordinates can be connected to Cartesian coordinates using the
following equations:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-6&quot; aria-label&#x3D;&quot;\begin{align*}
x - x_0 &amp;amp;&#x3D; r \cos \theta\\
y - y_0 &amp;amp;&#x3D; r \sin \theta
\end{align*}&quot;&gt;&lt;semantics&gt;&lt;mtable displaystyle&#x3D;&quot;true&quot; class&#x3D;&quot;tml-jot&quot;&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mrow&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mrow&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;mi&gt;cos&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mrow&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mrow&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;mi&gt;sin&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;/mtable&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;\begin{align*}
x - x_0 &amp;amp;&#x3D; r \cos \theta\\
y - y_0 &amp;amp;&#x3D; r \sin \theta
\end{align*}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;Substituting these into the boundary equation gave:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-7&quot; aria-label&#x3D;&quot;r \sin \theta + y_0 &#x3D; \frac{x_1 - x_0}{y_0 - y_1}\left(r \cos \theta + \frac{x_0 - x_1}{2}\right) + \frac{y_0 + y_1}{2}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mrow&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;mi&gt;sin&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;true&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mrow&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;mi&gt;cos&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;true&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;r \sin \theta + y_0 &#x3D; \frac{x_1 - x_0}{y_0 - y_1}\left(r \cos \theta + \frac{x_0 - x_1}{2}\right) + \frac{y_0 + y_1}{2}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;After some rearranging, the solution for &lt;math&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/math&gt; looks like:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-8&quot; aria-label&#x3D;&quot;r &#x3D; \frac{1}{2} \cdot \frac{(y_1 - y_0)^2 + (x_1 - x_0)^2}{(y_1 - y_0)\sin \theta + (x_1 - x_0) \cos \theta}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msup&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msup&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;sin&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mrow&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;mo fence&#x3D;&quot;true&quot; form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;cos&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;r &#x3D; \frac{1}{2} \cdot \frac{(y_1 - y_0)^2 + (x_1 - x_0)^2}{(y_1 - y_0)\sin \theta + (x_1 - x_0) \cos \theta}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;Since the equations for the bounding box edges are more simple:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-9&quot; aria-label&#x3D;&quot;\begin{align*}
x &amp;amp;&#x3D; 0\\
x &amp;amp;&#x3D; x_\text{max}\\
y &amp;amp;&#x3D; 0\\
y &amp;amp;&#x3D; y_\text{max}
\end{align*}&quot;&gt;&lt;semantics&gt;&lt;mtable displaystyle&#x3D;&quot;true&quot; class&#x3D;&quot;tml-jot&quot;&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mtext&gt;max&lt;/mtext&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mtext&gt;max&lt;/mtext&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;/mtable&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;\begin{align*}
x &amp;amp;&#x3D; 0\\
x &amp;amp;&#x3D; x_\text{max}\\
y &amp;amp;&#x3D; 0\\
y &amp;amp;&#x3D; y_\text{max}
\end{align*}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;The solutions for r with these bounding box equations are a little simpler too:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-10&quot; aria-label&#x3D;&quot;\begin{align*}
r &amp;amp;&#x3D; \frac{-x_0}{\cos\theta}\\
r &amp;amp;&#x3D; \frac{x_\text{max} - x_0}{\cos\theta}\\
r &amp;amp;&#x3D; \frac{-y_0}{\sin\theta}\\
r &amp;amp;&#x3D; \frac{y_\text{max} - y_0}{\sin\theta}
\end{align*}&quot;&gt;&lt;semantics&gt;&lt;mtable displaystyle&#x3D;&quot;true&quot; class&#x3D;&quot;tml-jot&quot;&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mo form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot; lspace&#x3D;&quot;0em&quot; rspace&#x3D;&quot;0em&quot;&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mi&gt;cos&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mtext&gt;max&lt;/mtext&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mi&gt;cos&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mo form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot; lspace&#x3D;&quot;0em&quot; rspace&#x3D;&quot;0em&quot;&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mi&gt;sin&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd class&#x3D;&quot;tml-right&quot;&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/mtd&gt;&lt;mtd class&#x3D;&quot;tml-left&quot;&gt;&lt;mrow&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mtext&gt;max&lt;/mtext&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;mi&gt;sin&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mspace width&#x3D;&quot;0.1667em&quot;&gt;&lt;/mspace&gt;&lt;/mrow&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;/mtable&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;\begin{align*}
r &amp;amp;&#x3D; \frac{-x_0}{\cos\theta}\\
r &amp;amp;&#x3D; \frac{x_\text{max} - x_0}{\cos\theta}\\
r &amp;amp;&#x3D; \frac{-y_0}{\sin\theta}\\
r &amp;amp;&#x3D; \frac{y_\text{max} - y_0}{\sin\theta}
\end{align*}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;p&gt;Given all these equations, for each spoke radiating from each point we need to
calculate &lt;math&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/math&gt; for boundaries with every other point and also the bounding box.
Spokes radiate &lt;em&gt;away&lt;/em&gt; from a point, so all negative values of &lt;math&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/math&gt; can be
discarded. Of the remaining possible values of &lt;math&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/math&gt;, the shortest wins!&lt;/p&gt;
&lt;p&gt;I settled on using 10 randomly placed points, each with 48 evenly spaced spokes.
I&amp;#39;ve added a gap between spoke ends (both with their point and the boundary) to
make the graphic look as I remember it. Points are not drawn.&lt;/p&gt;
&lt;p&gt;Finally, I used JavaScript to create an SVG and used line elements to draw each
spoke. A little inline CSS in the head applies the foreground colour of the page
(the colour of the text) to the lines. This is neat because it means the graphic
will look natural on the page in both light and dark modes (although I think it
has a certain art deco look to it in dark mode). This ease of theming is in
contrast to the effort it took to theme the &lt;a href&#x3D;&quot;ffconf-2019&quot;&gt;Game of Life&lt;/a&gt; demo in
a previous post.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1577809288352</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1577809288352"/>
    <title>Note 41</title>
    <published>2019-12-31T16:21:28Z</published>
    <updated>2019-12-31T16:21:28Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Fox in the Pavilion Gardens.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1577809248447.jpeg&quot; alt&#x3D;&quot;Fox in the Pavilion Gardens&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1579455053643</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1579455053643"/>
    <title>Note 42</title>
    <published>2020-01-19T17:30:53Z</published>
    <updated>2020-01-19T17:30:53Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Rainbow sunset with Venus.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1579455013882.jpeg&quot; alt&#x3D;&quot;Rainbow sunset with Venus visible.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1579511219094</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1579511219094"/>
    <title>Note 43</title>
    <published>2020-01-20T09:06:59Z</published>
    <updated>2020-01-20T09:06:59Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;From yesterday: Vegan buckwheat pancakes with caramelised banana, chai spiced coconut cream, coconut chips, and cacao nibs. Eaten at Black Mocha in Brighton.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1579511116404.jpeg&quot; alt&#x3D;&quot;Vegan buckwheat pancakes with caramelised banana, chai spiced coconut cream, coconut chips, and cacao nibs.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1580060799517</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1580060799517"/>
    <title>Note 44</title>
    <published>2020-01-26T17:46:39Z</published>
    <updated>2020-01-26T17:46:39Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Want: Recipes to label their ingredients in groups, so I can keep them together in the same bowls and save on washing up. Also good for knowing when things need to be ready.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1580476486159</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1580476486159"/>
    <title>Like of https://adactio.com/journal/16375</title>
    <published>2020-01-31T13:14:46Z</published>
    <updated>2020-01-31T13:14:46Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/journal/16375&quot;&gt;https://adactio.com/journal/16375&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1584877075362</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1584877075362"/>
    <title>Note 45</title>
    <published>2020-03-22T11:37:55Z</published>
    <updated>2020-03-22T11:37:55Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I began a sourdough starter yesterday. Due to a lack of whole wheat flour I used 50:50 very strong white flour and buckwheat flour. I’m hoping the latter will donate the yeast!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1584876886786.jpeg&quot; alt&#x3D;&quot;The beginnings of a sourdough starter in a jar.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1584877175472</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1584877175472"/>
    <title>Note 46</title>
    <published>2020-03-22T11:39:35Z</published>
    <updated>2020-03-22T11:39:35Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;After a day I’m supposed to discard half the sourdough and feed the remainder. This is the discard, and it’s already showing some signs of activity!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1584877105552.jpeg&quot; alt&#x3D;&quot;A discarded portion of the sourdough starter. Bubbles can be seen on the surface.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1584909386452</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1584909386452"/>
    <title>Note 47</title>
    <published>2020-03-22T20:36:26Z</published>
    <updated>2020-03-22T20:36:26Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;First batch of white chocolate and macadamia nut cookies!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1584909313900.jpeg&quot; alt&#x3D;&quot;A batch of home cooked white chocolate and macadamia nut cookies cooling on a baking tray.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1584910781459</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1584910781459"/>
    <title>Note 48</title>
    <published>2020-03-22T20:59:41Z</published>
    <updated>2020-03-22T20:59:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The sourdough starter is growing!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1584910743309.jpeg&quot; alt&#x3D;&quot;Sourdough starter, showing signs of growth in a glass jar.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1585762603424</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1585762603424"/>
    <title>Note 49</title>
    <published>2020-04-01T17:36:43Z</published>
    <updated>2020-04-01T17:36:43Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The sourdough starter is finally ready to use! It seemed to go a bit dormant, so I fed it some rye flour and it seems to have done the trick! The rubber band marks where the top was at its last feed six and a half hours ago.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1585762444474.jpeg&quot; alt&#x3D;&quot;My sourdough starter in a jar. A rubber band marks where the top was, and it has doubled in volume. Bubbles can be seen in it.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1585867238982</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1585867238982"/>
    <title>Note 50</title>
    <published>2020-04-02T22:40:38Z</published>
    <updated>2020-04-02T22:40:38Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;My first sourdough loaf!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1585867200330.jpeg&quot; alt&#x3D;&quot;A dome shaped sourdough loaf o. A cooling rack.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1585867307316</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1585867307316"/>
    <title>Note 51</title>
    <published>2020-04-02T22:41:47Z</published>
    <updated>2020-04-02T22:41:47Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It’s good! I got lucky!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1585867264039.jpeg&quot; alt&#x3D;&quot;A slice of my first sourdough loaf. It has good structure and a fine crust.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1587986063915</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1587986063915"/>
    <title>Note 52</title>
    <published>2020-04-27T11:14:23Z</published>
    <updated>2020-04-27T11:14:23Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I finally found a use for that physics PhD.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1587986010726.jpeg&quot; alt&#x3D;&quot;A paint brush taped to the end of a stick.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1590366122066</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1590366122066"/>
    <title>Note 53</title>
    <published>2020-05-25T00:22:02Z</published>
    <updated>2020-05-25T00:22:02Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Sourdough brioche. About 25cm in diameter.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1590366098816.jpeg&quot; alt&#x3D;&quot;Freshly baked sourdough brioche. About 25cm in diameter.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/generative-art-piece-bubbles</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/generative-art-piece-bubbles"/>
    <title>Generative art piece: bubbles</title>
    <published>2020-06-17T22:00:00Z</published>
    <updated>2020-06-17T22:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;An experiment into generating SVG circles which don&amp;#39;t overlap. It was timeboxed to an hour,
so it&amp;#39;s a little rough around the edges (but I think that adds to its charm).&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1592562340506</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1592562340506"/>
    <title>Link to https://mxb.dev/blog/the-return-of-the-90s-web/</title>
    <published>2020-06-19T10:25:40Z</published>
    <updated>2020-06-19T10:25:40Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A nice discussion about how old techniques like server rendering are coming back into fashion, and personal websites and communities. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1592562340506&quot;&gt;The Return of the 90s Web - Max Böck&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1593104782918</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1593104782918"/>
    <title>Link to https://www.cassie.codes/</title>
    <published>2020-06-25T17:06:22Z</published>
    <updated>2020-06-25T17:06:22Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Cassie&amp;#x27;s new blog looks great! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1593104782918&quot;&gt;Cassie Evans&amp;#x27;s Blog&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1594107603722</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1594107603722"/>
    <title>Like of https://ohhelloana.blog/overthinking-likes/</title>
    <published>2020-07-07T07:40:03Z</published>
    <updated>2020-07-07T07:40:03Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://ohhelloana.blog/overthinking-likes/&quot;&gt;https://ohhelloana.blog/overthinking-likes/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/superimposed-triangular-grids</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/superimposed-triangular-grids"/>
    <title>superimposed triangular grids</title>
    <published>2020-08-21T16:30:00Z</published>
    <updated>2020-08-21T16:30:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This experiment was inspired by field of &lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/Twistronics&quot;&gt;&lt;em&gt;twistronics&lt;/em&gt;&lt;/a&gt;, the study
of the intersesting properties of misaligned graphene sheets. The misalignment
produces a &lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/Moir%C3%A9_pattern&quot;&gt;moiré pattern&lt;/a&gt; which echoes the underlying structure. I&amp;#39;m too
lazy to do hexagonal grids in an afternoon of tinkering so I used triangular
grids instead.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/superimposed-hexagonal-grids</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/superimposed-hexagonal-grids"/>
    <title>superimposed hexagonal grids</title>
    <published>2020-08-22T13:30:00Z</published>
    <updated>2020-08-22T13:30:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Based on the &lt;a href&#x3D;&quot;/blog/superimposed-triangular-grids&quot;&gt;last experiment&lt;/a&gt;, this one uses hexagonal grids rather than
triangular ones. The way the SVG is constructed isn&amp;#39;t pretty (a mish-mash of
paths) but it gets the job done. I&amp;#39;ve given the two grids red and blue lines
and a black background to make the moiré pattern stand out.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/custom-markdown-blocks-with-marked</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/custom-markdown-blocks-with-marked"/>
    <title>Custom markdown blocks with marked</title>
    <published>2020-10-09T21:45:00Z</published>
    <updated>2020-10-09T21:45:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I use &lt;a href&#x3D;&quot;https://marked.js.org&quot;&gt;&lt;code&gt;marked&lt;/code&gt;&lt;/a&gt; to do the markdown rendering for this blog. A recent
feature makes it possible to create custom block types with a little hacking. In
this post I show you how!&lt;/p&gt;
&lt;p&gt;I&amp;#39;ll be using mathematics (&lt;a href&#x3D;&quot;https://tug.org/&quot;&gt;TeX&lt;/a&gt;) blocks for my example. The &lt;code&gt;marked&lt;/code&gt; setup
code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;marked.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;({
  &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;walkTokens&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;token&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { type, raw } &#x3D; token;

    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Modify paragraph blocks beginning and ending with $$.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (type &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;paragraph&amp;#x27;&lt;/span&gt; &amp;amp;&amp;amp; raw.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;startsWith&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;$$\n&amp;#x27;&lt;/span&gt;) &amp;amp;&amp;amp; raw.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;endsWith&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;\n$$&amp;#x27;&lt;/span&gt;)) {
      token.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;type&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;code&amp;#x27;&lt;/span&gt;;
      token.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;lang&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;mathematics&amp;#x27;&lt;/span&gt;;
      token.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;text&lt;/span&gt; &#x3D; token.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;raw&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;slice&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-number&quot;&gt;3&lt;/span&gt;, -&lt;span class&#x3D;&quot;hljs-number&quot;&gt;3&lt;/span&gt;); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Remove the $$ boundaries.&lt;/span&gt;
      token.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;tokens&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;length&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-number&quot;&gt;0&lt;/span&gt;; &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Remove child tokens.&lt;/span&gt;
    }
  },
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;renderer&lt;/span&gt;: {
    &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;code&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;code, language&lt;/span&gt;) {
      &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Use custom mathematics renderer.&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (language &#x3D;&#x3D;&#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;mathematics&amp;#x27;&lt;/span&gt;) {
        &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;renderMathematics&lt;/span&gt;(code);
      }

      &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Use default code renderer.&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;m passing two things to &lt;code&gt;marked&lt;/code&gt; to configure it here. The first is a
&lt;a href&#x3D;&quot;https://marked.js.org/using_pro#walk-tokens&quot;&gt;token walker function&lt;/a&gt;, which is the recent feature which makes
this all possible. It is called for each token, traversing the children of a
token before it progresses to its siblings (so it&amp;#39;s sort of depth-first).&lt;/p&gt;
&lt;p&gt;The idea is for blocks of text with a &lt;code&gt;$$&lt;/code&gt; above and below them to be handled
as mathematics. To a person this looks like a fenced code block with two dollar
symbols in the place of the three backticks. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-markdown&quot;&gt;Some example text with some mathematics to render below:

$$
a^2 &#x3D; b^2 + c^2
$$

Some example text below.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;#39;s a common extension to place LaTeX code inside &lt;code&gt;$$&lt;/code&gt; delimited blocks.
Even if you&amp;#39;re not familiar, the dollar symbols above and below are a little
like a fenced code block. To &lt;code&gt;marked&lt;/code&gt; the block looks like a paragraph. This
means that the token walker will receive some paragraph tokens which need to be
modified.&lt;/p&gt;
&lt;p&gt;To know the difference, paragraph tokens are checked by the token walker to see
if they begin with &lt;code&gt;$$\n&lt;/code&gt; and end with &lt;code&gt;\n$$&lt;/code&gt;. When they do, the block is
modified to look like a code block with a special &lt;code&gt;&amp;#39;markdown&amp;#39;&lt;/code&gt; language. Child
tokens are removed because the content shouldn&amp;#39;t be treated as markdown, and the
&lt;code&gt;text&lt;/code&gt; property is set by snipping the leading and trailing dollar symbols and
newlines off.&lt;/p&gt;
&lt;p&gt;The second part of this trick is in the renderer option. The renderer for code
blocks is modified with special handling for the &lt;code&gt;&amp;#39;mathematics&amp;#39;&lt;/code&gt; language. The
code parameter received by it is the text we set on the token, so it&amp;#39;s ready to
be rendered to mathematics. The rendering itself is beyond the scope of this
post, but I use &lt;a href&#x3D;&quot;https://www.mathjax.org/&quot;&gt;MathJax&lt;/a&gt;. When code is of any other language the
custom code renderer returns &lt;code&gt;false&lt;/code&gt; to instruct &lt;code&gt;marked&lt;/code&gt; to use the default
code rendering behaviour.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1607597111642</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1607597111642"/>
    <title>Link to https://amberwilson.co.uk/blog/grow-the-indieweb-with-webmentions/</title>
    <published>2020-12-10T10:45:11Z</published>
    <updated>2020-12-10T10:45:11Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I really like webmentions, so it makes me happy when folk blog about them! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1607597111642&quot;&gt;Grow the IndieWeb with Webmentions - Amber Wilson&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1612361125691</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1612361125691"/>
    <title>Link to https://adactio.com/journal/17794</title>
    <published>2021-02-03T14:05:25Z</published>
    <updated>2021-02-03T14:05:25Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The start of this article is a great introduction to what 2fa is and what it isn&amp;#x27;t. Worth sharing with family and friends to help motivate setting 2fa up. For web devs there are some nice UX tips. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1612361125691&quot;&gt;Authentication - Jeremy Keith&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/dispatching-webmentions-with-a-netlify-build-plugin</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/dispatching-webmentions-with-a-netlify-build-plugin"/>
    <title>Dispatching Webmentions with a Netlify build plugin</title>
    <published>2021-02-27T15:45:00Z</published>
    <updated>2023-02-20T23:23:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This site uses a static site generator to build plain HTML pages. Since there&amp;#39;s
no database to add, update, or delete pages from, determining when to dispatch
mentions can be challenging! Here&amp;#39;s how I use a Netlify
&lt;a href&#x3D;&quot;https://docs.netlify.com/configure-builds/build-plugins/create-plugins/&quot;&gt;build plugin&lt;/a&gt; and an &lt;a href&#x3D;&quot;https://tools.ietf.org/html/rfc5023&quot;&gt;atom feed&lt;/a&gt; to manage it.&lt;/p&gt;
&lt;p&gt;The &lt;a href&#x3D;&quot;https://www.w3.org/TR/webmention/&quot;&gt;Webmention spec&lt;/a&gt; requires that a mention should be sent
whenever a link is added or removed from a page, or the page one is on is
updated or deleted. The recipient will receive an HTTP post which looks the same
whatever happened. It&amp;#39;s up to them to determine what changed.&lt;/p&gt;
&lt;p&gt;Netlify provides hooks for various stages of the build, and a plugin may use as
many as it needs. I use the &lt;code&gt;onPostBuild&lt;/code&gt; and &lt;code&gt;onSuccess&lt;/code&gt; hooks. The former is
called when the build is complete, but &lt;em&gt;before&lt;/em&gt; it is deployed. This gives me
access to the old atom feed and pages over the network (the old version of the
site is still deployed), and new versions in the build directory. Mentions are
gathered and kept until the &lt;code&gt;onSuccess&lt;/code&gt; hook. This hook fires when the site is
deployed. It&amp;#39;s important to wait for the deployment because the receiver of a
mention may automatically check the content of the source URL to know what
happened.&lt;/p&gt;
&lt;p&gt;If you prefer to read code, &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/tree/main/plugins/dispatch-webmentions/index.js&quot;&gt;check it out here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is just the beginning! I plan to port other capabilities over to Netlify
build plugins in time.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1614636915109</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1614636915109"/>
    <title>Link to https://alvin.codes/writing/digital-gardens</title>
    <published>2021-03-01T22:15:15Z</published>
    <updated>2021-03-01T22:15:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I really like the idea of a digital garden. I think I&amp;#x27;d still keep some content as dated blog posts as a sort of snapshot of knowledge, and maybe add a garden tab for timeless items which I maintain and grow. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1614636915109&quot;&gt;Digital Gardens - Alvin Bryan&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1615486045856</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1615486045856"/>
    <title>Link to https://maggieappleton.com/garden-history</title>
    <published>2021-03-11T18:07:25Z</published>
    <updated>2021-03-11T18:07:25Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This article is about the history of digital gardening. The focus of the term is mainly on content, but I like to think it applies to my whole site. That said, I plan to include garden items soon! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1615486045856&quot;&gt;A Brief History &amp;amp; Ethos of the Digital Garden - Maggie Appleton&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1615979015081</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1615979015081"/>
    <title>Like of https://adactio.com/notes/17936</title>
    <published>2021-03-17T11:03:35Z</published>
    <updated>2021-03-17T11:03:35Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/notes/17936&quot;&gt;https://adactio.com/notes/17936&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/tip-connecting-to-localstack-s3-using-the-javascript-aws-sdk-v3</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/tip-connecting-to-localstack-s3-using-the-javascript-aws-sdk-v3"/>
    <title>Tip: Connecting to localstack S3 using the JavaScript AWS SDK v3</title>
    <published>2021-03-26T18:30:51Z</published>
    <updated>2021-03-26T18:30:51Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I had some issues getting the &lt;a href&#x3D;&quot;https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/index.html&quot;&gt;v3 AWS SDK for JavaScript&lt;/a&gt; to communicate
with localstack S3, but I found a solution! With the &lt;a href&#x3D;&quot;https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/&quot;&gt;V2 JS SDK&lt;/a&gt;, the
configuration object for the S3 client looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-json&quot;&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;region&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;eu-west-1&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;endpoint&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;http://localhost:4566&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;s3ForcePathStyle&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The last field tells the sdk not to use &lt;code&gt;&amp;lt;bucket&amp;gt;.hostname&lt;/code&gt; style connections,
and instead puts the bucket in the path. For local dev this is important because
otherwise the SDK tries to make connections to &lt;code&gt;&amp;lt;bucket&amp;gt;.localhost&lt;/code&gt;. This&amp;#39;ll
work when managing buckets, but not when dealing with their contents (unless you
add an entry to your &lt;code&gt;/etc/hosts&lt;/code&gt; file, which often isn&amp;#39;t practical).&lt;/p&gt;
&lt;p&gt;Unfortunately this field doesn&amp;#39;t exist for v3, and after searching I didn&amp;#39;t find
all that much except for a couple of mentions of the v3 JS SDK no longer
supporting it.&lt;/p&gt;
&lt;p&gt;Fortunately it&amp;#39;s not actually the case. It was just renamed to &lt;code&gt;forcePathStyle&lt;/code&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; { S3Client } &#x3D; &lt;span class&#x3D;&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;@aws-sdk/client-s3&amp;#x27;&lt;/span&gt;);

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; s3 &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;S3Client&lt;/span&gt;({
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;region&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;eu-west-1&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// The value here doesn&amp;#x27;t matter.&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;endpoint&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;http://localhost:4566&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// This is the localstack EDGE_PORT&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;forcePathStyle&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt;
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For reference, I boot localstack with this &lt;a href&#x3D;&quot;https://docs.docker.com/compose/&quot;&gt;docker-compose&lt;/a&gt;
configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-yaml&quot;&gt;&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;version:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;3.9&amp;quot;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-attr&quot;&gt;services:&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;mock-s3:&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;localstack/localstack:latest&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;4566-4583:4566-4583&amp;quot;&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;environment:&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;AWS_DEFAULT_REGION&#x3D;eu-west-1&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;EDGE_PORT&#x3D;4566&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;SERVICES&#x3D;s3&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;DEBUG&#x3D;1&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;DATA_DIR&#x3D;/tmp/localstack/data&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;volumes:&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;./.localstack:/tmp/localstack&amp;quot;&lt;/span&gt;
      &lt;span class&#x3D;&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/var/run/docker.sock:/var/run/docker.sock&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1617367731110</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1617367731110"/>
    <title>Link to https://www.sarasoueidan.com/blog/redesign/</title>
    <published>2021-04-02T12:48:51Z</published>
    <updated>2021-04-02T12:48:51Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It&amp;#x27;s a rare treat to get to see a personal site built in the open. I&amp;#x27;m really looking forward to this! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1617367731110&quot;&gt;Redesigning and rebuilding my Web site from the ground up - Sara Soueidan&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1617467646379</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1617467646379"/>
    <title>Note 54</title>
    <published>2021-04-03T16:34:06Z</published>
    <updated>2021-04-03T16:34:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I made some sourdough hot cross buns!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1617467594766.jpeg&quot; alt&#x3D;&quot;A plate with some freshly made sourdough hot cross buns on.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1617480719805</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1617480719805"/>
    <title>Note 55</title>
    <published>2021-04-03T20:11:59Z</published>
    <updated>2021-04-03T20:11:59Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I made an excellent banana bread the other day. No sourdough involved though.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1617480668108.jpeg&quot; alt&#x3D;&quot;A slice of deep brown banana bread on a small plate.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1618678341204</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1618678341204"/>
    <title>Note 56</title>
    <published>2021-04-17T16:52:21Z</published>
    <updated>2021-04-17T16:52:21Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Feels good to wander around Brighton.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1618678281927.jpeg&quot; alt&#x3D;&quot;A view of the ocean between some houses and a church in the Montpelier area of Brighton.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1628256883634</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1628256883634"/>
    <title>Note 57</title>
    <published>2021-08-06T13:34:43Z</published>
    <updated>2021-08-06T13:34:43Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;An interesting path close to home.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1628256856626.jpeg&quot; alt&#x3D;&quot;Steps in the shade between houses.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1630576061198</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1630576061198"/>
    <title>Link to https://georgefrancis.dev/writing/crafting-organic-patterns-with-voronoi-tessellations/</title>
    <published>2021-09-02T09:47:41Z</published>
    <updated>2021-09-02T09:47:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;George does incredible things with Voronoi diagrams. I&amp;#x27;ll read this later to figure out how George does it... &lt;a href&#x3D;&quot;https://qubyte.codes/links/1630576061198&quot;&gt;Crafting Organic Patterns With Voronoi Tessellations - George Francis&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/pastel-migraine-auras</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/pastel-migraine-auras"/>
    <title>Pastel migraine auras</title>
    <published>2021-10-14T08:10:00Z</published>
    <updated>2021-10-14T08:10:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A generative patchwork of pastel colours. The colours begin with a randomly
picked colour in &lt;a href&#x3D;&quot;https://www.w3.org/TR/css-color-4/#lab-colors&quot;&gt;LCH&lt;/a&gt;. Other
colours are hue rotations in LCH space so that they&amp;#39;re perceptually nice
together. Feature detection is used to render using LCH colours when the browser
supports them (Safari only at the time of writing), or to pick a close colour in
RGB space when LCH is not supported (the code for this is based on code in
&lt;a href&#x3D;&quot;https://github.com/d3/d3-color&quot;&gt;d3-color&lt;/a&gt;). Refreshing generates a new
patchwork.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1635191881767</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1635191881767"/>
    <title>Note 58</title>
    <published>2021-10-25T19:58:01Z</published>
    <updated>2021-10-25T19:58:01Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A little autumn weather isn’t putting folk off the sea.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1635191837674.jpeg&quot; alt&#x3D;&quot;Three people in coats sit on the beach facing the ocean. In the distance there are people boating, windmills on the horizon, and clear sky peaking through the cloud.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1635875090652</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1635875090652"/>
    <title>Note 59</title>
    <published>2021-11-02T17:44:50Z</published>
    <updated>2021-11-02T17:44:50Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A caterpillar.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1635875054653.jpeg&quot; alt&#x3D;&quot;A yellow and black caterpillar perched behind some purple flowers.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/procedural-christmas-cards</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/procedural-christmas-cards"/>
    <title>Procedural Christmas cards</title>
    <published>2021-12-07T16:01:05Z</published>
    <updated>2021-12-07T16:01:05Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Below is a procedural snowflake. I&amp;#39;m using a little code to create Christmas
cards this year, and I wanted each to be unique! If you received a card from me,
you may see something like &lt;code&gt;?seed&#x3D;1234567890&lt;/code&gt; in the URL bar. That will be the
random seed which generated your snowflake (and it&amp;#39;s yours to keep). To see a
random snowflake, remove everything after the question mark and hit enter.
Refresh the page to see a fresh random snowflake!&lt;/p&gt;
&lt;p&gt;The red colour is a convenience to make it show up in the software I use to
cut card.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1641343212473</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1641343212473"/>
    <title>Note 60</title>
    <published>2022-01-05T00:40:12Z</published>
    <updated>2022-01-05T00:40:12Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I found an old picture of me holding peanuts.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1641343125611.jpeg&quot; alt&#x3D;&quot;Me with a handful of freshly dug peanuts in a field in Japan.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1643579044553</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1643579044553"/>
    <title>Note 61</title>
    <published>2022-01-30T21:44:04Z</published>
    <updated>2022-01-30T21:44:04Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A bridge being built close to where I live.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1643578984364.jpeg&quot; alt&#x3D;&quot;The view of a pedestrian bridge being constructed from one end. It currently lacks a platform.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1650301277001</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1650301277001"/>
    <title>Note 62</title>
    <published>2022-04-18T17:01:17Z</published>
    <updated>2022-04-18T17:01:17Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;First concert since December 2019. Back then it was seeing Devin Townsend. This time it was seeing… Devin Townsend. This time was stripped back and very tight. They played some songs I never thought I’d see live, like Dynamics and Almost Again. We got upgraded to a box in The Royal Albert Hall too.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1650301012299.jpeg&quot; alt&#x3D;&quot;Devin Townsend and Co. on the stage at The Royal Albert hall, as seen from the Second level circle.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1651336273444</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1651336273444"/>
    <title>Note 63</title>
    <published>2022-04-30T16:31:13Z</published>
    <updated>2022-04-30T16:31:13Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This afternoon I&amp;#39;m playing with webmentions again. I&amp;#39;ve decided to implement my own endpoint and migrate away from webmention.io (which is fine, I just want to build my own).&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1651485242791</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1651485242791"/>
    <title>Link to https://chriscoyier.net/2022/04/29/rss-3/</title>
    <published>2022-05-02T09:54:02Z</published>
    <updated>2022-05-02T09:54:02Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Chris Coyier pitches RSS as a sort of slow social networking. In hindsight I do wonder if all the IndieWeb bells and whistles on my site are worth it when I already have feeds, but they&amp;#x27;re also a lot of fun to work on. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1651485242791&quot;&gt;RSS - Chris Coyier&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1651501103834</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1651501103834"/>
    <title>Note 64</title>
    <published>2022-05-02T14:18:23Z</published>
    <updated>2022-05-02T14:18:23Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I guess the reason for rolling my own webmention receiver is that I don&amp;#39;t want to rely on services I don&amp;#39;t manage or pay for. Now, an argument could be made that I&amp;#39;m putting everything on Netlify, so my eggs are all in their basket. My counter to that is that the functions I&amp;#39;m writing fairly portable. All I&amp;#39;d have to do is wrap the functions in a little server. So, the important part is to own the logic! Of course, there&amp;#39;s also the fun part of reading a spec and implementing it.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/marqdown</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/marqdown"/>
    <title>Marqdown</title>
    <published>2022-05-15T09:55:00Z</published>
    <updated>2023-06-19T23:20:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Markdown is the standard for writing in techie circles these days, but
it&#x27;s pretty minimal. For a readme it&#x27;s all you need, but if you create a site
around Markdown like I have then you pretty quickly bump into its limitations.
Markdown is &lt;em&gt;deliberately&lt;/em&gt; limited, so it&#x27;s no fault of the language or its
creator!&lt;/p&gt;
&lt;p&gt;Nevertheless, over time I&#x27;ve added my own tweaks and extensions upon Markdown,
so I&#x27;ve decided to document them, and name the dialect Marqdown. Naming may seem
a little arrogant, but it&#x27;s mostly to disambiguate what I&#x27;m writing with more
common Markdown variants.&lt;/p&gt;
&lt;p&gt;My variant is based on the default configuration provided by &lt;a href&#x3D;&quot;https://marked.js.org/&quot;&gt;marked&lt;/a&gt;, with
additions layered on top. This is mostly the original flavour of Markdown with
a few deviations to fix broken behaviour. As I add features I&#x27;ll document them
in this post.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;footnotes&quot;&gt;Footnotes&lt;/h2&gt;
&lt;p&gt;I use footnotes&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-1&quot; href&#x3D;&quot;#footnote-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; from time to time. The way I&#x27;ve implemented
them makes the superscript a link to the footnote text, and the footnote text
itself has a backlink to the superscript, so you can jump back to where you
were.&lt;/p&gt;
&lt;p&gt;The footnote in the previous paragraph is encoded like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;I use footnotes[^][sparingly] from time to time.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This was an interesting feature to implement because it produces content out of
the regular flow of the document. The markdown engine had to be abused a bit to
create the superscript links first and keep a list of their footnote texts. Once
the document is rendered, a post-render routine checks for any footnote texts,
and when there&#x27;s at least one it appends a section with an ordered list of
footnotes. Another complication is index pages. For the blog posts
&lt;a href&#x3D;&quot;/blog&quot;&gt;index page&lt;/a&gt; only the first paragraph of each post is used, and footnote
superscripts have to be removed from those.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;languages&quot;&gt;Languages&lt;/h2&gt;
&lt;p&gt;HTML supports language attributes. Most of the time a (well-built) page will
have a single language attribute on the opening &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag itself, setting
the language for the entire document.&lt;/p&gt;
&lt;p&gt;I write notes in mixed English and Japanese as I learn the latter. When working
with CJK text it&#x27;s particularly important to give elements containing such text
the appropriate language tag so that Chinese characters are
&lt;a href&#x3D;&quot;https://heistak.github.io/your-code-displays-japanese-wrong/&quot;&gt;properly rendered&lt;/a&gt; (there are divergences which are important).&lt;/p&gt;
&lt;p&gt;I wrote a Markdown syntax extension to add these tags. Since my documents are
mostly in English, this remains as the language attribute of each page as a
whole. For snippets of Japanese I use the syntax extension, which looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;The text between here {ja:今日は} and here is in Japanese.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This snippet renders to:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;
  The text between here &lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;span&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;lang&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&quot;ja&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;今日は&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt; and here is in Japanese.
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Simple enough. The span is unavoidable because there is only text within it and
text surrounding it. Where the renderer gets smart is in eliminating the span!
If the span is the only child of its parent, the renderer eliminates the span by
moving the language attribute to the parent. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;- English
- {ja:日本語}
- English
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;migrates the language attribute to the parent &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; to eliminate a span:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;English&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;li&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;lang&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&quot;ja&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;日本語&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;English&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Similarly, the renderer is smart enough to see when the span has only one child,
and can move the language attribute to the child to eliminate the span. Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;{ja:_すごい_}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;migrates the language attribute to the spans only child, an &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;em&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;lang&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&quot;ja&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;すごい&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;em&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This becomes particularly important in the case of my notes, where it&#x27;s common
to nest ruby elements inside these language wrappers. There&#x27;s a ruby annotation
in the next section, and you&#x27;ll see the language attributes appear directly
on the ruby element if you inspect it.&lt;/p&gt;
&lt;p&gt;As with footnotes, the language attribute migration and span elimination is
handled using JSDOM &lt;em&gt;after&lt;/em&gt; a markdown document is rendered as part of a
post-render routine. In the future I may look into adapting marked to render
directly to JSDOM rather than to a string.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;ruby-annotations&quot;&gt;Ruby annotations&lt;/h2&gt;
&lt;p&gt;I&#x27;m studying Japanese. It&#x27;s pretty common to see annotations to help with the
pronunciation of words containing Chinese characters. This could be because the
text is intended for learners like me, but it&#x27;s also common to see it for less
common words, or where the reading of a word may be ambiguous.&lt;/p&gt;
&lt;p&gt;These annotations typically look like kana rendered above or below the word
(when Japanese is written left-to-right), or to one side (when Japanese is
written from top to bottom). Ruby annotations are not unique to Japanese, but in
the Japanese context they&#x27;re called
&lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/Furigana&quot;&gt;&lt;ruby lang&#x3D;&quot;ja&quot;&gt;振&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;ふ&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;り&lt;rt&gt;&lt;/rt&gt;仮名&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;がな&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;&lt;/ruby&gt; (furigana)&lt;/a&gt;, and you can see them right here
in the Japanese text in this sentence! The code for it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;^振,ふ,り,,仮名,がな^
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The delimiters are the carets, odd elements are regular text, and even elements
are their annotations. So, &lt;span lang&#x3D;&quot;ja&quot;&gt;ふ&lt;/span&gt; goes above &lt;span lang&#x3D;&quot;ja&quot;&gt;振&lt;/span&gt;, nothing goes above &lt;span lang&#x3D;&quot;ja&quot;&gt;り&lt;/span&gt;
(it&#x27;s already a kana character), and &lt;span lang&#x3D;&quot;ja&quot;&gt;がな&lt;/span&gt; goes above &lt;span lang&#x3D;&quot;ja&quot;&gt;仮名&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;There are actually &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby&quot;&gt;specific elements&lt;/a&gt; for handling ruby annotations, so
what you see rendered is only from HTML and CSS! They&#x27;re pretty fiddly to work
with manually though, so this extension saves me a lot of time and saves me from
lots of broken markup.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;highlighted-text&quot;&gt;Highlighted text&lt;/h2&gt;
&lt;p&gt;The syntax of this extension is borrowed from elsewhere (I didn&#x27;t invent it).
This addition allows me to wrap stuff in &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; elements. By default, this
is a bit like using a highlighter pen on text. The syntax looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;Boring &#x3D;&#x3D;important&#x3D;&#x3D; boring again.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which renders to:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;
  Boring &lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;mark&lt;/span&gt;&amp;gt;&lt;/span&gt;important&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;mark&lt;/span&gt;&amp;gt;&lt;/span&gt; boring again.
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class&#x3D;&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which looks like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Boring &lt;mark&gt;important&lt;/mark&gt; boring again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is another extension I use heavily in my language notes to emphasize the
important parts of grammar notes.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;fancy-maths&quot;&gt;Fancy maths&lt;/h2&gt;
&lt;p&gt;Now and then I do a post with some equations in. I could render these elsewhere,
but I like to keep everything together for source control. Add to that, I want
to render the maths statically to avoid client side rendering (and all the JS
I&#x27;d have to include to do that).&lt;/p&gt;
&lt;p&gt;I settled on another common markdown extension to do this, which is to embed
LaTeX code. The previous extensions are all inline, whereas maths comes both in
inline and block contexts. I use &lt;a href&#x3D;&quot;https://temml.org&quot;&gt;temml&lt;/a&gt; to render LaTeX directly to
presentational MathML. Inline mode uses single &lt;code&gt;$&lt;/code&gt; symbols as delimiters, and
blocks use double &lt;code&gt;$$&lt;/code&gt; on their own lines above and below the content.&lt;/p&gt;
&lt;p&gt;In the past I used &lt;a href&#x3D;&quot;https://www.mathjax.org/&quot;&gt;MathJax&lt;/a&gt;. Visually the &lt;code&gt;SVG&lt;/code&gt; MathJax produces is superior to
MathML, but the latter is now acceptable, and &lt;code&gt;&amp;lt;math&amp;gt;&lt;/code&gt; elements are more
appropriate than &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;, and much less bulky.&lt;/p&gt;
&lt;p&gt;This:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-plaintext&quot;&gt;$$
x &#x3D; \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
$$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Results in:&lt;/p&gt;
&lt;math display&#x3D;&quot;block&quot; class&#x3D;&quot;tml-display&quot; id&#x3D;&quot;equation-1&quot; aria-label&#x3D;&quot;x &#x3D; \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;&#x3D;&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mo form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot; lspace&#x3D;&quot;0em&quot; rspace&#x3D;&quot;0em&quot;&gt;−&lt;/mo&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mo&gt;±&lt;/mo&gt;&lt;msqrt&gt;&lt;mrow&gt;&lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;4&lt;/mn&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;/mrow&gt;&lt;/msqrt&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding&#x3D;&quot;application/x-tex&quot;&gt;x &#x3D; \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1652904006765</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1652904006765"/>
    <title>Note 65</title>
    <published>2022-05-18T20:00:06Z</published>
    <updated>2022-05-18T20:00:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I finally caved in and ordered myself parts for an ortholinear keyboard. I&amp;#39;ve gone with a Preonic through Drop, and paired it with Holy Panda X switches. The wait means it&amp;#39;ll be a nice birthday present for myself!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1652907213004</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1652907213004"/>
    <title>Note 66</title>
    <published>2022-05-18T20:53:33Z</published>
    <updated>2022-05-18T20:53:33Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;m using Safari for personal use these days. I wanted to use omnibear for micropub stuff, so I compiled the JS (a normal build step for it), and then ran the command below. The only change I had to make was to add a description to the manifest.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-shell&quot;&gt;xcrun safari-web-extension-converter /path/to/my/extension/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In Safari, the Allow Unsigned Extensions option must be checked. Xcode generates a wrapper application for omnibear. After booting it, you can quit it and go to Safari -&amp;gt; Preferences -&amp;gt; Extensions to enable the extension. I&amp;#39;m posting this note using it!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1653002055390</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1653002055390"/>
    <title>Link to https://drop.com/buy/drop-matt3o-mt3-susuwatari-custom-keycap-set?defaultSelectionIds&#x3D;952687</title>
    <published>2022-05-19T23:14:15Z</published>
    <updated>2022-05-19T23:14:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Bookmarking these so I can find them later. I&amp;#x27;ve been looking for caps like the old BBC Micro had, and these seem to be pretty close. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1653002055390&quot;&gt;Drop + Matt3o MT3 Susuwatari Custom Keycap Set - Drop&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1653089373068</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1653089373068"/>
    <title>Note 67</title>
    <published>2022-05-20T23:29:33Z</published>
    <updated>2022-05-20T23:29:33Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I bought the keycaps.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/its-time-to-build-a-study-habit</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/its-time-to-build-a-study-habit"/>
    <title>It&#x27;s time to build a study habit</title>
    <published>2022-05-28T12:00:00Z</published>
    <updated>2022-07-09T15:05:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I started a new role recently, and the company is large enough that there are a
number of folk learning or proficient in Japanese as a second language.&lt;/p&gt;
&lt;p&gt;I joined a chat session with two other people whose Japanese was much further
along than mine. It was great listening practice (I could mostly follow the
conversation) but I was unable to contribute, which I found disappointing.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve said it so many times in the past, but I really need to build a study habit
which is substantial enough to improve my proficiency, but light enough that I
can sustain it. Not just flash cards too.&lt;/p&gt;
&lt;p&gt;All this is complicated by the little one. Evenings are complicated by his
dinner/bath/bedtime routine, which can be exhausting. Mornings are more open,
but that time needs to be carved out of my existing routine... I could make that
work.&lt;/p&gt;
&lt;p&gt;Sometimes it should be book work. I stalled on Genki II because there are holes
in my basic Japanese from stop-start learning over such a long time. I might
pick up Genki I and do one or two morning sessions a week with it.&lt;/p&gt;
&lt;p&gt;Other times I think I should read some manga. Two I&amp;#39;ve been meaning to start
reading which I think may work are &lt;span lang&#x3D;&quot;ja&quot;&gt;よつばと!&lt;/span&gt; and &lt;span lang&#x3D;&quot;ja&quot;&gt;しろくまカフェ&lt;/span&gt;. They&amp;#39;re
gentle and humorous stories which I think will work well as spring becomes
summer here. I also have some graded readers on the shelf I can start on.&lt;/p&gt;
&lt;p&gt;Of course, these are still not speaking, but I hope that if I can improve on
reading and consuming a wider variety of discourse than just set phrases I stand
a better chance of finding the words and phrases I need to contribute in the
Japanese chat sessions.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;update-2022-07-09&quot;&gt;Update (2022-07-09)&lt;/h2&gt;
&lt;p&gt;To prove that I&amp;#39;m not just talking crap, it&amp;#39;s time for an update!&lt;/p&gt;
&lt;p&gt;Since I wrote the above post, I&amp;#39;ve subscribed to &lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;WaniKani&lt;/a&gt;, and bought three
volumes of each of the manga I mentioned above. I&amp;#39;m reading &lt;span lang&#x3D;&quot;ja&quot;&gt;よつばと!&lt;/span&gt; first
because it&amp;#39;s a little more accessible at my level (and I&amp;#39;m really enjoying it,
even if I am looking forward to &lt;span lang&#x3D;&quot;ja&quot;&gt;しろくまカフェ&lt;/span&gt;). I spend 30-60 minutes
each week on a Saturday or Sunday reading, and I intend to build that up to
more.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve found WaniKani to be especially effective. Over the years (decades?) I&amp;#39;ve
tried a lot of flash card apps and approaches, but avoided WaniKani since my
kanji wasn&amp;#39;t too bad and I didn&amp;#39;t want to spend a disproportionate amount of
effort on it at the cost of time I could have put into other study. In
hindsight, this was a mistake. The approach WaniKani takes is designed
specifically for Japanese, and also includes relevant vocabulary. I can already
tell it&amp;#39;s helping me as I read manga.&lt;/p&gt;
&lt;p&gt;To hold myself to account I&amp;#39;m logging my &lt;a href&#x3D;&quot;/study-sessions/&quot;&gt;study sessions&lt;/a&gt;. I&amp;#39;m only logging
sessions over about 10 minutes. Lots of WaniKani review sessions come in under
that bar.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1654181143314</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1654181143314"/>
    <title>Note 68</title>
    <published>2022-06-02T14:45:43Z</published>
    <updated>2022-06-02T14:45:43Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&lt;ruby lang&#x3D;&quot;ja&quot;&gt;多読&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;たどく&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;&lt;/ruby&gt; time! First up, &lt;span lang&#x3D;&quot;ja&quot;&gt;しろくまカフェ&lt;/span&gt;&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1654180945493.jpeg&quot; alt&#x3D;&quot;The first book of shirokuma cafe (in Japanese). Beside the book is an iced coffee.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1655129458802</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1655129458802"/>
    <title>Note 69</title>
    <published>2022-06-13T14:10:58Z</published>
    <updated>2022-06-13T14:10:58Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I live so close to this countryside, but rarely take time to visit. It’s the perfect weather for it today.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1655129330234.jpeg&quot; alt&#x3D;&quot;A field of barley. Deep blue sky with white fluffy clouds. In the distance there is a ridge of green hills.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1655565232054</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1655565232054"/>
    <title>Note 70</title>
    <published>2022-06-18T15:13:52Z</published>
    <updated>2022-06-18T15:13:52Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This cat is attempting to be adopted by us and is in no way subtle about it.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1655565195542.jpeg&quot; alt&#x3D;&quot;A cat sitting on my doormat, right outside my house.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1656021028301</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1656021028301"/>
    <title>Link to https://www.miriamsuzanne.com/2022/06/04/indiweb/</title>
    <published>2022-06-23T21:50:28Z</published>
    <updated>2022-06-23T21:50:28Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I enjoyed this read. It&amp;#x27;s always worth coming back to the difficulties in the social IndieWeb features, and how tricky they are to implement. I&amp;#x27;ve invested substantial effort in webmentions in particular, and that means there&amp;#x27;s a barrier to entry. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1656021028301&quot;&gt;Am I on the IndieWeb Yet? - Miriam Eric Suzanne&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1656021230433</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1656021230433"/>
    <title>Link to https://matthiasott.com/articles/into-the-personal-website-verse</title>
    <published>2022-06-23T21:53:50Z</published>
    <updated>2022-06-23T21:53:50Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;With twitter announcing a long-form writing feature, it&amp;#x27;s time (it seems to be time at least daily) to remind folk that there&amp;#x27;s a better, far more creative way. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1656021230433&quot;&gt;Into the Personal-Website-Verse - Matthias Ott&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1656161544690</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1656161544690"/>
    <title>Note 71</title>
    <published>2022-06-25T12:52:24Z</published>
    <updated>2022-06-25T12:52:24Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;GitHub sent me a one-time donation as part of their &lt;a href&#x3D;&quot;https://github.blog/2022-06-24-thank-you-to-our-maintainers/&quot;&gt;thank you&lt;/a&gt; to OS contributors to software GitHub uses (who have their sponsors dashboard set up). I originally set it up in hope that someone would send me the occasional buck or two for a coffee. That never happened (at least not yet), but $550 as a one off makes up for it!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1658075038505</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1658075038505"/>
    <title>Note 72</title>
    <published>2022-07-17T16:23:58Z</published>
    <updated>2022-07-17T16:23:58Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I built a keyboard. Hopefully it&amp;#39;s more practical than it looks.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1658074739108.jpeg&quot; alt&#x3D;&quot;A photo of a four row ortholinear (square layout) keyboard. The keycaps are mostly light grey, with dark grey on the bottom row and the first and last columns. The escape and return keys are red. The caps have a high profile, with each row pitched differently at the top for comfort. The base has a low profile, so you can see the switches below the caps.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1665735493297</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1665735493297"/>
    <title>Note 73</title>
    <published>2022-10-14T08:18:13Z</published>
    <updated>2022-10-14T08:18:13Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I designed and built a keyboard! This is build for my comfort (despite how it looks). I designed it from the PCB and plates up! It uses kailh choc switches for a low profile, and a QWERTY variant of &lt;a href&#x3D;&quot;https://github.com/manna-harbour/miryoku&quot;&gt;Miryoku&lt;/a&gt; to minimize finger and thumb movement.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1665735132683.jpeg&quot; alt&#x3D;&quot;The FG-11 split keyboard. Each side has 23 keys. The keys for the fingers are layed out in five columns or three rows on each side. The second and third column from the inside are one key higher further from the typist than the other columns, to suit where my middle and ring fingers sit. There are three thumb keys on each side, arranged along a diagonal. The PCB of each side can be seen peaking out on the inside edge, and the controller can be seen on the left hand. A ribbon cable connects the two sides from PCB to PCB. The plates are stainless steel and key caps white.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/controlling-ruby-annotation-positioning-and-appearance-with-pure-css-and-a-select-box</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/controlling-ruby-annotation-positioning-and-appearance-with-pure-css-and-a-select-box"/>
    <title>Controlling ruby annotation positioning and appearance with pure CSS and a select box</title>
    <published>2022-11-07T12:05:00Z</published>
    <updated>2022-11-07T12:05:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:has&quot;&gt;&lt;code&gt;:has()&lt;/code&gt;&lt;/a&gt; CSS pseudo-class opens up all sorts of possibilities. I
wanted to see if it could simplify how I handle the ruby text (annotations above
or below text to help with reading) in my Japanese notes. It works (in Safari
and Chrome at least, and hopefully Firefox soon)!&lt;/p&gt;
&lt;p&gt;Demonstration time. Below the date stamp in the header above you&#x27;ll see
&quot;&lt;span lang&#x3D;&quot;ja&quot;&gt;ふりがな&lt;/span&gt;&quot;. To the right is a word. Click or tap on the word to select the
positioning of the annotations in the Japanese text below, or hide them. This
works even with JS turned off.&lt;/p&gt;
&lt;p lang&#x3D;&quot;ja&quot;&gt;この&lt;ruby&gt;文&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;ぶん&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;&lt;/ruby&gt;にはふりがながあります。&lt;/p&gt;
&lt;p&gt;Previously I had to use some JS to achieve this. Pretty simple CSS can be used
to target and style ruby text (although Safari lags behind the standard). I used
JS to append and update a class to the body to set the mode desired. The markup
for ruby text is managed using &lt;a href&#x3D;&quot;/blog/marqdown&quot;&gt;my own extensions to Markdown&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The replacement CSS looks like this (there&#x27;s a little more to handle the quirks
of &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; elements which I omit here):&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-css&quot;&gt;&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;/* Select &amp;lt;body&amp;gt; when an element with class furigana-position has an &amp;lt;option&amp;gt;
 * with value &quot;over&quot; and that option is in the checked state.
 */&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;body&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:has&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-selector-class&quot;&gt;.furigana-position&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;option&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-attr&quot;&gt;[value&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&quot;over&quot;&lt;/span&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:checked&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;ruby-position&lt;/span&gt;: over;
  -webkit-&lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;ruby-position&lt;/span&gt;: before;
}

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;/* Select &amp;lt;body&amp;gt; when an element with class furigana-position has an &amp;lt;option&amp;gt;
 * with value &quot;under&quot; and that option is in the checked state.
 */&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;body&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:has&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-selector-class&quot;&gt;.furigana-position&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;option&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-attr&quot;&gt;[value&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&quot;under&quot;&lt;/span&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:checked&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;ruby-position&lt;/span&gt;: under;
  -webkit-&lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;ruby-position&lt;/span&gt;: after;
}

&lt;span class&#x3D;&quot;hljs-comment&quot;&gt;/* Select &amp;lt;rt&amp;gt; and &amp;lt;rp&amp;gt; elements in the &amp;lt;body&amp;gt; when an element with class
 * furigana-position has an &amp;lt;option&amp;gt; with value &quot;off&quot; and that option is in the
 * checked state.
 */&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;body&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:has&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-selector-class&quot;&gt;.furigana-position&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-selector-tag&quot;&gt;option&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-attr&quot;&gt;[value&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&quot;off&quot;&lt;/span&gt;]&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:checked&lt;/span&gt;) &lt;span class&#x3D;&quot;hljs-selector-pseudo&quot;&gt;:is&lt;/span&gt;(rt, rp) {
  &lt;span class&#x3D;&quot;hljs-attribute&quot;&gt;display&lt;/span&gt;: none;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I have kept a little JS to persist this state to local storage, as it was in the
earlier version of this feature. Now that only one place in the DOM needs to be
synchronized (it was two before; the option and the class on the body element)
the JS has become much simpler. Most importantly, it&#x27;s a progressive enhancement
of sorts. It&#x27;s not required for the positioning feature to work, just for the
state to be saved.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; select &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;querySelector&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&#x27;.furigana-position&#x27;&lt;/span&gt;);

select.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;value&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;localStorage&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;getItem&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&#x27;ruby-position&#x27;&lt;/span&gt;) || &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&#x27;over&#x27;&lt;/span&gt;;
select.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;onchange&lt;/span&gt; &#x3D; &lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;e&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-variable language_&quot;&gt;localStorage&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;setItem&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&#x27;ruby-position&#x27;&lt;/span&gt;, e.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;target&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;value&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, a note on why I have this feature. For learners, it can be helpful to
show and hide the annotations depending on what you&#x27;re reading and why. It can
also be helpful, especially on a tablet, to position furigana &lt;em&gt;below&lt;/em&gt; the text
it annotates, so it can be hidden with a sheet of paper as you read.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1668638994100</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1668638994100"/>
    <title>Link to https://localghost.dev/blog/building-a-website-like-it-s-1999-in-2022/</title>
    <published>2022-11-16T22:49:54Z</published>
    <updated>2022-11-16T22:49:54Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Lots of tips here to help you give your site a nostalgic feel using modern browser features. I should probably use some to jazz this place up... &lt;a href&#x3D;&quot;https://qubyte.codes/links/1668638994100&quot;&gt;Building a website like it&amp;#x27;s 1999... in 2022 - localghost&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/progressively-enhanced-caching-of-javascript-modules-without-bundling-using-import-maps</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/progressively-enhanced-caching-of-javascript-modules-without-bundling-using-import-maps"/>
    <title>Progressively enhanced caching of JavaScript modules without bundling using import maps</title>
    <published>2022-11-23T09:00:00Z</published>
    <updated>2023-09-24T11:50:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I went to &lt;a href&#x3D;&quot;https://2022.ffconf.org&quot;&gt;ffconf 2022&lt;/a&gt; a couple of weeks ago, and two of the talks in
particular resonated with me... (more actually, but these felt actionable):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://youtu.be/vGYm9VdfJ8s&quot;&gt;&amp;quot;This Talk is Under Construction: a love letter to the personal website&amp;quot;&lt;/a&gt; &lt;a href&#x3D;&quot;https://localghost.dev/&quot;&gt;Sophie Koonin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://youtu.be/CS-3bFo1XHA&quot;&gt;&amp;quot;Working towards a greener world from behind the keyboard&amp;quot;&lt;/a&gt; Natalia Waniczek&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I really like having my own place on the web, and I&amp;#39;ve already put a fairly
substantial amount of effort into making it as gentle on the environment as
possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Semantic markup without lots of nesting reduces the size of HTML over the wire
(and probably makes it slightly less demanding to parse for the browser).&lt;/li&gt;
&lt;li&gt;Few images, served in the &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images&quot;&gt;most efficient formats&lt;/a&gt; supported by the
browser making the request.&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes&quot;&gt;Lazy loaded images&lt;/a&gt;. This is important for &lt;a href&#x3D;&quot;/notes&quot;&gt;my notes stream&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;CSS is minified, hashed, and has immutable cache headers. This means that your
browser will make a single network request for it and reuse it every time you
visit, unless your browser evicts it from the cache, or the CSS changes (which
is reflected by a change of the hash in the CSS file name).&lt;/li&gt;
&lt;li&gt;The site uses a static site generator, so requests for pages resolve to files
which are easily cached. There&amp;#39;s no database serving queries behind the
scenes.&lt;/li&gt;
&lt;li&gt;As little JS as possible. Most pages have only enough JS to
&lt;a href&#x3D;&quot;/blog/putting-back-the-service-worker&quot;&gt;load a service worker&lt;/a&gt; as a progressive enhancement to allow offline access.
These pages work with JS disabled (you&amp;#39;re reading one now).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was inspired to put some time into reducing my footprint even further!&lt;/p&gt;
&lt;h3 id&#x3D;&quot;caching-javascript&quot;&gt;Caching JavaScript&lt;/h3&gt;
&lt;p&gt;The small elephant in the room are my JavaScript experiments (for example,
&lt;a href&#x3D;&quot;/tags/GenerativeArt&quot;&gt;generative art stuff&lt;/a&gt;). I like to build with small, reusable
modules, and until now I haven&amp;#39;t bothered bundling. The most common solution is
to bundle the JS for each page, put a hash of the content in the file name, and
serves it with &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable&quot;&gt;immutable&lt;/a&gt;&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-1&quot; href&#x3D;&quot;#footnote-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; cache headers with a long &lt;code&gt;max-age&lt;/code&gt;, like the CSS
mentioned above.&lt;/p&gt;
&lt;p&gt;Why though? Resources which are immutably cached need no more network requests
once cached. This means fewer requests (especially on subsequent page views).
&lt;strong&gt;Less energy used&lt;/strong&gt; to transmit files. Less opportunity for network latency and
failure to delay or break features using JS. It&amp;#39;s also kinder to mobile users
and their plans.&lt;/p&gt;
&lt;p&gt;For this little site the savings are going to be pretty modest, but it proves
that the approach works. Sites which use a lot more JS with frequent small
changes stand to benefit a lot more.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;bundling-versus-small-modules&quot;&gt;Bundling versus small modules&lt;/h3&gt;
&lt;p&gt;I&amp;#39;d still rather not bundle though. The pool of small modules I&amp;#39;ve written for
my experiments are intended for reuse. The bundle for each page would
significantly overlap with the bundles of other pages, but would be cached
independently. A single character change to any module will invalidate all
bundles which include it.&lt;/p&gt;
&lt;p&gt;It would be far better to cache these modules independently. Consider this tree
of dependencies (imagine that &lt;code&gt;/a.js&lt;/code&gt; is the entry point):&lt;/p&gt;
&lt;svg class&#x3D;&quot;flow-diagram&quot; role&#x3D;&quot;img&quot; focusable&#x3D;&quot;false&quot; aria-labelledby&#x3D;&quot;dependency-tree-no-hashes&quot; height&#x3D;&quot;322&quot; viewBox&#x3D;&quot;0 0 207.9453125 322&quot;&gt;
  &lt;title id&#x3D;&quot;dependency-tree-no-hashes&quot;&gt;Dependency tree with no hashes&lt;/title&gt;
  &lt;defs&gt;
    &lt;marker id&#x3D;&quot;arrowhead&quot; viewBox&#x3D;&quot;0 0 10 10&quot; refX&#x3D;&quot;9&quot; refY&#x3D;&quot;5&quot; markerUnits&#x3D;&quot;strokeWidth&quot; markerWidth&#x3D;&quot;8&quot; markerHeight&#x3D;&quot;6&quot; orient&#x3D;&quot;auto&quot;&gt;&lt;path d&#x3D;&quot;M 0 0 L 10 5 L 0 10 z&quot; class&#x3D;&quot;arrowheadPath&quot;&gt;&lt;/path&gt;&lt;/marker&gt;
    &lt;rect id&#x3D;&quot;small-box&quot; rx&#x3D;&quot;0&quot; ry&#x3D;&quot;0&quot; x&#x3D;&quot;-27.5078125&quot; y&#x3D;&quot;-19.5&quot; width&#x3D;&quot;55.015625&quot; height&#x3D;&quot;39&quot; class&#x3D;&quot;label-container&quot;&gt;&lt;/rect&gt;
    &lt;rect id&#x3D;&quot;large-box&quot; rx&#x3D;&quot;0&quot; ry&#x3D;&quot;0&quot; x&#x3D;&quot;-50.765625&quot; y&#x3D;&quot;-19.5&quot; width&#x3D;&quot;101.53125&quot; height&#x3D;&quot;39&quot; class&#x3D;&quot;label-container&quot;&gt;&lt;/rect&gt;
  &lt;/defs&gt;
  &lt;g transform&#x3D;&quot;translate(0, 0)&quot;&gt;
    &lt;g class&#x3D;&quot;edgePaths&quot;&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M65.26878511235955,47L60.35159176029962,51.166666666666664C55.4343984082397,55.333333333333336,45.60001170411985,63.666666666666664,40.682818352059925,72C35.765625,80.33333333333333,35.765625,88.66666666666667,35.765625,92.83333333333333L35.765625,97&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M111.29371488764045,47L116.21090823970037,51.166666666666664C121.1281015917603,55.333333333333336,130.96248829588015,63.666666666666664,135.8796816479401,72C140.796875,80.33333333333333,140.796875,88.66666666666667,140.796875,92.83333333333333L140.796875,97&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M154.54889396067415,136L157.4873595505618,140.16666666666666C160.42582514044943,144.33333333333334,166.30275632022472,152.66666666666666,169.24122191011236,161C172.1796875,169.33333333333334,172.1796875,177.66666666666666,172.1796875,181.83333333333334L172.1796875,186&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M127.04485603932585,136L124.10639044943821,140.16666666666666C121.16792485955057,144.33333333333334,115.29099367977528,152.66666666666666,112.35252808988764,164.25C109.4140625,175.83333333333334,109.4140625,190.66666666666666,109.4140625,205.5C109.4140625,220.33333333333334,109.4140625,235.16666666666666,112.35252808988764,246.75C115.29099367977528,258.3333333333333,121.16792485955057,266.6666666666667,124.10639044943821,270.8333333333333L127.04485603932585,275&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M172.1796875,225L172.1796875,229.16666666666666C172.1796875,233.33333333333334,172.1796875,241.66666666666666,169.24122191011236,250C166.30275632022472,258.3333333333333,160.42582514044943,266.6666666666667,157.4873595505618,270.8333333333333L154.54889396067415,275&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g class&#x3D;&quot;nodes&quot;&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(88.28125,27.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#small-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/a.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(35.765625,116.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#small-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/b.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(140.796875,116.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#small-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/c.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(172.1796875,205.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#small-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/d.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(140.796875,294.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#small-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/e.js&lt;/text&gt;
      &lt;/g&gt;
    &lt;/g&gt;
  &lt;/g&gt;
&lt;/svg&gt;

&lt;p&gt;This is more or less how all browsers saw JS on this site until recently. It
suffers from some problems... You can cache using ETags, but that&amp;#39;s about as
far as you can get without transforming the JS in the modules. It means that
the browser only knows about each dependency as it encounters it in import
statements. We &lt;em&gt;could&lt;/em&gt; do some preloading, but whatever we do it&amp;#39;s still a
request per module, even when the module has been downloaded before and
there are no changes.&lt;/p&gt;
&lt;p&gt;We could try to hash the content of each file, and put it in the filenames (the
&lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#cache_busting&quot;&gt;cache-busting pattern&lt;/a&gt;):&lt;/p&gt;
&lt;svg class&#x3D;&quot;flow-diagram&quot; role&#x3D;&quot;img&quot; focusable&#x3D;&quot;false&quot; aria-labelledby&#x3D;&quot;dependency-tree-with-hashes&quot; height&#x3D;&quot;322&quot; viewBox&#x3D;&quot;0 0 247.9453125 322&quot;&gt;
  &lt;title id&#x3D;&quot;dependency-tree-with-hashes&quot;&gt;Dependency tree with hashed file names&lt;/title&gt;
  &lt;g transform&#x3D;&quot;translate(20, 0)&quot;&gt;
    &lt;g class&#x3D;&quot;edgePaths&quot;&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M65.26878511235955,47L60.35159176029962,51.166666666666664C55.4343984082397,55.333333333333336,45.60001170411985,63.666666666666664,40.682818352059925,72C35.765625,80.33333333333333,35.765625,88.66666666666667,35.765625,92.83333333333333L35.765625,97&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M111.29371488764045,47L116.21090823970037,51.166666666666664C121.1281015917603,55.333333333333336,130.96248829588015,63.666666666666664,135.8796816479401,72C140.796875,80.33333333333333,140.796875,88.66666666666667,140.796875,92.83333333333333L140.796875,97&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M154.54889396067415,136L157.4873595505618,140.16666666666666C160.42582514044943,144.33333333333334,166.30275632022472,152.66666666666666,169.24122191011236,161C172.1796875,169.33333333333334,172.1796875,177.66666666666666,172.1796875,181.83333333333334L172.1796875,186&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M127.04485603932585,136L124.10639044943821,140.16666666666666C121.16792485955057,144.33333333333334,115.29099367977528,152.66666666666666,112.35252808988764,164.25C109.4140625,175.83333333333334,109.4140625,190.66666666666666,109.4140625,205.5C109.4140625,220.33333333333334,109.4140625,235.16666666666666,112.35252808988764,246.75C115.29099367977528,258.3333333333333,121.16792485955057,266.6666666666667,124.10639044943821,270.8333333333333L127.04485603932585,275&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M172.1796875,225L172.1796875,229.16666666666666C172.1796875,233.33333333333334,172.1796875,241.66666666666666,169.24122191011236,250C166.30275632022472,258.3333333333333,160.42582514044943,266.6666666666667,157.4873595505618,270.8333333333333L154.54889396067415,275&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g class&#x3D;&quot;nodes&quot;&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(88.28125,27.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/a-63efa7.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(35.765625,116.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/b-913d04.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(140.796875,116.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/c-f61966.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(172.1796875,205.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/d-732056.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(140.796875,294.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/e-d2df5d.js&lt;/text&gt;
      &lt;/g&gt;
    &lt;/g&gt;
  &lt;/g&gt;
&lt;/svg&gt;

&lt;p&gt;This lets us cache each file immutably. If there is a change to a file, then the
hash changes and the new file is downloaded.&lt;/p&gt;
&lt;p&gt;But there&amp;#39;s a problem with this... when a file changes, its name will change.
When it&amp;#39;s name changes, any files which depend on it will change since the
imports need to be updated! For example, if &lt;code&gt;/e.js&lt;/code&gt; changes, the imports in
&lt;code&gt;/c.js&lt;/code&gt; and &lt;code&gt;/d.js&lt;/code&gt; must be updated, which leads to a change in &lt;code&gt;/a.js&lt;/code&gt; too. The
changes go all the way from the updated module to the top!&lt;/p&gt;
&lt;svg class&#x3D;&quot;flow-diagram&quot; role&#x3D;&quot;img&quot; focusable&#x3D;&quot;false&quot; aria-labelledby&#x3D;&quot;dependency-tree-with-invalidated-hashes&quot; height&#x3D;&quot;322&quot; viewBox&#x3D;&quot;0 0 247.9453125 322&quot;&gt;
  &lt;title id&#x3D;&quot;dependency-tree-with-invalidated-hashes&quot;&gt;Dependency tree with invalidated hashed file names&lt;/title&gt;
  &lt;g transform&#x3D;&quot;translate(20, 0)&quot;&gt;
    &lt;g class&#x3D;&quot;edgePaths&quot;&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M65.26878511235955,47L60.35159176029962,51.166666666666664C55.4343984082397,55.333333333333336,45.60001170411985,63.666666666666664,40.682818352059925,72C35.765625,80.33333333333333,35.765625,88.66666666666667,35.765625,92.83333333333333L35.765625,97&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M111.29371488764045,47L116.21090823970037,51.166666666666664C121.1281015917603,55.333333333333336,130.96248829588015,63.666666666666664,135.8796816479401,72C140.796875,80.33333333333333,140.796875,88.66666666666667,140.796875,92.83333333333333L140.796875,97&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M154.54889396067415,136L157.4873595505618,140.16666666666666C160.42582514044943,144.33333333333334,166.30275632022472,152.66666666666666,169.24122191011236,161C172.1796875,169.33333333333334,172.1796875,177.66666666666666,172.1796875,181.83333333333334L172.1796875,186&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M127.04485603932585,136L124.10639044943821,140.16666666666666C121.16792485955057,144.33333333333334,115.29099367977528,152.66666666666666,112.35252808988764,164.25C109.4140625,175.83333333333334,109.4140625,190.66666666666666,109.4140625,205.5C109.4140625,220.33333333333334,109.4140625,235.16666666666666,112.35252808988764,246.75C115.29099367977528,258.3333333333333,121.16792485955057,266.6666666666667,124.10639044943821,270.8333333333333L127.04485603932585,275&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
      &lt;path class&#x3D;&quot;arrow-path&quot; d&#x3D;&quot;M172.1796875,225L172.1796875,229.16666666666666C172.1796875,233.33333333333334,172.1796875,241.66666666666666,169.24122191011236,250C166.30275632022472,258.3333333333333,160.42582514044943,266.6666666666667,157.4873595505618,270.8333333333333L154.54889396067415,275&quot; marker-end&#x3D;&quot;url(#arrowhead)&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g class&#x3D;&quot;nodes&quot;&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(88.28125,27.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text class&#x3D;&quot;error&quot;&gt;/a-5b5be8.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(35.765625,116.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text&gt;/b-913d04.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(140.796875,116.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text class&#x3D;&quot;error&quot;&gt;/c-7534a8.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(172.1796875,205.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text class&#x3D;&quot;error&quot;&gt;/d-25255e.js&lt;/text&gt;
      &lt;/g&gt;
      &lt;g class&#x3D;&quot;node&quot; transform&#x3D;&quot;translate(140.796875,294.5)&quot;&gt;
        &lt;use href&#x3D;&quot;#large-box&quot;&gt;&lt;/use&gt;
        &lt;text class&#x3D;&quot;error&quot;&gt;/e-bb438d.js&lt;/text&gt;
      &lt;/g&gt;
    &lt;/g&gt;
  &lt;/g&gt;
&lt;/svg&gt;

&lt;p&gt;This is a problem. I don&amp;#39;t want to invalidate caching for modules which have no
meaningful code changes.&lt;/p&gt;
&lt;p&gt;&lt;a href&#x3D;&quot;https://github.com/WICG/import-maps&quot;&gt;Import maps&lt;/a&gt; provide a way out of this quandary. They let us tell
the browser how to resolve imports. This means that instead of rewriting
the imports of each module, we keep them as they originally were. The engine
looks in the import map and resolves accordingly. Changing the content of a
module will mean only its entry in the import map gets updated.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-json&quot;&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;imports&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/a.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/a-5b5be8.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/b.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/b-913d04.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/c.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/c-7534a8.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/d.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/d-25255e.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/e.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/e-bb438d.js&amp;quot;&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For caching this is really nice. Your whole JS application can be cached, and
whenever a module is updated the browser only needs to make a request for the
updated content for that one module.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;implementation&quot;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;For a static site generator the setup is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy the files with their original names into the target directory.&lt;/li&gt;
&lt;li&gt;Copy each file a second time to the same target, but include a prefix and the
hash in the file name.&lt;/li&gt;
&lt;li&gt;Add an import map to each page which uses JS.&lt;/li&gt;
&lt;li&gt;Update the script entry point to use the hashed file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first point allows browsers which don&amp;#39;t understand import maps yet to work
as they did before. Temporary redirects would work too, but mean more requests.
The prefixes of the files with content hashed in their names allows immutable
cache headers to be added when they&amp;#39;re served (the second point).&lt;/p&gt;
&lt;p&gt;The last point was unexpected, but to spec (it&amp;#39;s not just a Chrome quirk).
Browsers which don&amp;#39;t understand import maps actually benefit from this change,
because they can at least cache the entry point.&lt;/p&gt;
&lt;p&gt;The third point hides &lt;em&gt;a lot&lt;/em&gt; of pain. As I add more experiments, I don&amp;#39;t want
the import maps of other pages to grow, so I don&amp;#39;t want just one import map for
all pages, but rather an import map for each page.&lt;/p&gt;
&lt;p&gt;When the files are copied and hashed, a big import map is generated. The code
I&amp;#39;ve written also parses (but does not modify) each module to determine its
dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-json&quot;&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/a.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-a-5b5be8.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/b.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/c.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/b.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-b-913d04.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/c.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-c-7534a8.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/d.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;e.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/d.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-d-25255e.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;e.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;/e.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;hashedFileName&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-e-bb438d.js&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When the map of each page is generated, the entry point is looked up in the big
import map, and its dependencies recursively added too. The hashed entry point
is used in the entry script tag.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;content-security-policy&quot;&gt;Content Security Policy&lt;/h3&gt;
&lt;p&gt;While the import map specification allows for them to be external to the HTML of
a page, no browsers currently implement this&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-2&quot; href&#x3D;&quot;#footnote-2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;. The HTML pages of my static
site have quite strict &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP&quot;&gt;Content Security Policy (CSP)&lt;/a&gt; headers. These
headers prevent the execution of inline scripts and inline CSS, and I prefer not
to relax them.&lt;/p&gt;
&lt;p&gt;The way to resolve the conflict is to &lt;a href&#x3D;&quot;https://content-security-policy.com/hash/&quot;&gt;add the hash of the import map&lt;/a&gt;
to the CSP header of a page. This is like the server telling the browser &lt;em&gt;you
can&amp;#39;t run scripts inlined in the page, except for those with this hash&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I once used Netlify edge functions to accomplish this&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-3&quot; href&#x3D;&quot;#footnote-3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;, but now I
template a &lt;a href&#x3D;&quot;https://docs.netlify.com/routing/headers/&quot;&gt;headers file&lt;/a&gt; with an entry for each HTML page which needs a custom
CSP header for an import map hash.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;preloading&quot;&gt;Preloading&lt;/h3&gt;
&lt;p&gt;One problem with lots of small modules I&amp;#39;ve not addressed yet is that on first
load, the browser only discovers what it needs to fetch as it reads the imports
of each module.&lt;/p&gt;
&lt;p&gt;Since the import maps are created specifically for each page, their values are
a list of URLs for the hashed files which &lt;em&gt;will&lt;/em&gt; be needed. It&amp;#39;s possible to
preload the modules by adding &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload&quot;&gt;module preload&lt;/a&gt; links in the header. Like the
entry point, the browser won&amp;#39;t use the import map to resolve JS modules. For
stuff in HTML you have to do that.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-html&quot;&gt;&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;href&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-every-90ec60.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;href&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-javascript-dc77dc.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;href&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-module-8d541a.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;href&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-needed-54bb3e.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class&#x3D;&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;modulepreload&amp;quot;&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;href&lt;/span&gt;&#x3D;&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;quot;/hashed-later-05fbf2.js&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The solution isn&amp;#39;t perfect though. A hypothetical problem (there are no browsers
like this at the time of writing) is when module preload is supported, but
import maps are not. This would lead to modules being fetched twice (hashed and
original file names). One way out of that would be to have the original
filenames respond with a temporary redirect to the file names with the hashes
in.&lt;/p&gt;
&lt;p&gt;One potential problem is the head of a document growing very large due to lots
of modules mapping to lots of links. In my case each experiment uses a handful
of modules, so the increase in the size of the head is not problematic. The
module preloads spec allows (but does not require) the browser to resolve the
child imports of preloaded modules, so a middle ground may be to preload just
some modules.&lt;/p&gt;
&lt;p&gt;One small saving I make is to exclude preload links for scripts which will
appear in script tags. This includes the &lt;code&gt;index.js&lt;/code&gt; file which applies to all
pages (mostly there to install a service worker), the entry point(s) of any
experiments, and the ruby state persistence script on any page with ruby
annotations. This means that most pages don&amp;#39;t have an import map or any preload
links.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;interactions-with-the-service-worker&quot;&gt;Interactions with the service worker&lt;/h3&gt;
&lt;p&gt;When a file is served with the immutable cache-control header, it&amp;#39;ll either be
in the browser cache or it won&amp;#39;t be (and will trigger a request to populate the
cache). The service worker isn&amp;#39;t needed for such files. It&amp;#39;s only there to make
decisions about more weakly cached files when browsing offline.&lt;/p&gt;
&lt;p&gt;The service worker intercepts all requests, but we don&amp;#39;t want it to handle
ones cached immutably. By returning early from a fetch event handler the handler
delegates the request back to the browser and the browser cache will be used as
if the service worker isn&amp;#39;t there at all.&lt;/p&gt;
&lt;p&gt;Since I&amp;#39;m prefixing immutable resources, with &lt;code&gt;hashed-&lt;/code&gt;, I can check the URL
of the request in the fetch event, and use the prefix to know not to continue.&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;fetch&amp;#x27;&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-function&quot;&gt;&lt;span class&#x3D;&quot;hljs-params&quot;&gt;fetchEvent&lt;/span&gt; &#x3D;&amp;gt;&lt;/span&gt; {
  &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;use strict&amp;#x27;&lt;/span&gt;;

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; isHashed &#x3D; fetchEvent.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;request&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;url&lt;/span&gt;
    .&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;split&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt;)
    .&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;pop&lt;/span&gt;()
    .&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;startsWith&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;hashed-&amp;#x27;&lt;/span&gt;);

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (isHashed) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt;; &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// Delegate to the browser.&lt;/span&gt;
  }

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// ...&lt;/span&gt;
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I like this. My site only uses a service worker to handle caching, and I&amp;#39;d
rather not have one at all. The less it does the better! In an ideal world, all
resources would be immutable, and only the HTML document would not be. In such
a world a request for a page resulting in a 304 response (not changed) would
also mean all resources are already cached, and no further requests are needed.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;conclusion-and-future-work&quot;&gt;Conclusion and future work&lt;/h3&gt;
&lt;p&gt;When I first published this article, the only browsers to support import maps
were those based on Chrome. Firefox shipped support about a month after, and
finally Safari in March 2023. The big browsers now all support import maps!&lt;/p&gt;
&lt;p&gt;This work so far handles all JavaScript files I deploy except for the service
worker. The service worker may be tricky to handle (the URL of the service
worker script is important for the running worker and its cache). I&amp;#39;ll update
this post as I improve coverage.&lt;/p&gt;
&lt;p&gt;For smaller applications which differ in composition from page to page (while
sharing many source modules) I think this solution has advantages over bundling.
There&amp;#39;s less to cache (eliminates bundle overlap) and less to fetch when there
are changes (only fetch changed modules, not a whole bundle).&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1670070141171</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1670070141171"/>
    <title>Link to https://andy-bell.co.uk/working-on-my-personal-site-is-fun-actually/</title>
    <published>2022-12-03T12:22:21Z</published>
    <updated>2022-12-03T12:22:21Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#x27;m seeing more and more sites gain support for webmentions. ❤️ &lt;a href&#x3D;&quot;https://qubyte.codes/links/1670070141171&quot;&gt;Working on my personal site is fun actually - Andy Bell&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/procedural-christmas-cards-2022</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/procedural-christmas-cards-2022"/>
    <title>Procedural Christmas cards 2022</title>
    <published>2022-12-17T15:03:00Z</published>
    <updated>2022-12-17T15:03:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Below is a procedural snowman. I&amp;#39;m using a little code to create Christmas
cards again this year, and as before I wanted each to be unique! If you received
a card from me, you may see something like &lt;code&gt;?seed&#x3D;1234567890&lt;/code&gt; in the URL bar.
That will be the random seed which generated your snowflake (and it&amp;#39;s yours to
keep). To see a random snowflake, remove everything after the question mark and
hit enter. Refresh the page to see a fresh random snowman!&lt;/p&gt;
&lt;p&gt;The red colour is a convenience to make it show up in the software I use to
cut card.&lt;/p&gt;
&lt;p&gt;Full disclosure; I managed the time badly this year. If you got a card after
Christmas then it&amp;#39;s on me (not the mail strikes).&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1671364969820</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1671364969820"/>
    <title>Link to https://paulrobertlloyd.com/articles/2022/12/indiekit/</title>
    <published>2022-12-18T12:02:49Z</published>
    <updated>2022-12-18T12:02:49Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Figuring out bits of indie web interconnectivity is fun for folk like me, but if we want broader adoption then there needs to be a spectrum of tools for all which aren&amp;#x27;t a huge time sink. Indiekit looks great! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1671364969820&quot;&gt;Introducing Indiekit: The IndieWeb for Everyone - Paul Robert Lloyd&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1671710956187</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1671710956187"/>
    <title>Note 74</title>
    <published>2022-12-22T12:09:16Z</published>
    <updated>2022-12-22T12:09:16Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A sample of the &lt;a href&#x3D;&quot;https://qubyte.codes/blog/procedural-christmas-cards-2022&quot;&gt;Christmas cards for this year&lt;/a&gt;. They were rushed, and certainly minimalist, but I’m happy with the result.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1671712318355.jpeg&quot; alt&#x3D;&quot;A hand made Christmas card standing on a table in a cafe, next to a cup of coffee. The card is white, with a blue panel on the front. On top of the panel is a snow man cut from white card with a specular snow shine to it.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1672099062189</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1672099062189"/>
    <title>Note 75</title>
    <published>2022-12-26T23:57:42Z</published>
    <updated>2022-12-26T23:57:42Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m testing syndication of notes from my personal site to mastodon, so here’s a photo of an icicle.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1672098964506.jpeg&quot; alt&#x3D;&quot;A small icicle hanging from a branch of a tree. The view is from below, so the clear blue sky can be seen behind.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1672102309609</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1672102309609"/>
    <title>Note 76</title>
    <published>2022-12-27T00:51:49Z</published>
    <updated>2022-12-27T00:51:49Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Another syndication test. This is a photo of a new pedestrian bridge close to where I live.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1672102069959.jpeg&quot; alt&#x3D;&quot;Taken at night. The bridge has downlighting on either side of the path, showing the light brown colour and slightly rough texture (for grip). On the other side of the bride is the entrance to an elevator down to street level. Some lights are on in the high rise university residences behind.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1672234003829</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1672234003829"/>
    <title>Note 77</title>
    <published>2022-12-28T13:26:43Z</published>
    <updated>2022-12-28T13:26:43Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Work in progress render of revision 2 of my custom split mechanical keyboard.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1672233837072.jpeg&quot; alt&#x3D;&quot;A 3D render of the PCBs for a revision of my custom split keyboard. The vertical column stagger is moved one key outward, so the pinky and index columns are lower than the middle and ring finder columns, but now the pinkies rest on the outer-most column. The thumb clusters are moved one key outward too. This revision also shows holes for per key LEDs.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1672528073110</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1672528073110"/>
    <title>Note 78</title>
    <published>2022-12-31T23:07:53Z</published>
    <updated>2022-12-31T23:07:53Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;ve run out of time to publish it this year (still 2022 here for almost an hour), but I have a full year of language study sessions (special micropub posts with durations in) to crunch the numbers for. Should make for a few interesting plots!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1672735995597</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1672735995597"/>
    <title>Link to https://maggieappleton.com/ai-dark-forest</title>
    <published>2023-01-03T08:53:15Z</published>
    <updated>2023-01-03T08:53:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A good long read on being a human in a web awash with bots and ML generated content. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1672735995597&quot;&gt;The Expanding Dark Forest and Generative AI - Maggie Appleton&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/my-2022-japanese-language-study-habits</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/my-2022-japanese-language-study-habits"/>
    <title>My 2022 Japanese language study habits</title>
    <published>2023-01-03T09:10:00Z</published>
    <updated>2023-01-03T09:10:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This time last year I put together some custom scripts to send a record of each
study session to my personal site using the &lt;a href&#x3D;&quot;https://indieweb.org/Micropub&quot;&gt;micropub&lt;/a&gt; endpoint. You can see
them &lt;a href&#x3D;&quot;/study-sessions&quot;&gt;here&lt;/a&gt;. Each entry says what I did, how long I did it for,
and when I started doing it. On their own these aren&amp;#39;t particularly interesting
or useful. They mostly serve to hold me accountable. However, now that I have a
full year of data, it seems like a good time to see if there are any trends!&lt;/p&gt;
&lt;p&gt;I also took lessons during 2022, but I&amp;#39;m not including those here. Study
sessions as I define them here are solo study time, but that does include
homework. While I have categorized each session, the vast majority of time was
spent on &lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;WaniKani&lt;/a&gt; and &lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;Bunpro&lt;/a&gt;, both of which I categorize as flashcards.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;most&lt;/em&gt; time I spent studying on a single day was 88 minutes.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;least&lt;/em&gt; time I spent studying on a single day was (unsurprisingly) 0 minutes.&lt;/li&gt;
&lt;li&gt;I averaged about 17 minutes a day.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;most&lt;/em&gt; time I spent studying in a calendar week was 5 hours and 47 minutes.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;least&lt;/em&gt; time I spent studying in a calendar week was (regrettably, but we&amp;#39;ll get to that) 0 hours.&lt;/li&gt;
&lt;li&gt;I studied the least on Saturdays (averaging about 11 minutes), and the most on Sundays (averaging about 24 minutes).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There were days and weeks in which I apparently did no study at all. Missing
days may not be surprising (family, holidays, illness, etc.), but whole weeks
surely are. The study minutes for each calendar week plot below helps to explain
what happened.&lt;/p&gt;
&lt;div class&#x3D;&quot;plot&quot;&gt;
  &lt;svg viewBox&#x3D;&quot;-20 -5 580 369&quot; role&#x3D;&quot;img&quot; aria-labelledby&#x3D;&quot;weeks-plot&quot;&gt;
    &lt;title id&#x3D;&quot;weeks-plot&quot;&gt;A plot of total minutes studied for each calendar week&lt;/title&gt;
    &lt;line x1&#x3D;&quot;0&quot; x2&#x3D;&quot;535&quot; y1&#x3D;&quot;347&quot; y2&#x3D;&quot;347&quot; /&gt;
    &lt;line x1&#x3D;&quot;0&quot; x2&#x3D;&quot;0&quot; y1&#x3D;&quot;0&quot; y2&#x3D;&quot;347&quot; /&gt;
    &lt;text x&#x3D;&quot;265&quot; y&#x3D;&quot;362&quot;&gt;week&lt;/text&gt;
    &lt;text transform&#x3D;&quot;rotate(270) translate(-173.5, -5)&quot;&gt;minutes&lt;/text&gt;
    &lt;g transform&#x3D;&quot;translate(10, 347)&quot;&gt;
      &lt;title&gt;Week 2, 150 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-150&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;150&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(20, 347)&quot;&gt;
      &lt;title&gt;Week 3, 103 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-103&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;103&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(30, 347)&quot;&gt;
      &lt;title&gt;Week 4, 76 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-76&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;76&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(40, 347)&quot;&gt;
      &lt;title&gt;Week 5, 142 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-142&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;142&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(50, 347)&quot;&gt;
      &lt;title&gt;Week 6, 103 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-103&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;103&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(60, 347)&quot;&gt;
      &lt;title&gt;Week 7, 58 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-58&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;58&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(70, 347)&quot;&gt;
      &lt;title&gt;Week 8, 50 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-50&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;50&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(80, 347)&quot;&gt;
      &lt;title&gt;Week 9, 55 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-55&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;55&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(90, 347)&quot;&gt;
      &lt;title&gt;Week 10, 21 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-21&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;21&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(100, 347)&quot;&gt;
      &lt;title&gt;Week 11, 20 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-20&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;20&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(200, 347)&quot;&gt;
      &lt;title&gt;Week 21, 15 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-15&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;15&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(210, 347)&quot;&gt;
      &lt;title&gt;Week 22, 25 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-25&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;25&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(220, 347)&quot;&gt;
      &lt;title&gt;Week 23, 145 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-145&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;145&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(230, 347)&quot;&gt;
      &lt;title&gt;Week 24, 130 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-130&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;130&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(240, 347)&quot;&gt;
      &lt;title&gt;Week 25, 135 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-135&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;135&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(250, 347)&quot;&gt;
      &lt;title&gt;Week 26, 45 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-45&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;45&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(260, 347)&quot;&gt;
      &lt;title&gt;Week 27, 163 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-163&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;163&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(270, 347)&quot;&gt;
      &lt;title&gt;Week 28, 100 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-100&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;100&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(280, 347)&quot;&gt;
      &lt;title&gt;Week 29, 140 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-140&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;140&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(290, 347)&quot;&gt;
      &lt;title&gt;Week 30, 192 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-192&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;192&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(300, 347)&quot;&gt;
      &lt;title&gt;Week 31, 181 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-181&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;181&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(310, 347)&quot;&gt;
      &lt;title&gt;Week 32, 196 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-196&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;196&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(320, 347)&quot;&gt;
      &lt;title&gt;Week 33, 165 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-165&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;165&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(330, 347)&quot;&gt;
      &lt;title&gt;Week 34, 230 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-230&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;230&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(340, 347)&quot;&gt;
      &lt;title&gt;Week 35, 172 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-172&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;172&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(350, 347)&quot;&gt;
      &lt;title&gt;Week 36, 303 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-303&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;303&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(360, 347)&quot;&gt;
      &lt;title&gt;Week 37, 297 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-297&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;297&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(370, 347)&quot;&gt;
      &lt;title&gt;Week 38, 268 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-268&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;268&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(380, 347)&quot;&gt;
      &lt;title&gt;Week 39, 293 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-293&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;293&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(390, 347)&quot;&gt;
      &lt;title&gt;Week 40, 347 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-347&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;347&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(400, 347)&quot;&gt;
      &lt;title&gt;Week 41, 242 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-242&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;242&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(410, 347)&quot;&gt;
      &lt;title&gt;Week 42, 239 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-239&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;239&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(420, 347)&quot;&gt;
      &lt;title&gt;Week 43, 180 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-180&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;180&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(430, 347)&quot;&gt;
      &lt;title&gt;Week 44, 156 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-156&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;156&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(440, 347)&quot;&gt;
      &lt;title&gt;Week 45, 170 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-170&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;170&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(450, 347)&quot;&gt;
      &lt;title&gt;Week 46, 141 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-141&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;141&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(460, 347)&quot;&gt;
      &lt;title&gt;Week 47, 99 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-99&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;99&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(470, 347)&quot;&gt;
      &lt;title&gt;Week 48, 115 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-115&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;115&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(480, 347)&quot;&gt;
      &lt;title&gt;Week 49, 184 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-184&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;184&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(490, 347)&quot;&gt;
      &lt;title&gt;Week 50, 132 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-132&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;132&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(500, 347)&quot;&gt;
      &lt;title&gt;Week 51, 92 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-92&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;92&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(510, 347)&quot;&gt;
      &lt;title&gt;Week 52, 146 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-146&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;146&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(520, 347)&quot;&gt;
      &lt;title&gt;Week 53, 127 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-127&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;127&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
  &lt;/svg&gt;
&lt;/div&gt;

&lt;p&gt;Week 1 aside (January 1st 2022 was a Saturday) you can see that I started out
with &lt;em&gt;good intentions&lt;/em&gt;&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-1&quot; href&#x3D;&quot;#footnote-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. At the time
I didn&amp;#39;t have much structure to my study, and the tools I used were not working
well for me. I was using flash card applications like &lt;a href&#x3D;&quot;https://www.memrise.com&quot;&gt;Memrise&lt;/a&gt; and &lt;a href&#x3D;&quot;https://www.busuu.com&quot;&gt;Busuu&lt;/a&gt;
which are general purpose. Japanese (and probably many or most other languages)
is a unique beast, and these general purpose flash card apps didn&amp;#39;t do a great
job in my case.&lt;/p&gt;
&lt;p&gt;At the end of May I resolved to &lt;a href&#x3D;&quot;/blog/its-time-to-build-a-study-habit&quot;&gt;build a study habit&lt;/a&gt;. That&amp;#39;s where you can see
my study minutes really take off. A mixture of finding interesting reading
material within my comprehension and a couple of learning applications (I
mentioned &lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;WaniKani&lt;/a&gt; and &lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;Bunpro&lt;/a&gt; above) helped a lot. These applications are
designed specifically for Japanese, and I&amp;#39;m getting much better results from
them. I also changed my morning routine to include waking up a little earlier,
heading to a local coffee shop, and clearing my review backlog for both each
weekday morning before work.&lt;/p&gt;
&lt;p&gt;I appear to have peaked in the Autumn. At the time I was doing a lot of reading
and I think I need to push in that direction a bit more rather than leaning on
flashcards alone. I have plenty to read to keep me going for a while!&lt;/p&gt;
&lt;p&gt;My resolution for this year is to regain all the ground I&amp;#39;ve lost since I passed
the &lt;a href&#x3D;&quot;https://www.jlpt.jp&quot;&gt;JLPT&lt;/a&gt; N4 exam a decade ago, and sit the N3 exam in December. I&amp;#39;m going to
need to increase my study minutes (I&amp;#39;m still considering where to place this
but a minimum of 30 minutes a day seems like a good goal) and sign up for more
lessons.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1673105691385</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1673105691385"/>
    <title>Link to https://www.dazeddigital.com/fashion/article/51269/1/hackers-1995-cult-movie-angelina-jolie-jonny-lee-miller-costume-design-bts</title>
    <published>2023-01-07T15:34:51Z</published>
    <updated>2023-01-07T15:34:51Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I (a neckbeard) particularly like the bit on real life hackers at the time: &amp;quot;And fucking hell, they were so boring. Dressed in black. Some of them were quite old, actually, and they&amp;#x27;d obviously been into computers for a long time.&amp;quot; &lt;a href&#x3D;&quot;https://qubyte.codes/links/1673105691385&quot;&gt;Never-before-seen Polaroids from the set of cult cyber classic Hackers - Dazed&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1673171966130</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1673171966130"/>
    <title>Like of https://jsrn.net/brighton-ruby-2022</title>
    <published>2023-01-08T09:59:26Z</published>
    <updated>2023-01-08T09:59:26Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://jsrn.net/brighton-ruby-2022&quot;&gt;https://jsrn.net/brighton-ruby-2022&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1673911778282</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1673911778282"/>
    <title>Note 79</title>
    <published>2023-01-16T23:29:38Z</published>
    <updated>2023-01-16T23:29:38Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;m vain, so I have one domain for my personal site, and another for shortlinks. The personal site generates a text file, which the build of the shortlinks site uses when it builds.&lt;/p&gt;
&lt;p&gt;I had Netlify set up to trigger a build of the shortlinks site after the personal site builds, but most triggers were redundant (I don&amp;#39;t have them for notes etc.).&lt;/p&gt;
&lt;p&gt;I decided to replace the hook with a build plugin. &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/main/plugins/update-shortlinks/index.js&quot;&gt;https://github.com/qubyte/qubyte-codes/blob/main/plugins/update-shortlinks/index.js&lt;/a&gt;&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1674319069662</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1674319069662"/>
    <title>Link to https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/syndicate-to-mastodon.yml</title>
    <published>2023-01-21T16:37:49Z</published>
    <updated>2023-01-21T16:37:49Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This is a test of link (bookmarks really) syndication to mastodon using a link to the GitHub Actions workflow used to do the syndication. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1674319069662&quot;&gt;Link syndication workflow&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1674499241102</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1674499241102"/>
    <title>Note 80</title>
    <published>2023-01-23T18:40:41Z</published>
    <updated>2023-01-23T18:40:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I may have a problem.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1674499084068.jpeg&quot; alt&#x3D;&quot;A display of my mechanical keyboard collection. There are five displayed. Two ortholinear, one compact ANSI layout. One full size ANSI layout, and two splits.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/tip-type-narrowing-arrays-for-sorbet-in-ruby</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/tip-type-narrowing-arrays-for-sorbet-in-ruby"/>
    <title>Tip: Type narrowing arrays for sorbet in ruby</title>
    <published>2023-01-28T15:25:00Z</published>
    <updated>2023-01-28T15:25:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;When working with type systems like &lt;a href&#x3D;&quot;https://www.typescriptlang.org&quot;&gt;TypeScript&lt;/a&gt; or &lt;a href&#x3D;&quot;https://sorbet.org&quot;&gt;Sorbet&lt;/a&gt;, type narrowing
patterns are a way to handle different types a variable may contain in different
branches. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;n:&lt;/span&gt; T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)
  ).returns(
    T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;double&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;n&lt;/span&gt;)
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; n
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# n can&amp;#x27;t be nil in this branch, so sorbet&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# knows the type is narrowed to Integer.&lt;/span&gt;
    n * &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;else&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# This branch could be implied, but is&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# here for clarity.&lt;/span&gt;
    &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;nil&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Type narrowing applies to more than just &lt;a href&#x3D;&quot;https://sorbet.org/docs/nilable-types&quot;&gt;&lt;code&gt;T.nilable(X)&lt;/code&gt;&lt;/a&gt; to &lt;code&gt;X&lt;/code&gt;. You
can use it to refine a type to a subset of some types allowed by a &lt;a href&#x3D;&quot;https://sorbet.org/docs/union-types&quot;&gt;union&lt;/a&gt;, or
from a value of some class to a value of a child class.&lt;/p&gt;
&lt;p&gt;This works well on singular values, but when filtering an array sorbet asserts
that the resultant array has the same type. My guess is that this is to ensure
consistency between &lt;code&gt;select&lt;/code&gt; and &lt;code&gt;select!&lt;/code&gt; (the latter modifies in place).&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;array:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;]
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter_and_double_array&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;array&lt;/span&gt;)
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Sorbet thinks that integers is&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# T::Array[T.nilable(Integer)] :(&lt;/span&gt;
  integers &#x3D; array.select { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;n&lt;/span&gt;| n }

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# So it thinks the next line is broken,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# although we know it&amp;#x27;s safe.&lt;/span&gt;
  integers.map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;n&lt;/span&gt;| n * &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; }
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The brute force option here is to &lt;a href&#x3D;&quot;https://sorbet.org/docs/type-assertions#tcast&quot;&gt;&lt;code&gt;T.cast(x, Integer)&lt;/code&gt;&lt;/a&gt; in the &lt;code&gt;map&lt;/code&gt;, but
escape hatches like &lt;code&gt;T.cast&lt;/code&gt; and &lt;a href&#x3D;&quot;https://sorbet.org/docs/type-assertions#tmust&quot;&gt;&lt;code&gt;T.must&lt;/code&gt;&lt;/a&gt; are a last resort. When the
type system is telling us the type we&amp;#39;re safer.  There&amp;#39;s also a runtime overhead
to using &lt;code&gt;T.cast&lt;/code&gt; or &lt;code&gt;T.must&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The solution is to use &lt;code&gt;filter_map&lt;/code&gt;. It can be used to filter out the type(s)
you don&amp;#39;t want (by returning &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;nil&lt;/code&gt;), and &lt;em&gt;return&lt;/em&gt; those you do:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;array:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;]
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter_and_double_array&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;array&lt;/span&gt;)
  integers &#x3D; array.filter_map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;n&lt;/span&gt;| n }
  integers.map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;n&lt;/span&gt;| n * &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; }
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or in this very simple case, the two array operations can be put together:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;array:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;]
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter_and_double&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;array&lt;/span&gt;)
  array.filter_map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;n&lt;/span&gt;| n * &lt;span class&#x3D;&quot;hljs-number&quot;&gt;2&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; n }
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just like in the single value case, this works on more than just &lt;code&gt;T.nilable(X)&lt;/code&gt;
to &lt;code&gt;X&lt;/code&gt;. Filter an array of &lt;code&gt;Numeric&lt;/code&gt; to an array of &lt;code&gt;Integer&lt;/code&gt; (a child type of
&lt;code&gt;Numeric&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;array:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Numeric&lt;/span&gt;]
  ).returns(
    T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;]
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter_non_integers&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;array&lt;/span&gt;)
  array.filter_map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;n&lt;/span&gt;| n &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; n.is_a?(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;) }
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Filter an array of some union or types to a subset of the union, in this case
&lt;code&gt;T.any(String, Integer, Boolean)&lt;/code&gt; to &lt;code&gt;T.any(String, Integer)&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;array:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.any(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;, T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Boolean&lt;/span&gt;)]
  ).returns(
    T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.any(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;filter_non_integers&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;array&lt;/span&gt;)
  array.filter_map &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt; |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;x&lt;/span&gt;|
    x &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; x !&#x3D; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;true&lt;/span&gt; &amp;amp;&amp;amp; x !&#x3D; &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;false&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/tip-find-and-type-narrow-an-element-from-an-array-in-ruby-and-sorbet</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/tip-find-and-type-narrow-an-element-from-an-array-in-ruby-and-sorbet"/>
    <title>Tip: Find and type narrow an element from an array in ruby and sorbet</title>
    <published>2023-01-30T09:05:00Z</published>
    <updated>2023-07-08T14:30:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Recently I had a problem where I had to find the first matching element of an
array by type. Ruby provides a method to return the first matching (or &lt;code&gt;nil&lt;/code&gt;)
element in an array, but sorbet isn&amp;#39;t smart enough to type narrow it when the
match is related to the type of the element.&lt;/p&gt;
&lt;p&gt;For illustrative purposes, here&amp;#39;s a function which takes an array of strings
and integers, and returns the first string element lowercased, or &lt;code&gt;nil&lt;/code&gt; when no
strings are in the array. My first try looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;maybe_strings:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.any(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;)
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;find_first_and_lower&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;maybe_strings&lt;/span&gt;)
  first &#x3D; maybe_strings.find { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;x&lt;/span&gt;| x.is_a?(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;) }

  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# Sorbet thinks that first is &lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# &#x60;T.nilable(T.any(String, Integer))&#x60;,&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# so this doesn&amp;#x27;t work because Integer&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;# has no &#x60;downcase&#x60; method.&lt;/span&gt;
  first&amp;amp;.downcase
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This unfortunately doesn&amp;#39;t work. The code is fine, but sorbet doesn&amp;#39;t (at the
time of writing) understand that the type of &lt;code&gt;first&lt;/code&gt; should be
&lt;code&gt;T.nilable(String)&lt;/code&gt;, so it thinks the last line of the function is incorrect.&lt;/p&gt;
&lt;p&gt;My next attempt was to manually iterate through the array:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;maybe_strings:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.any(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;)
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;find_first_and_lower&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;maybe_strings&lt;/span&gt;)
  maybe_strings.each &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt; |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;s&lt;/span&gt;|
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; s.downcase &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; s.is_a?(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;)
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class&#x3D;&quot;hljs-literal&quot;&gt;nil&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works! It&amp;#39;s pretty ugly though. The only good thing going for it is that it
stops iterating through the array once the string is found (like &lt;code&gt;find&lt;/code&gt;). To
folk new to ruby (me) the &lt;code&gt;return&lt;/code&gt; applying to the function as a whole and not
just the block was jarring too...&lt;/p&gt;
&lt;p&gt;In the end I &lt;a href&#x3D;&quot;/blog/tip-type-narrowing-arrays-for-sorbet-in-ruby&quot;&gt;used &lt;code&gt;filter_map&lt;/code&gt; again&lt;/a&gt;, which encodes most of the
behaviour I want:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;maybe_strings:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.any(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;)
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;find_first_and_lower&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;maybe_strings&lt;/span&gt;)
  first &#x3D; maybe_strings
    .filter_map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;x&lt;/span&gt;| x &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; x.is_a?(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;) }
    .first

  first&amp;amp;.downcase
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works too! It&amp;#39;s not &lt;em&gt;quite&lt;/em&gt; ideal though, because the &lt;code&gt;filter_map&lt;/code&gt; will
build us a whole new array when we only want the first (if any) element. That&amp;#39;s
why I&amp;#39;ve kept the &lt;code&gt;downcase&lt;/code&gt; outside the &lt;code&gt;filter_map&lt;/code&gt;. The solution was to
make the &lt;code&gt;filter_map&lt;/code&gt; lazy:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;maybe_strings:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.any(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;)
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;find_first_and_lower&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;maybe_strings&lt;/span&gt;)
  first &#x3D; maybe_strings
    .lazy
    .filter_map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;x&lt;/span&gt;| x.downcase &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; x.is_a?(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;) }
    .first
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#39;ve moved the &lt;code&gt;downcase&lt;/code&gt; call into the &lt;code&gt;filter_map&lt;/code&gt; now because it looks a
little cleaner, and it will only apply to the first found element.&lt;/p&gt;
&lt;p&gt;Bonus: How about getting the &lt;em&gt;last&lt;/em&gt; matching element? While there&amp;#39;s a &lt;code&gt;.last&lt;/code&gt;
method I could use, I don&amp;#39;t want to iterate over the whole array to get to it.
It turns out that &lt;code&gt;reverse_each&lt;/code&gt; returns an enumerator when it&amp;#39;s called without
a block:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-ruby&quot;&gt;sig &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;do&lt;/span&gt;
  params(
    &lt;span class&#x3D;&quot;hljs-symbol&quot;&gt;maybe_strings:&lt;/span&gt; T::&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Array&lt;/span&gt;[T.any(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;, &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Integer&lt;/span&gt;)]
  ).returns(
    T.nilable(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;)
  )
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;find_first_and_lower&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;maybe_strings&lt;/span&gt;)
  first &#x3D; maybe_strings
    .reverse_each
    .lazy
    .filter_map { |&lt;span class&#x3D;&quot;hljs-params&quot;&gt;x&lt;/span&gt;| x.downcase &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; x.is_a?(&lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;) }
    .first
&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Otherwise, the solution looks the same, and like the one for the first element
it only looks at as many entries (from the end of the array this time) as it
needs.&lt;/p&gt;
&lt;p&gt;Finally, a note on performance. If you plan to use lazy iteration in a
performance sensitive code path, you should benchmark it with realistic data to
see how it performs for you. It may be that an eager &lt;code&gt;filter_map&lt;/code&gt; performs
better on average for your use case, even if it does process an entire array.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1675203385391</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1675203385391"/>
    <title>Reply to https://css-irl.info/scheduling-netlify-deployments-with-github-actions/</title>
    <published>2023-01-31T22:16:25Z</published>
    <updated>2023-01-31T22:16:25Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;Yes! I do this too. I have one workflow on a 15 minute interval to check and deploy scheduled posts (which means some logic), but my favourite has to be the one which runs at midnight on Jan 1st every year. It&amp;amp;#39;s just a curl post like yours to trigger a build so all my copyright year footers get bumped.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1675289131896</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1675289131896"/>
    <title>Note 81</title>
    <published>2023-02-01T22:05:31Z</published>
    <updated>2023-02-01T22:05:31Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Photos never do a sunset justice, but this was a magnificent one.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1675289060890.jpeg&quot; alt&#x3D;&quot;The sunset over the houses to the rear of my house. Stunning peach and pink shaded clouds.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1675560608445</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1675560608445"/>
    <title>Note 82</title>
    <published>2023-02-05T01:30:08Z</published>
    <updated>2023-02-05T01:30:08Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;One pain in the ass thing about micropub is that there&amp;#39;s no way to send photos and content together in a single request &lt;em&gt;and&lt;/em&gt; include alt text. It can only be done with a multipart request to a media endpoint for the photo and a JSON request to the regular endpoint. Why not allow both in a multipart request? #indieweb&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1675561019609</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1675561019609"/>
    <title>Note 83</title>
    <published>2023-02-05T01:36:59Z</published>
    <updated>2023-02-05T01:36:59Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I love the #indieweb, but the aforementioned micropub thing isn&amp;#39;t the only friction. JSON encoded microformats-2 documents are just plain weird. Everything is an array for some reason. Unnecessarily complex to consume. We can surely do better...&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1675562037657</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1675562037657"/>
    <title>Note 84</title>
    <published>2023-02-05T01:53:57Z</published>
    <updated>2023-02-05T01:53:57Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I get why microformats documents came to look like this. It comes from their roots as data embedded in HTML. But it&amp;#39;s not very pragmatic. Why would a document have more than one name or content? Why would photo (singular) be used for a &lt;em&gt;plural&lt;/em&gt; field. It&amp;#39;s all out of whack. #indieweb.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1675563760896</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1675563760896"/>
    <title>Note 85</title>
    <published>2023-02-05T02:22:40Z</published>
    <updated>2023-02-05T02:22:40Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;An example of a more pragmatic approach to image uploads, for example, is the mastodon API. You still have to use a separate multipart request for an image, but the description (alt) text accompanies the image as an additional field. I think you can probably use the same image (and by extension its alt text) for multiple posts if you want. This fits because alt text is descriptive, so it shouldn&amp;#39;t depend on the context the image is used in. #indieweb&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1676040767750</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1676040767750"/>
    <title>Note 86</title>
    <published>2023-02-10T14:52:47Z</published>
    <updated>2023-02-10T14:52:47Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;One of the doves (they fledged last year) is roosting near us again this year.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1676040651753.jpeg&quot; alt&#x3D;&quot;A white dove standing beside some seed on a wall outside my house. Some bird shit can be seen on the window behind it.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1676135374997</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1676135374997"/>
    <title>Like of https://adactio.com/links/19897</title>
    <published>2023-02-11T17:09:34Z</published>
    <updated>2023-02-11T17:09:34Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/links/19897&quot;&gt;https://adactio.com/links/19897&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1676724204480</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1676724204480"/>
    <title>Note 87</title>
    <published>2023-02-18T12:43:24Z</published>
    <updated>2023-02-18T12:43:24Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Last night we went to see Mogwai at The Brighton Dome. Just amazing.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1676724047577.jpeg&quot; alt&#x3D;&quot;The band Mogwai on stage at The Brighton Dome. Seen from the left circle.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1677526275266</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1677526275266"/>
    <title>Note 88</title>
    <published>2023-02-27T19:31:15Z</published>
    <updated>2023-02-27T19:31:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A pop up gallery below Brighton station.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1677526216726.jpeg&quot; alt&#x3D;&quot;Viewed from the back toward the entrance. Cobbled floor. Art hung on the walls.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1677781517348</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1677781517348"/>
    <title>Link to https://charlottedann.com/article/magical-vector-fields</title>
    <published>2023-03-02T18:25:17Z</published>
    <updated>2023-03-02T18:25:17Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A really nice deep dive on noise and vector fields with inline demos to play with! &lt;a href&#x3D;&quot;https://qubyte.codes/links/1677781517348&quot;&gt;Magical vector fields - Charlotte Dann&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1678552448422</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1678552448422"/>
    <title>Note 89</title>
    <published>2023-03-11T16:34:08Z</published>
    <updated>2023-03-11T16:34:08Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Word gets around…&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1678552387050.jpeg&quot; alt&#x3D;&quot;Four white doves huddled together, eating seed scattered on a wall.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1678553853247</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1678553853247"/>
    <title>Note 90</title>
    <published>2023-03-11T16:57:33Z</published>
    <updated>2023-03-11T16:57:33Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;m really enjoying my POSSE setup. I publish to my micropub endpoint using my own iOS shortcuts for photos or study sessions (as events). For plain notes and bookmarks I typically use Omnibear.&lt;/p&gt;
&lt;p&gt;The micropub endpoint pushes to a repository on GitHub, and an Actions workflow picks them up and syndicates photos, notes, and bookmarks to Mastodon. The workflow is fast enough that stuff frequently reaches Mastodon before Netlify has finished building my site. #indieweb&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1678554183580</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1678554183580"/>
    <title>Note 91</title>
    <published>2023-03-11T17:03:03Z</published>
    <updated>2023-03-11T17:03:03Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I should probably make some sequence diagrams. My setup is a bit of a Rube Goldberg machine, but it&amp;#39;s surprisingly robust and really convenient to use. For example, sharing photos happens right in the iOS photos app because shortcuts integrates into sharesheets. #indieweb&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1678636160571</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1678636160571"/>
    <title>Note 92</title>
    <published>2023-03-12T15:49:20Z</published>
    <updated>2023-03-12T15:49:20Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Testing tweaks to my plain note shortcut.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1679154187931</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1679154187931"/>
    <title>Note 93</title>
    <published>2023-03-18T15:43:07Z</published>
    <updated>2023-03-18T15:43:07Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Ever since I figured out how to build monochrome (black and white or any other pair of colours) PNGs I’ve been thinking about hiding secret messages in the unused bits…&lt;/p&gt;
&lt;p&gt;Monochrome PNGs have a bit depth of 1 (when encoded efficiently), which means that each pixel only needs one bit. Each row of the image is an integer number of octets though, so when the number of pixels in a row isn’t divisible by 8 you get some unused padding bits at the end.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1680164687332</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1680164687332"/>
    <title>Note 94</title>
    <published>2023-03-30T08:24:47Z</published>
    <updated>2023-03-30T08:24:47Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I went to see Devin Townsend and his touring band play at the Bexhill De La Warr Pavilion on Tuesday. It was a good concert, even by his standards.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1680164556919.jpeg&quot; alt&#x3D;&quot;Devin on the stage with the band behind him. He’s thanking the crowd after the gig. The audience can be see clapping and arms raised in appreciation.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1680947829672</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1680947829672"/>
    <title>Note 95</title>
    <published>2023-04-08T09:57:09Z</published>
    <updated>2023-04-08T09:57:09Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Last time I visited Japan (about four years ago!) mobile data was kind of a pain in the ass. I would rent a little mobile wifi hotspot. This time it looks like eSIMs are so well supported that I&amp;#39;m just going to use my phone.&lt;/p&gt;
&lt;p&gt;That doesn&amp;#39;t sound like a lot but it&amp;#39;ll be huge. It means my partner and I can always be connected, even when we&amp;#39;re off doing different things. Those hotspots run hot too (I guess the clue is in the name), so I always had to plug them into a battery, which meant I had to carry a bag (not enough pockets for all the things). This way I&amp;#39;ll be able to enjoy my time there as if I live there again.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1681338239636</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1681338239636"/>
    <title>Note 96</title>
    <published>2023-04-12T22:23:59Z</published>
    <updated>2023-04-12T22:23:59Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Posted from a plane somewhere probably over Nepal.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1681824469188</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1681824469188"/>
    <title>Note 97</title>
    <published>2023-04-18T13:27:49Z</published>
    <updated>2023-04-18T13:27:49Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It&amp;#39;s been a few years since I was last in Japan, and my phone now supports Suica for travel and low value purchases. It&amp;#39;s really, really good. My only complaint is that if I want to use my watch the readers are all on the wrong side of the train turnstiles.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682055382713</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682055382713"/>
    <title>Note 98</title>
    <published>2023-04-21T05:36:22Z</published>
    <updated>2023-04-21T05:36:22Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m at my in-laws, and the place is wonderfully remote after a week spent in Tokyo.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1682055207191.jpeg&quot; alt&#x3D;&quot;The upstairs view from a house in the Japanese countryside. It’s a sunny day with a mostly clear sky. A small polytunnel can be seen in the foreground with some other small plots prepared for crops. In the background are green mountains covered with trees.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682138820158</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682138820158"/>
    <title>Note 99</title>
    <published>2023-04-22T04:47:00Z</published>
    <updated>2023-04-22T04:47:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Through the power of ✨physics ✨my son managed to throw a stone at his own head.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682480777127</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682480777127"/>
    <title>Note 100</title>
    <published>2023-04-26T03:46:17Z</published>
    <updated>2023-04-26T03:46:17Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Absolute scenes.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1682480592906.jpeg&quot; alt&#x3D;&quot;A photo of a steep (downward), narrow alley in the rain. A red-leafed tree is in the foreground on the left, and houses either side. In the background are green mountains and mist.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682756307013</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682756307013"/>
    <title>Note 101</title>
    <published>2023-04-29T08:18:27Z</published>
    <updated>2023-04-29T08:18:27Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;m on my own in Tokyo for a few days (back from the countryside). I arrived early this afternoon, and I&amp;#39;m allowing myself just the remaining hours of today to visit old haunts.&lt;/p&gt;
&lt;p&gt;I might allow myself a bit more time though, toward the end. While most of my time is set aside for some site-seeing of things I missed back when I lived here, I think it&amp;#39;s important to just do nothing in a place to really take it in.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682759551034</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682759551034"/>
    <title>Note 102</title>
    <published>2023-04-29T09:12:31Z</published>
    <updated>2023-04-29T09:12:31Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;High on the list of places to see which I missed before was Kanda Myojin. I know, shameful given how important it is.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1682759408296.jpeg&quot; alt&#x3D;&quot;The main gate of Kanda Myojin, a major shrine close to Akihabara. The gate is large and ornate, with lots of red and white wooden structure visible.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682763529279</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682763529279"/>
    <title>Note 103</title>
    <published>2023-04-29T10:18:49Z</published>
    <updated>2023-04-29T10:18:49Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Next up, Kanda Matsuya. This is a highly regarded soba restaurant which I walked past every weekday for two and a half years and never got around to trying. Very tasty.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1682763354485.jpeg&quot; alt&#x3D;&quot;The ceiling lamp of the restaurant. It’s large and rectangular, dominating the ceiling. It’s composed of a square, dark wooden lattice and white paper reminiscent of traditional Japanese sliding doors. Some patrons can be seen below.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682764135307</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682764135307"/>
    <title>Note 104</title>
    <published>2023-04-29T10:28:55Z</published>
    <updated>2023-04-29T10:28:55Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Now I’m in the Hitachino Nest Brew Lab in the converted Manseibashi Station arches. It’s full of is Western Folk and blasting English pop music from ten years ago but it’s still pretty good in here. Ironically I was at the actual brewery in Ibaraki yesterday.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1682763942323.jpeg&quot; alt&#x3D;&quot;The sign board above the bar of the Manseibashi Hitachino Nest Brew Lab. the signs are hand drawn in black and backlit on a white board. &quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1682994576003</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1682994576003"/>
    <title>Note 105</title>
    <published>2023-05-02T02:29:36Z</published>
    <updated>2023-05-02T02:29:36Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I love it when I find a good spot for a coffee. This one has it all. Fairly calm and quiet. Close to the museums in Ueno, so some foot traffic for people watching. Trees. Shade.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1682994394454.jpeg&quot; alt&#x3D;&quot;The view from the patio of a cafe. In front is a quiet road, and on the other side green trees and some pedestrians walking by an old brick wall. It’s a sunny day, but the photo is taken from the shade of an awning.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1683164998964</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1683164998964"/>
    <title>Note 106</title>
    <published>2023-05-04T01:49:58Z</published>
    <updated>2023-05-04T01:49:58Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Yesterday I visited the Hikawa Maru in Yokohama. It’s a museum now, but started life in the ‘30s as a luxury liner. I recommend it. Anyway, I took this photo at 12:05. Five minutes earlier the &lt;em&gt;horn&lt;/em&gt; went off next to my head and I nearly shit myself.&lt;/p&gt;
&lt;p&gt;Not a whistle.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1683164725912.jpeg&quot; alt&#x3D;&quot;A photograph of a brass lever with a few rotary setting. The detailing on the housing shows one of the labels to be “silent”. A sign above the lever reads: “When the ship was in active service, turning this lever could sound a whistle. At present, too, a whistle is blown every day at noon.”&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1683165898587</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1683165898587"/>
    <title>Note 107</title>
    <published>2023-05-04T02:04:58Z</published>
    <updated>2023-05-04T02:04:58Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The one issue I have with my iOS shortcuts flow for posting images to my personal site (and by extension mastodon) is that the input box for alt text appears over the image I’m posting, so I have to remember what the image looks like. Not a problem on a tablet or larger device because I can do it side by side, but I mainly do it from my phone since it’s also my camera.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1683365816421</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1683365816421"/>
    <title>Note 108</title>
    <published>2023-05-06T09:36:56Z</published>
    <updated>2023-05-06T09:36:56Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m on a BA flight, somewhere over the Bearing Sea, and BA have decided to enable free streaming of the coronation over Wi-Fi. Also they gave me a packet of Eton Mess flavour popcorn.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1683365741497.jpeg&quot; alt&#x3D;&quot;A small packet of Eton Mess popcorn. The design sports a union flag.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1684656416847</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1684656416847"/>
    <title>Note 109</title>
    <published>2023-05-21T08:06:56Z</published>
    <updated>2023-05-21T08:06:56Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’ve been trying to get a photo of our local rook for months. It doesn’t stay put for long, and the light is usually all wrong. Finally I got this while eating breakfast just now.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1684656271905.jpeg&quot; alt&#x3D;&quot;A picture of a rook perched on a house roof. The sky is clear. The white face of the rook is obvious.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1687079807933</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1687079807933"/>
    <title>Note 110</title>
    <published>2023-06-18T09:16:47Z</published>
    <updated>2023-06-18T09:16:47Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m tantalisingly close to replacing LaTeX statically rendered to SVG with MathJax with presentational MathML. It’s bulkier to write, but not difficult. Browser rendering (chrome seems quirkiest) isn’t quite there yet though.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1688123256989</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1688123256989"/>
    <title>Note 111</title>
    <published>2023-06-30T11:07:36Z</published>
    <updated>2023-06-30T11:07:36Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This talk on documentation here at #BrightonRuby by Kaitlyn Tierney is &lt;em&gt;gold&lt;/em&gt;. I’ll definitely use some of these tips for my personal site.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1688917279834</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1688917279834"/>
    <title>Note 112</title>
    <published>2023-07-09T15:41:19Z</published>
    <updated>2023-07-09T15:41:19Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;⚠︎ Testing syndicated posting to mastodon with spoiler text. ⚠︎&lt;/p&gt;&lt;p&gt;Follow the link to reveal.&lt;/p&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1694603040384</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1694603040384"/>
    <title>Note 113</title>
    <published>2023-09-13T11:04:00Z</published>
    <updated>2023-09-13T11:04:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Unexpected upside to having a mouse problem in the neighbourhood… a falcon just perched on our garden wall! Unfortunately I wasn’t fast enough to snap a photo of it.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1696201219546</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1696201219546"/>
    <title>Note 114</title>
    <published>2023-10-01T23:00:19Z</published>
    <updated>2023-10-01T23:00:19Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’ve arrived in Toronto on business, and the hotel has pie.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1696201061345.jpeg&quot; alt&#x3D;&quot;A slice of cream pie. It’s about 5cm deep, with a radius of about 12cm and an arc of about 45 degrees. It’s topped with shards of white chocolate, coconut, and chocolate brittle. It’s served on a white place, upon a low wooden coffee table.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1696245476866</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1696245476866"/>
    <title>Note 115</title>
    <published>2023-10-02T11:17:56Z</published>
    <updated>2023-10-02T11:17:56Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Jet lag has its perks! Up before sunrise for a coffee at a nice little place called Fahrenheit in downtown Toronto.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1696245377641.jpeg&quot; alt&#x3D;&quot;A photograph of a cup of black coffee on a wooden bar.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1696517473287</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1696517473287"/>
    <title>Note 116</title>
    <published>2023-10-05T14:51:13Z</published>
    <updated>2023-10-05T14:51:13Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Toronto at dusk.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1696517405820.jpeg&quot; alt&#x3D;&quot;The Toronto skyline as viewed from the roof of the Shopify offices. The CN tower is in frame.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1696546535205</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1696546535205"/>
    <title>Note 117</title>
    <published>2023-10-05T22:55:35Z</published>
    <updated>2023-10-05T22:55:35Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’ve only been away on business since Sunday, but I miss my family &lt;em&gt;so&lt;/em&gt; much. Thank goodness I’m on the way back.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1696903389133</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1696903389133"/>
    <title>Note 118</title>
    <published>2023-10-10T02:03:09Z</published>
    <updated>2023-10-10T02:03:09Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A high fibre diet promotes regular shit-posting. This lad installed ours today.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1696903228326.jpeg&quot; alt&#x3D;&quot;A man in a high-vis jacket and hard hat about to ascend a cordoned off telegraph pole to string a fibre from it to my house.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1697999909239</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1697999909239"/>
    <title>Note 119</title>
    <published>2023-10-22T18:38:29Z</published>
    <updated>2023-10-22T18:38:29Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I got to see Pip Blom’s first ever acoustic set at Resident Records in Brighton. It was amazing fun!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1697999718930.jpeg&quot; alt&#x3D;&quot;Pip Blom and co playing a set. Two up front on acoustic guitars on a tiny temporary stage in front of the shop counter, and one on synths behind the counter. Both up front are singing.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1698582606333</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1698582606333"/>
    <title>Note 120</title>
    <published>2023-10-29T12:30:06Z</published>
    <updated>2023-10-29T12:30:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I made a cheesecake the other day.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1698582491759.jpeg&quot; alt&#x3D;&quot;A slice of white chocolate cheesecake on a small plate. A raspberry is perched atop the slice, and the handle of a fork can be seen behind the slice. The small plate is on a wooden table.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1698678096122</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1698678096122"/>
    <title>Note 121</title>
    <published>2023-10-30T15:01:36Z</published>
    <updated>2023-10-30T15:01:36Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Big skies over Brighton these days.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1698677990783.jpeg&quot; alt&#x3D;&quot;The view from my home towards the ocean off of Brighton. A sliver of the sea can be seen over the houses, and you can just about make out the wind farm. Above are some dramatic clouds of various kinds. Above those the sky is blue.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1699386639456</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1699386639456"/>
    <title>Note 122</title>
    <published>2023-11-07T19:50:39Z</published>
    <updated>2023-11-07T19:50:39Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Hello #IndieWeb enthusiasts. I’m holding an unofficial Homebrew Website Club at the Lord Nelson Inn near Brighton station tomorrow evening. I’ll be there around 17:30, wrapping up around 20:00. #ffconf folk most welcome!&lt;/p&gt;
&lt;p&gt;I’ll post more information when I’ve found a spot, but if you look for a balding dude with a beard and a dubious waxed moustache the odds are good that you’ll find me.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1702314910745</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1702314910745"/>
    <title>Note 123</title>
    <published>2023-12-11T17:15:10Z</published>
    <updated>2023-12-11T17:15:10Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’ve been solving #AdventOfCode23 with Swift this year, it’s not been completely smooth sailing, but I really enjoy the language.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/my-2023-japanese-language-study-habits</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/my-2023-japanese-language-study-habits"/>
    <title>My 2023 Japanese language study habits</title>
    <published>2024-01-07T11:20:00Z</published>
    <updated>2024-01-07T11:20:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;ve been tracking my Japanese language study session since 2022. You can see
the &lt;a href&#x3D;&quot;/blog/my-2022-japanese-language-study-habits&quot;&gt;first year report&lt;/a&gt; for
comparison.&lt;/p&gt;
&lt;p&gt;This year my regular Japanese tutor has been taking maternity leave, so I&amp;#39;ve
been doing conversation lessons with another tutor. The format is a little
different, and I think this change helped to spark some improvements in my
development. Before a typical lesson I&amp;#39;m given an article to read in Japanese.
I then spend part of the lesson chatting with my tutor, and the rest re-reading
the article, answering comprehension questions, and discussing it. These lessons
aren&amp;#39;t included in the data, but I do include the pre-lesson read of an article
as reading time. As for last time year, while I have categorized each session,
the vast majority of time was spent on &lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;WaniKani&lt;/a&gt; and &lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;Bunpro&lt;/a&gt;, both of which I
categorize as flashcards.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;most&lt;/em&gt; time I spent studying on a single day was 80 minutes.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;least&lt;/em&gt; time I spent studying on a single day was (unsurprisingly) 0
minutes.&lt;/li&gt;
&lt;li&gt;I averaged about 16 minutes a day.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;most&lt;/em&gt; time I spent studying in a calendar week was 2 hours and 56
minutes.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;least&lt;/em&gt; time I spent studying in a calendar week was (regrettably) 0
hours.&lt;/li&gt;
&lt;li&gt;I studied the least on Fridays (averaging about 10 minutes), and the most on
Sundays (averaging about 25 minutes).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There were days and weeks in which I apparently did no study at all. Missing
days may not be surprising (family, holidays, illness, etc.), but whole weeks
surely are. The study minutes for each calendar week plot below helps to explain
what happened.&lt;/p&gt;
&lt;div class&#x3D;&quot;plot&quot;&gt;
  &lt;svg viewBox&#x3D;&quot;-20 -5 550 198&quot; role&#x3D;&quot;img&quot; aria-labelledby&#x3D;&quot;weeks-plot&quot;&gt;
    &lt;title id&#x3D;&quot;weeks-plot&quot;&gt;A plot of total minutes studied for each calendar week&lt;/title&gt;
    &lt;line x1&#x3D;&quot;0&quot; x2&#x3D;&quot;485&quot; y1&#x3D;&quot;176&quot; y2&#x3D;&quot;176&quot; /&gt;
    &lt;line x1&#x3D;&quot;0&quot; x2&#x3D;&quot;0&quot; y1&#x3D;&quot;0&quot; y2&#x3D;&quot;176&quot; /&gt;
    &lt;text x&#x3D;&quot;265&quot; y&#x3D;&quot;191&quot;&gt;week&lt;/text&gt;
    &lt;text transform&#x3D;&quot;rotate(270) translate(-88, -5)&quot;&gt;minutes&lt;/text&gt;
    &lt;g transform&#x3D;&quot;translate(0, 176)&quot;&gt;
      &lt;title&gt;Week 1, 22 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-22&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;22&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(10, 176)&quot;&gt;
      &lt;title&gt;Week 2, 172 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-172&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;172&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(20, 176)&quot;&gt;
      &lt;title&gt;Week 3, 165 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-165&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;165&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(30, 176)&quot;&gt;
      &lt;title&gt;Week 4, 169 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-169&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;169&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(40, 176)&quot;&gt;
      &lt;title&gt;Week 5, 140 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-140&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;140&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(50, 176)&quot;&gt;
      &lt;title&gt;Week 6, 162 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-162&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;162&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(60, 176)&quot;&gt;
      &lt;title&gt;Week 7, 152 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-152&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;152&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(70, 176)&quot;&gt;
      &lt;title&gt;Week 8, 143 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-143&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;143&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(80, 176)&quot;&gt;
      &lt;title&gt;Week 9, 159 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-159&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;159&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(90, 176)&quot;&gt;
      &lt;title&gt;Week 10, 120 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-120&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;120&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(100, 176)&quot;&gt;
      &lt;title&gt;Week 11, 77 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-77&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;77&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(110, 176)&quot;&gt;
      &lt;title&gt;Week 12, 86 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-86&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;86&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(120, 176)&quot;&gt;
      &lt;title&gt;Week 13, 76 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-76&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;76&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(130, 176)&quot;&gt;
      &lt;title&gt;Week 14, 152 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-152&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;152&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(140, 176)&quot;&gt;
      &lt;title&gt;Week 15, 110 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-110&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;110&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(150, 176)&quot;&gt;
      &lt;title&gt;Week 16, 20 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-20&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;20&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(160, 176)&quot;&gt;
      &lt;title&gt;Week 17, 0 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;0&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;0&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(170, 176)&quot;&gt;
      &lt;title&gt;Week 18, 0 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;0&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;0&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(180, 176)&quot;&gt;
      &lt;title&gt;Week 19, 37 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-37&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;37&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(190, 176)&quot;&gt;
      &lt;title&gt;Week 20, 176 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-176&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;176&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(200, 176)&quot;&gt;
      &lt;title&gt;Week 21, 171 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-171&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;171&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(210, 176)&quot;&gt;
      &lt;title&gt;Week 22, 167 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-167&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;167&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(220, 176)&quot;&gt;
      &lt;title&gt;Week 23, 163 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-163&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;163&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(230, 176)&quot;&gt;
      &lt;title&gt;Week 24, 176 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-176&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;176&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(240, 176)&quot;&gt;
      &lt;title&gt;Week 25, 112 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-112&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;112&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(250, 176)&quot;&gt;
      &lt;title&gt;Week 26, 117 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-117&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;117&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(260, 176)&quot;&gt;
      &lt;title&gt;Week 27, 144 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-144&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;144&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(270, 176)&quot;&gt;
      &lt;title&gt;Week 28, 138 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-138&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;138&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(280, 176)&quot;&gt;
      &lt;title&gt;Week 29, 144 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-144&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;144&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(290, 176)&quot;&gt;
      &lt;title&gt;Week 30, 100 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-100&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;100&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(300, 176)&quot;&gt;
      &lt;title&gt;Week 31, 140 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-140&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;140&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(310, 176)&quot;&gt;
      &lt;title&gt;Week 32, 165 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-165&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;165&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(320, 176)&quot;&gt;
      &lt;title&gt;Week 33, 91 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-91&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;91&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(330, 176)&quot;&gt;
      &lt;title&gt;Week 34, 94 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-94&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;94&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(340, 176)&quot;&gt;
      &lt;title&gt;Week 35, 103 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-103&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;103&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(350, 176)&quot;&gt;
      &lt;title&gt;Week 36, 85 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-85&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;85&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(360, 176)&quot;&gt;
      &lt;title&gt;Week 37, 121 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-121&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;121&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(370, 176)&quot;&gt;
      &lt;title&gt;Week 38, 144 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-144&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;144&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(380, 176)&quot;&gt;
      &lt;title&gt;Week 39, 95 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-95&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;95&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(390, 176)&quot;&gt;
      &lt;title&gt;Week 40, 86 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-86&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;86&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(400, 176)&quot;&gt;
      &lt;title&gt;Week 41, 66 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-66&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;66&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(410, 176)&quot;&gt;
      &lt;title&gt;Week 42, 139 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-139&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;139&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(420, 176)&quot;&gt;
      &lt;title&gt;Week 43, 105 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-105&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;105&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(430, 176)&quot;&gt;
      &lt;title&gt;Week 44, 100 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-100&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;100&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(440, 176)&quot;&gt;
      &lt;title&gt;Week 45, 139 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-139&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;139&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(450, 176)&quot;&gt;
      &lt;title&gt;Week 46, 126 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-126&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;126&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(460, 176)&quot;&gt;
      &lt;title&gt;Week 47, 100 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-100&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;100&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(470, 176)&quot;&gt;
      &lt;title&gt;Week 48, 107 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-107&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;107&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(480, 176)&quot;&gt;
      &lt;title&gt;Week 49, 75 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-75&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;75&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(490, 176)&quot;&gt;
      &lt;title&gt;Week 50, 61 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-61&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;61&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(500, 176)&quot;&gt;
      &lt;title&gt;Week 51, 33 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-33&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;33&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(510, 176)&quot;&gt;
      &lt;title&gt;Week 52, 64 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-64&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;64&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
    &lt;g transform&#x3D;&quot;translate(520, 176)&quot;&gt;
      &lt;title&gt;Week 53, 100 minutes&lt;/title&gt;
      &lt;rect x&#x3D;&quot;1&quot; y&#x3D;&quot;-100&quot; width&#x3D;&quot;8&quot; height&#x3D;&quot;100&quot;&gt;&lt;/rect&gt;
    &lt;/g&gt;
  &lt;/svg&gt;
&lt;/div&gt;

&lt;p&gt;The gap you see was when I was in Japan! I think I can give myself a pass for
that.&lt;/p&gt;
&lt;p&gt;One vague trend I see is a decline in minutes studied over the year. I also
studied slightly less over this year than last year. Within the two flashcard
apps I lean most heavily on, my progress has slowed a lot too. Partly I think
this is because I&amp;#39;ve built up a collection of cards I find difficult to
memorize, and that&amp;#39;s both hurting my motivation and constricting the flow of new
cards. I&amp;#39;ll make a more focussed effort to memorize these difficult cards, even
if it means I have to write them out by hand a lot or something.&lt;/p&gt;
&lt;p&gt;Another thing I&amp;#39;ve noticed is that with the grammar focussed app (&lt;a href&#x3D;&quot;https://www.wanikani.com&quot;&gt;Bunpro&lt;/a&gt;) is
that it&amp;#39;s way more than just a flashcard app, and I&amp;#39;m underutilizing it. This is
not just to get the most value out of it; I&amp;#39;ve found that simple repetition
isn&amp;#39;t enough to learn many grammar points (what I think of as natural
acquisition, like a child might learn). I&amp;#39;m going to have to sit down and start
writing proper notes out. Of course, I have the &lt;a href&#x3D;&quot;/japanese-notes&quot;&gt;notes section&lt;/a&gt;
of this site, but nothing beats doing it by hand with glittery gel pens.&lt;/p&gt;
&lt;p&gt;My resolutions &lt;a href&#x3D;&quot;/blog/my-2022-japanese-language-study-habits&quot;&gt;from last year&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...regain all the ground I&amp;#39;ve lost since I passed
the &lt;a href&#x3D;&quot;https://www.jlpt.jp&quot;&gt;JLPT&lt;/a&gt; N4 exam a decade ago, and sit the N3 exam in December. I&amp;#39;m going to
need to increase my study minutes (I&amp;#39;m still considering where to place this
but a minimum of 30 minutes a day seems like a good goal) and sign up for more
lessons.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;#39;m &lt;em&gt;almost&lt;/em&gt; back to where I was when I passed the N4, and in terms of listening
and speaking I&amp;#39;m more advanced thanks to signing up for more tuition. As for
sitting the N3... not even close. That&amp;#39;s a good goal to have, but in hindsight
it was a high target given the free time I have around work and family.&lt;/p&gt;
&lt;p&gt;This year? More of the same! Regain the remaining lost ground with respect to
N4, improve my conversation skills, and work toward sitting the N3. I don&amp;#39;t know
if I&amp;#39;ll do that this year (I&amp;#39;d say probably not given my trajectory). Goals
should be measurable though, so here&amp;#39;s what I&amp;#39;m promising myself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Properly write out 5 grammar points a week, guided by Bunpro.&lt;/li&gt;
&lt;li&gt;Revise one grammar point per day.&lt;/li&gt;
&lt;li&gt;Read one article on &lt;a href&#x3D;&quot;https://www3.nhk.or.jp/news/easy/&quot;&gt;NHK News Web Easy&lt;/a&gt; a
day.&lt;/li&gt;
&lt;li&gt;Continue with flashcards. At least fifteen minutes per day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those don&amp;#39;t represent a lot of time, but they &lt;em&gt;do&lt;/em&gt; need organization and
discipline, which I&amp;#39;m not so great at. Wish me luck!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1708160564168</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1708160564168"/>
    <title>Link to https://flamedfury.com/posts/making-websites-should-be-easy/</title>
    <published>2024-02-17T09:02:44Z</published>
    <updated>2024-02-17T09:02:44Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;We absolutely should be lowering the bar to entry owning websites. Beyond the markup and hosting, I think there&amp;#x27;s a lot we can do to make DNS less scary (for example). &lt;a href&#x3D;&quot;https://qubyte.codes/links/1708160564168&quot;&gt;Making Websites Should Be Easy - Flamed Fury&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1708350683461</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1708350683461"/>
    <title>Link to https://jamesg.blog/2024/02/19/personal-website-ideas/</title>
    <published>2024-02-19T13:51:23Z</published>
    <updated>2024-02-19T13:51:23Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Lots of great ideas here! I find 14 particularly appealing. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1708350683461&quot;&gt;100 things you can do on your personal website - James&amp;#x27; Coffee Blog&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1708719435036</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1708719435036"/>
    <title>Like of https://remysharp.com/2024/02/23/why-my-code-isnt-in-typescript</title>
    <published>2024-02-23T20:17:15Z</published>
    <updated>2024-02-23T20:17:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://remysharp.com/2024/02/23/why-my-code-isnt-in-typescript&quot;&gt;https://remysharp.com/2024/02/23/why-my-code-isnt-in-typescript&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1708773630232</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1708773630232"/>
    <title>Note 124</title>
    <published>2024-02-24T11:20:30Z</published>
    <updated>2024-02-24T11:20:30Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I finally found time to visit Ikigai Coffee in Brighton. It seems like the baristas in all the other places rave about it, and I can see why. The coffee is amazing.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1708773451366.jpeg&quot; alt&#x3D;&quot;A delicate, simple glass bottle of pour-over coffee. It has a little blue rope tied around the neck. It is presented beside a ridged black cup upon a small, stainless steel tray.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1709111830466</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1709111830466"/>
    <title>Link to https://indieweb.org/2024/Brighton</title>
    <published>2024-02-28T09:17:10Z</published>
    <updated>2024-02-28T09:17:10Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Not long to go how, and only a few (free) tickets left! Come and join us on 9th and 10th March. #IndieWeb &lt;a href&#x3D;&quot;https://qubyte.codes/links/1709111830466&quot;&gt;IndieWebCamp Brighton 2024&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/why-did-i-create-a-keyboard</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/why-did-i-create-a-keyboard"/>
    <title>Why did I create a keyboard?</title>
    <published>2024-02-29T21:00:00Z</published>
    <updated>2024-02-29T21:00:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;In 2021 I found myself falling down a mechanical keyboard rabbit hole. It
started off with two ortholinear (keys laid out in a grid) keyboards requiring
minimal assembly (mostly adding my own switches and key caps). From there I
moved onto a split keyboard (one unit per hand, connected together by a cable)
with a column staggered layout (keys are aligned in columns, rather than rows
like a conventional keyboard). In the end it was inevitable that I would try to
design my own, but what did I want?&lt;/p&gt;
&lt;h2 id&#x3D;&quot;the-wish-list&quot;&gt;The wish list&lt;/h2&gt;
&lt;p&gt;Constraints breed creativity, so I decided to be restrictive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It should be a split keyboard, like a &lt;a href&#x3D;&quot;https://github.com/foostan/crkbd&quot;&gt;Corne&lt;/a&gt; or a &lt;a href&#x3D;&quot;https://github.com/pierrechevalier83/ferris&quot;&gt;Ferris&lt;/a&gt;. Allowing
the hands to be further apart helps to avoid some fatigue, and means the
position of each hand can be adjusted. It also enforces which hand owns which
keys (no wandering to the wrong side of the keyboard).&lt;/li&gt;
&lt;li&gt;It should use only one controller, and no IO extenders. Most split keyboards
use one of these approaches, but it seems wasteful to me just to save on a few
wires bridging the two halves of the keyboard. Usually a TTRS cable is used
(a headphone cable). The number of wires depends on a few things, but the
lower bound using a matrix (there are other approaches which can use fewer
wires) is twice the square root of the number of keys, rounded up.&lt;/li&gt;
&lt;li&gt;It should be a 36 key build, with &lt;a href&#x3D;&quot;https://github.com/manna-harbour/miryoku&quot;&gt;Miryoku&lt;/a&gt; bindings. The idea here is to
minimize how far each digit needs to move, again to reduce fatigue.&lt;/li&gt;
&lt;li&gt;It should be built for my hands. I have long fingers and a particular way of
positioning my hands which feels comfortable for me. I&amp;#39;ve noticed that most
split keyboards tuck the thumb keys as a short row or arc below the finger
columns. My thumbs want to splay out more than that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id&#x3D;&quot;pcb-printed-circuit-board-design&quot;&gt;PCB (printed circuit board) design&lt;/h2&gt;
&lt;p&gt;While it&amp;#39;s possible (and fairly common) to hand-wire keyboards, the constraints
above led to a rough idea of what I wanted, which included some visible PCB. PCB
design is new to me, so I had to figure out how to do that. Thankfully there are
guides (I used &lt;a href&#x3D;&quot;https://github.com/ruiqimao/keyboard-pcb-guide&quot;&gt;this one&lt;/a&gt;). Since I was going with a controller, I
didn&amp;#39;t need to worry about much of this guide. The important bit for me was how
to do a key matrix. The software I used is KiCad, which is free, and libraries
can be installed using plain old git submodules (ordinarily I&amp;#39;d balk at
submodules, but this is a rare use case where they make sense). I ignored the
guide and borrowed the libraries used by other custom keyboard kits since many
of them are in GitHub.&lt;/p&gt;
&lt;p&gt;PCB design in KiCad is split into different parts. First you have to lay out a
schematic, and then you use than to send parts to a PCB for layout and to join
parts together with tracks. The nice thing about doing it this way is that the
schematic is abstract. You can wire things together without considering layout,
and in the PCB design view you arrange parts (particularly switches for a
keyboard) and you&amp;#39;re given guides to show which parts still need to be
connected. Here&amp;#39;s what my schematic looked like:&lt;/p&gt;
&lt;img src&#x3D;&quot;/images/fg-11-rev1-schematic.png&quot; width&#x3D;&quot;800&quot; height&#x3D;&quot;508&quot; lazy&gt;

&lt;p&gt;At the top centre is a pro-micro, with all its pins labelled. This is straight
out of a component library. The two grids on the left and right are the key
switch matrix. While there is a group of switches per hand, the matrix is joined
through the IDC connectors in the middle. Labels are used to show how things are
connected without traces cluttering up the diagram. For things like switches you
can choose and change footprints, so if you want to replace MX switches with
choc switches (which is something I did half-way through), the schematic doesn&amp;#39;t
change, just the footprints used on the PCB.&lt;/p&gt;
&lt;p&gt;TODO: PCB design and layout.&lt;/p&gt;
&lt;p&gt;The result is the FG-11 Rev1:&lt;/p&gt;
&lt;picture&gt;
  &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/1665735132683.avif, /images/1665735132683-2x.avif 2x&quot;&gt;
  &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/1665735132683.webp, /images/1665735132683-2x.webp 2x&quot;&gt;
  &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/1665735132683.jpeg&quot; alt&#x3D;&quot;The FG-11 split keyboard. Each side has 23 keys. The keys for the fingers are layed out in five columns or three rows on each side. The second and third column from the inside are one key higher further from the typist than the other columns, to suit where my middle and ring fingers sit. There are three thumb keys on each side, arranged along a diagonal. The PCB of each side can be seen peaking out on the inside edge, and the controller can be seen on the left hand. A ribbon cable connects the two sides from PCB to PCB. The plates are stainless steel and key caps white.&quot; width&#x3D;&quot;800&quot; height&#x3D;&quot;440&quot; loading&#x3D;&quot;lazy&quot;&gt;
&lt;/picture&gt;

&lt;h2 id&#x3D;&quot;retrospective&quot;&gt;Retrospective&lt;/h2&gt;
&lt;p&gt;After a few months using the device I&amp;#39;ve had time to understand what worked well
and what did not.&lt;/p&gt;
&lt;p&gt;Firstly, &lt;a href&#x3D;&quot;https://github.com/manna-harbour/miryoku&quot;&gt;Miryoku&lt;/a&gt; wasn&amp;#39;t for me. I like the idea, but it&amp;#39;s just too much of a
jump from conventional typing for me. I ended up creating a custom layered
layout, which covers all the keys I actually use (no mouse keys).&lt;/p&gt;
&lt;p&gt;Next, the column stagger is good, but the wrong columns are staggered. If you
think of the little notches or nubbins on the &lt;code&gt;f&lt;/code&gt; and &lt;code&gt;j&lt;/code&gt; keys, most people
(myself included) seek these with their index fingers. The column stagger I
designed for the FG-11 Rev1 effectively wanted makes me place my middle fingers
on these keys, so it&amp;#39;s like learning to type again because everything is shifted
one column over.&lt;/p&gt;
&lt;p&gt;On the stagger, this had the intended effect of having my hands in a sort of
cupped position, with the wrists at 45 degrees to the ground (halfway between
flat against the desk and vertical). This is a comfortable typing position for
me.&lt;/p&gt;
&lt;p&gt;The ribbon to connect the two sides is very flexible. I had no trouble
positioning each half wherever I wanted with it.&lt;/p&gt;
&lt;p&gt;One weird thing that occasionally happens is that a modifier key (typically
shift) will stop working. I think this is a software issue since other keys in
the row and column of the affected key are fine, and unplugging the keyboard for
a bit seems to fix the issue. I may try updating QMK and re-flashing the
controller to see if it helps.&lt;/p&gt;
&lt;p&gt;On the build, the laser cut steel plates have a very pleasing look and feel. The
give a lot of weight to the keyboard too, which I really like. I&amp;#39;ve found that
split keyboards often feel too light.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;the-future&quot;&gt;The future&lt;/h2&gt;
&lt;p&gt;The keyboard is good, but flawed. The next revision will have these changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Column stagger moved outwards one column on each side.&lt;/li&gt;
&lt;li&gt;Per-key backlighting. From the schematics of similar split keyboards I&amp;#39;m
pretty sure the controller can support enough LEDs to cover 36 keys.&lt;/li&gt;
&lt;li&gt;Brass plates. I like the steel plates, but I think brass plates will work well
with the Work Louder choc key caps.&lt;/li&gt;
&lt;li&gt;I&amp;#39;m considering a different connector standard to join the two halves. Maybe
something silly like a pair of PS/2 connectors per side, or a D-Sub 15
connector per side. I may keep the ribbon for flexibility though. It&amp;#39;d be nice
to source something which isn&amp;#39;t a bland grey.&lt;/li&gt;
&lt;li&gt;If I can make the power budget stretch, I may add a couple of extra pinky keys
per side. The 5x4 matrix can support this with no need for additional wires
between the two halves. I&amp;#39;d definitely like to have escape and tab keys on the
left, and enter and backspace on the right would be nice too. Except for
backspace the FG-11 Rev1 uses chording to achieve these, and it&amp;#39;s a bit of a
faff.&lt;/li&gt;
&lt;/ul&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1710062971035</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1710062971035"/>
    <title>Note 125</title>
    <published>2024-03-10T09:29:31Z</published>
    <updated>2024-03-10T09:29:31Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Group photo for #IndieWebCampBrighton day 1.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1710062881792.jpeg&quot; alt&#x3D;&quot;The group photo for IndieWebCamp day 1. It’s a sunny day, and there are 26 folk in the photo, with a building behind them.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1710071622767</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1710071622767"/>
    <title>Note 126</title>
    <published>2024-03-10T11:53:42Z</published>
    <updated>2024-03-10T11:53:42Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I walked the Under Cliff Walk between Brighton Marina and Rottingdean last weekend.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1710071531361.jpeg&quot; alt&#x3D;&quot;A wave breaking over a concrete slipway. There are chalk cliffs on the left, and a blue sky with some fluffy clouds&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1710103246654</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1710103246654"/>
    <title>Note 127</title>
    <published>2024-03-10T20:40:46Z</published>
    <updated>2024-03-10T20:40:46Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I spent most of Dune: Part Two trying to get rid of a persistant earworm in form of Fatboy Slim’s Weapon of Choice. If you know, you know.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1710278182347</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1710278182347"/>
    <title>Like of https://adactio.com/journal/20968</title>
    <published>2024-03-12T21:16:22Z</published>
    <updated>2024-03-12T21:16:22Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/journal/20968&quot;&gt;https://adactio.com/journal/20968&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1710280601588</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1710280601588"/>
    <title>Note 128</title>
    <published>2024-03-12T21:56:41Z</published>
    <updated>2024-03-12T21:56:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I was gonna blog about my time co-organising and attending IndieWebCamp Brighton 2024, but instead I&amp;#39;ve spent the evening debugging and fixing the webmention dispatcher I 100% perfectly authored and broke at some point.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1710414416957</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1710414416957"/>
    <title>Link to https://ohhelloana.blog/just-get-a-website/</title>
    <published>2024-03-14T11:06:56Z</published>
    <updated>2024-03-14T11:06:56Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;So much this! Something along these lines has become my go-to when talking about personal sites. A personal site can be a simple &amp;quot;hello&amp;quot; on a single page. Then it can become whatever you like as time goes on, or not! It&amp;#x27;s all about you, not _influencing_, or _thought leading_. Just you. And maybe your cat. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1710414416957&quot;&gt;You don&amp;#x27;t have to be a “content creator” to have a website. - Oh Hello Ana&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1710613347668</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1710613347668"/>
    <title>Like of https://localghost.dev/blog/some-things-i-ve-been-enjoying-recently/</title>
    <published>2024-03-16T18:22:27Z</published>
    <updated>2024-03-16T18:22:27Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://localghost.dev/blog/some-things-i-ve-been-enjoying-recently/&quot;&gt;https://localghost.dev/blog/some-things-i-ve-been-enjoying-recently/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1710615134657</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1710615134657"/>
    <title>Note 129</title>
    <published>2024-03-16T18:52:14Z</published>
    <updated>2024-03-16T18:52:14Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I accidentally took a photo of a bee’s arse.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1710614900959.jpeg&quot; alt&#x3D;&quot;A close-up photo of some cherry blossom, with an unanticipated honey bee in the centre of the shot, with its behind facing us.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/indiewebcamp-brighton-2024</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/indiewebcamp-brighton-2024"/>
    <title>IndieWebCamp Brighton 2024</title>
    <published>2024-03-16T23:00:00Z</published>
    <updated>2024-03-18T03:05:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Last weekend we held &lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton&quot;&gt;IndieWebCamp Brighton 2024&lt;/a&gt;, the first in Brighton since
2019. I thought I&amp;#39;d collect my thoughts both as a host, and as an attendee.&lt;/p&gt;
&lt;p&gt;The first signs something like this might happen (only from my perspective) were
back in November, when I tried to gather people for an impromptu
&lt;a href&#x3D;&quot;https://indieweb.org/Homebrew_Website_Club&quot;&gt;Homebrew Website Club&lt;/a&gt; while people were in town for &lt;a href&#x3D;&quot;https://2023.ffconf.org&quot;&gt;ffconf 2023&lt;/a&gt;. I had
[commented to Ana] on Mastodon that I was missing the Homebrew Website Club that
used to run here in Brighton, and she suggested something close to such an
event. That met with limited success because of the short notice, but &lt;a href&#x3D;&quot;https://paulrobertlloyd.com&quot;&gt;Paul&lt;/a&gt;
came, and I think we both realized that there may be enough interest. I didn&amp;#39;t
know it at the time, but Paul had attended &lt;a href&#x3D;&quot;https://indieweb.org/2023/Nuremberg&quot;&gt;IndieWebCamp Nuremberg 2023&lt;/a&gt; just
a few weeks before (which is where the seeds for Brighton 2024 were &lt;em&gt;actually&lt;/em&gt;
planted).&lt;/p&gt;
&lt;p&gt;So we (mostly Paul) got on with organizing things. I&amp;#39;m a member of &lt;a href&#x3D;&quot;https://theskiff.org&quot;&gt;The Skiff&lt;/a&gt;,
which we chose for the venue, so I took charge of that bit. On the weekend it
meant being first there to open up and last out to lock up. I also took on the
pastries for the first day&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-1&quot; href&#x3D;&quot;#footnote-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, and good local coffee, teas,
and milk and milk alternatives. On the weekend I also did my best to make folk
feel at home and to tell people about the place and a little local
history&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-2&quot; href&#x3D;&quot;#footnote-2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;saturday-introductions-planning-and-discussions&quot;&gt;Saturday: Introductions, planning, and discussions&lt;/h2&gt;
&lt;p&gt;After refreshments and some introductions, we started the planning session.
IndieWebCamps follow the &lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/BarCamp#Structure_and_participatory_process&quot;&gt;BarCamp&lt;/a&gt; model of collectively planning session.
&lt;a href&#x3D;&quot;https://adactio.com/journal/20968&quot;&gt;Jeremy&lt;/a&gt; officiated the process, and once we&amp;#39;d found a good &lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/Schedule&quot;&gt;schedule&lt;/a&gt; of
discussions, spaces, and times.&lt;/p&gt;
&lt;picture&gt;
  &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/1710294021680.avif, /images/1710294021680-2x.avif 2x&quot;&gt;
  &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/1710294021680.webp, /images/1710294021680-2x.webp 2x&quot;&gt;
  &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/1710294021680.jpeg&quot; alt&#x3D;&quot;Jeremy Keith, Ana Rodrigues, Ros, and Francesco Figari around a whiteboard. The board is split into columns for spaces, rows for times, and each cell has a yellow sticky note for a topic and a blue sticky note with the champion name on.&quot; width&#x3D;&quot;800&quot; height&#x3D;&quot;600&quot; loading&#x3D;&quot;lazy&quot;&gt;
&lt;/picture&gt;

&lt;p&gt;There was a session I wanted to participate in for each time slot.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;energy-efficiency&quot;&gt;&lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/energy-efficiency&quot;&gt;Energy Efficiency&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I wanted to be a part of this one because I&amp;#39;ve put so much effort into getting
the footprint of my own site as low as possible. We discussed efficiency
server-side, client-side, in transmission, and the costs and benefits of single
servers vs large data centres.&lt;/p&gt;
&lt;p&gt;We explored static sites versus dynamically served sites. Static sites are a
good way to serve efficiently, but harder to integrate IndieWeb standards like
&lt;a href&#x3D;&quot;https://www.w3.org/TR/webmention/&quot;&gt;Webmentions&lt;/a&gt; into.&lt;/p&gt;
&lt;p&gt;We also discussed how our efforts were a drop in the ocean compared with the
vast energy overheads of the elephants like Facebook. I argued that my time with
the IndieWeb helped me to advocate for more efficient means of creating and
deploying sites in the companies I work for.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;pictures&quot;&gt;&lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/pictures&quot;&gt;Pictures&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This site uses a static site build, and the content is committed to a git
repository. At the moment, so are the images. This bothers me because git works
best with text files, and images will slow down clones over time. I&amp;#39;ve been
wondering about storing images separately. Fortunately for me, others share this
pain point, and that&amp;#39;s what we talked about in this session.&lt;/p&gt;
&lt;p&gt;Various bucket-like solutions were discussed, and &lt;a href&#x3D;&quot;https://www.lazaruscorporation.co.uk/artists/paul-watson&quot;&gt;Paul Watson&lt;/a&gt; shared that a
combination of S3 and CloudFront cost him on the order of cents per month for a
site which is image heavy (making it, and similar combinations through other
vendors a good solution).&lt;/p&gt;
&lt;h3 id&#x3D;&quot;hosting&quot;&gt;&lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/hosting&quot;&gt;Hosting&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This was a discussion about the different kinds of hosting. For example, static
sites hosted on GitHub, Netlify, Vercel, etc. on one end of the spectrum,
virtual private servers (or even real personal servers) on the other end, and
middle ground solutions like DreamHost which help to administer server
applications in the middle.&lt;/p&gt;
&lt;p&gt;We discussed the pros and cons of them all, including avoiding hosting traps
which make it hard to move your site. There&amp;#39;s a clear need for a decision tree
to help folk starting out to make a good choice without overwhelming them with
lots of information. There&amp;#39;s lots more in the linked discussion.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;nfc&quot;&gt;&lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/nfc&quot;&gt;NFC&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This was a fun session. &lt;a href&#x3D;&quot;https://shkspr.mobi/blog/&quot;&gt;Terence&lt;/a&gt; bought some NFC tags for us to encode and play
with. I configured mine to trigger my study-session shortcut (which sends an
h-event to my site with the category and duration of a session) when I wave my
phone over it. I successfully did not accidentally try to hijack the session by
talking about an ingenious spying device called &lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/The_Thing_%28listening_device%29&quot;&gt;The Thing&lt;/a&gt;, which is an
ancestor technology.&lt;/p&gt;
&lt;h3 id&#x3D;&quot;personal-website-pain-points&quot;&gt;&lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/pains&quot;&gt;Personal website pain points&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The final session of the day was a sort of support group, where we could talk
about the various problems with our sites as they are now that prevent us from
posting to them more, or which just bother us.&lt;/p&gt;
&lt;p&gt;A theme which emerged over Saturday was &lt;em&gt;URL regret&lt;/em&gt;. Earlier decisions about
the structure of a site determine URLs of pages in a way which no longer seems
correct. For example, this URL for this page is under &lt;code&gt;/blog/&lt;/code&gt;, but I don&amp;#39;t
really think of many posts I&amp;#39;ve written on this site as blog entries. In fact,
internally they&amp;#39;ve always been managed as &lt;em&gt;posts&lt;/em&gt;. URLs link the web together,
so we don&amp;#39;t want to break them! The options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep pages where they are.&lt;/li&gt;
&lt;li&gt;Move pages and add permanent redirects to them so old links continue to work.&lt;/li&gt;
&lt;li&gt;Move the pages, and accept that links will break. Perhaps give just the most
important pages redirects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href&#x3D;&quot;https://maggieappleton.com/garden-history&quot;&gt;Digital gardening&lt;/a&gt; was another theme of the day. A traditional blog lends
itself to the idea of a &lt;em&gt;feed&lt;/em&gt;. A linear collection of entries, usually in
chronological order of publication. Chronology is much less important in a
digital garden, since the intent is to update posts over time&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-3&quot; href&#x3D;&quot;#footnote-3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;. The move from linear
feeds to gardening was the root of some URL regret in this session.&lt;/p&gt;
&lt;picture&gt;
  &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/1710730970838.avif, /images/1710730970838-2x.avif 2x&quot;&gt;
  &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/1710730970838.webp, /images/1710730970838-2x.webp 2x&quot;&gt;
  &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/1710730970838.jpeg&quot; alt&#x3D;&quot;The group photo for Saturday. Twenty six people stood in front of a building. The sun is shining and folk are in shirts, hoodies, and a few jackets.&quot; width&#x3D;&quot;800&quot; height&#x3D;&quot;450&quot; loading&#x3D;&quot;lazy&quot;&gt;
&lt;/picture&gt;

&lt;h2 id&#x3D;&quot;sunday-hacking-writing-and-demos&quot;&gt;&lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/Demos&quot;&gt;Sunday: Hacking, writing, and demos&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After a day of discussion it feels fantastic to build something! The day was
split into about three hours in the morning and three hours after lunch for
hacking and writing, and demos after.&lt;/p&gt;
&lt;p&gt;In the morning, &lt;a href&#x3D;&quot;https://scoutaloud.net&quot;&gt;Scout&lt;/a&gt; and I paired to buy a domain name, and wire DNS up to
&lt;a href&#x3D;&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;. Scout is an organizer at the &lt;a href&#x3D;&quot;https://codebar.io/brighton&quot;&gt;CodeBar Brighton&lt;/a&gt; chapter, so
she&amp;#39;s no stranger to GitHub (making pages an ideal starting place)! Of course,
being DNS, it took us a while to figure out its interaction with GitHub&amp;#39;s funky
way of doing things, but we got there. This was the highlight of the weekend for
me. Another independent, personal site on the web!&lt;/p&gt;
&lt;p&gt;I used the afternoon to scratch some itches. I recently wrote a &lt;a href&#x3D;&quot;/colophon&quot;&gt;colophon&lt;/a&gt;, but
it needed some editing and nothing linked to it, so I gave it a bit of polish
and added a link to it from &lt;a href&#x3D;&quot;/&quot;&gt;my about page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next I worked on CSS. The main CSS file (the one used by every page here) was at
risk of becoming a bit of a dumping ground. Most pages don&amp;#39;t use syntax
highlighting or MathML styles, so I put those in their own files. Both are third
party anyway, so it makes sense to keep them separate and versioned with their
associated dependencies. This was mostly plumbing work to configure my static
site generator.&lt;/p&gt;
&lt;p&gt;Finally, I tweaked a little of the code I use for &lt;a href&#x3D;&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby&quot;&gt;ruby annotations&lt;/a&gt;. Each page
with Japanese text on it has a little select dropdown which can be used to
position the annotation above or below the main text, or hide it entirely.
&lt;a href&#x3D;&quot;/blog/controlling-ruby-annotation-positioning-and-appearance-with-pure-css-and-a-select-box&quot;&gt;Thanks to the &lt;code&gt;:has()&lt;/code&gt; CSS pseudo-class&lt;/a&gt; no JavaScript is needed to
do this. &lt;em&gt;Remembering&lt;/em&gt; the preference, however, does need JS. So these pages
have a tiny JS script which stores the setting in &lt;code&gt;localStorage&lt;/code&gt;. I tweaked the
script so that when the user sets it to &amp;quot;above&amp;quot; (the default), the script
deletes the setting, rather than storing &amp;quot;above&amp;quot;.&lt;/p&gt;
&lt;p&gt;To wrap up the day &lt;a href&#x3D;&quot;https://indieweb.org/2024/Brighton/Demos&quot;&gt;we demoed what we&amp;#39;d written and made&lt;/a&gt;. Click through
to explore those (there&amp;#39;s too much good stuff to do it justice here).&lt;/p&gt;
&lt;h2 id&#x3D;&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The other attendees kindly helped me to restore The Skiff to its original layout
and clean up. Once we were done, I locked up, and a few of us went for a well
deserved drink.&lt;/p&gt;
&lt;picture&gt;
  &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/1710606604896.avif, /images/1710606604896-2x.avif 2x&quot;&gt;
  &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/1710606604896.webp, /images/1710606604896-2x.webp 2x&quot;&gt;
  &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/1710606604896.jpeg&quot; alt&#x3D;&quot;A photograph of my lanyard. It has the IndieWebCamp logo at the bottom, and diagonal yellow, orange, and red bands above. My name and web address are written on in thick black pen.&quot; width&#x3D;&quot;800&quot; height&#x3D;&quot;600&quot; loading&#x3D;&quot;lazy&quot;&gt;
&lt;/picture&gt;


    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1710678255149</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1710678255149"/>
    <title>Like of https://adactio.com/links/20979</title>
    <published>2024-03-17T12:24:15Z</published>
    <updated>2024-03-17T12:24:15Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://adactio.com/links/20979&quot;&gt;https://adactio.com/links/20979&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1711875919363</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1711875919363"/>
    <title>Like of https://www.claudinec.net/posts/2024-03-31-coffee-decision-tree/</title>
    <published>2024-03-31T09:05:19Z</published>
    <updated>2024-03-31T09:05:19Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://www.claudinec.net/posts/2024-03-31-coffee-decision-tree/&quot;&gt;https://www.claudinec.net/posts/2024-03-31-coffee-decision-tree/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1711878945173</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1711878945173"/>
    <title>Like of https://jamesg.blog/2024/03/30/coffee-to-drink-decision-tree/</title>
    <published>2024-03-31T09:55:45Z</published>
    <updated>2024-03-31T09:55:45Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://jamesg.blog/2024/03/30/coffee-to-drink-decision-tree/&quot;&gt;https://jamesg.blog/2024/03/30/coffee-to-drink-decision-tree/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1713184086327</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1713184086327"/>
    <title>Note 130</title>
    <published>2024-04-15T12:28:06Z</published>
    <updated>2024-04-15T12:28:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Inspired by &lt;a href&#x3D;&quot;https://chrisnewtn.com/posts/2024-03-10-image-captions.html&quot;&gt;Chris Newton’s work on captioned images in Markdown&lt;/a&gt;, I decided to alter rendering of images both to add an optional caption, but also to include alternative formats and scales using &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements. I was previously doing this all by hand with inline HTML and it somehow hadn’t occurred to me!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1714034338218</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1714034338218"/>
    <title>Note 131</title>
    <published>2024-04-25T08:38:58Z</published>
    <updated>2024-04-25T08:38:58Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;In 2009 I moved to Japan for my first postdoc position. I was still living like a student, so I got a room in a foreigner house. When I moved in, I didn&amp;#39;t know what to expect and my Japanese was basically non-existent. Fortunately for me, everyone living there at that time spoke English as a second language. Unfortunately for them, Estuary English is a really odd accent and to them I was just a weird guy drinking tea in a bathrobe making very English noises. Eventually I realised and adapted.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1714224809454</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1714224809454"/>
    <title>Note 132</title>
    <published>2024-04-27T13:33:29Z</published>
    <updated>2024-04-27T13:33:29Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;After a year and a half working on Scala 2 applications, I&amp;#39;ve gone from enamoured to jaded. It&amp;#39;s proof that smart people can design extremely stupid things.&lt;/p&gt;
&lt;p&gt;Oh, you want an example?&lt;/p&gt;
&lt;p&gt;Try &lt;code&gt;0L until Instant.now&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s a range, right? It&amp;#39;s the interval of long integers, containing the numbers &lt;math&gt;&lt;mrow&gt;&lt;mo form&#x3D;&quot;prefix&quot; stretchy&#x3D;&quot;false&quot;&gt;[&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;mo separator&#x3D;&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo form&#x3D;&quot;postfix&quot; stretchy&#x3D;&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/math&gt;, so it&amp;#39;s completely expressible as a pair of longs.&lt;/p&gt;
&lt;p&gt;BUT NOT TO SCALA. Scala is like &lt;em&gt;fuck you there aren&amp;#39;t enough regular ints to make this long int range&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So, so stupid.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1714903172746</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1714903172746"/>
    <title>Like of https://localghost.dev/blog/new-keyboard-alert-mykeyclub-mkc75/</title>
    <published>2024-05-05T09:59:32Z</published>
    <updated>2024-05-05T09:59:32Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://localghost.dev/blog/new-keyboard-alert-mykeyclub-mkc75/&quot;&gt;https://localghost.dev/blog/new-keyboard-alert-mykeyclub-mkc75/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/replies/1714906247110</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/replies/1714906247110"/>
    <title>Reply to https://adactio.com/journal/21104</title>
    <published>2024-05-05T10:50:47Z</published>
    <updated>2024-05-05T10:50:47Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;&amp;lt;p&amp;gt;If you want to avoid using a nonce value, the other way to go about it is to hash the resource and put that in the CSP header. &amp;lt;a href&amp;#x3D;&amp;quot;https://qubyte.codes/blog/progressively-enhanced-caching-of-javascript-modules-without-bundling-using-import-maps&amp;quot;&amp;gt;I blogged about the interaction between CSP and import maps&amp;lt;/a&amp;gt; (which must be inline), and caching and it turns out to work really well.&amp;lt;/p&amp;gt;
&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1715358630528</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1715358630528"/>
    <title>Note 133</title>
    <published>2024-05-10T16:30:30Z</published>
    <updated>2024-05-10T16:30:30Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;One of the best things about living in Brighton (UK) is the density of pubs.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1715358532371.jpeg&quot; alt&#x3D;&quot;A mostly empty pint of beer on a table in the foreground. The photo is looking out of a pub window, at a pub across the street.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1715710148553</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1715710148553"/>
    <title>Like of https://shellsharks.com/notes/2024/05/14/one-of-us</title>
    <published>2024-05-14T18:09:08Z</published>
    <updated>2024-05-14T18:09:08Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://shellsharks.com/notes/2024/05/14/one-of-us&quot;&gt;https://shellsharks.com/notes/2024/05/14/one-of-us&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1715792289548</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1715792289548"/>
    <title>Like of https://wingpang.com/writing/2024-05-12-decapcms/</title>
    <published>2024-05-15T16:58:09Z</published>
    <updated>2024-05-15T16:58:09Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://wingpang.com/writing/2024-05-12-decapcms/&quot;&gt;https://wingpang.com/writing/2024-05-12-decapcms/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1715880682317</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1715880682317"/>
    <title>Link to https://paulrobertlloyd.com/2024/136/a1/indieweb_principles/</title>
    <published>2024-05-16T17:31:22Z</published>
    <updated>2024-05-16T17:31:22Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It&amp;#x27;s good to see the IndieWeb core principles being refined to the human (important) stuff. I think this makes sense as the movement matures and gains broader appeal than just people like me. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1715880682317&quot;&gt;IndieWeb principles - Paul Robert Lloyd&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1716653385661</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1716653385661"/>
    <title>Link to https://rachsmith.com/code-sketch-6/</title>
    <published>2024-05-25T16:09:45Z</published>
    <updated>2024-05-25T16:09:45Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;These are mesmerizing. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1716653385661&quot;&gt;Code sketch #6 - Rach Smith&amp;#x27;s Digital Garden&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1717952192754</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1717952192754"/>
    <title>Note 134</title>
    <published>2024-06-09T16:56:32Z</published>
    <updated>2024-06-09T16:56:32Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I got a Keychron K3 Pro through work and I absolutely adore it. It’s a joy to type with and sounds satisfying without being too noisy. It came with linear switches, which I don’t usually use.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1717951864758.jpeg&quot; alt&#x3D;&quot;A picture of an ANSI layout ten key-less wireless keyboard. The keys are grey and dark grey, except for the return, backspace, and escape keys which are grass green. The return key is labelled “ship it”, the backspace “simplify”, and the escape has the Shopify bag logo on it. &quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1718044834644</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1718044834644"/>
    <title>Note 135</title>
    <published>2024-06-10T18:40:34Z</published>
    <updated>2024-06-10T18:40:34Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Good grief. Apple’s big integration of generative machine learning models into the OS is impressive, but the energy overhead is going to be staggering. I wonder if it’ll all be powered by renewables… #wwdc&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1718567192815</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1718567192815"/>
    <title>Note 136</title>
    <published>2024-06-16T19:46:32Z</published>
    <updated>2024-06-16T19:46:32Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Over recent years there’s been a growing population of rooks to complement the jackdaws, magpies, and crows in my neighbourhood(and very occasionally a jay in the nearby cemetery). I think the rooks are my favourite though. They’re extra shaggy and have interesting calls.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1718566877023.jpeg&quot; alt&#x3D;&quot;A rook perched in a telegraph line, seen from below and to the side. Its distinctive featherless white face and shaggy plumage leave no doubt that it’s a rook! Behind it is a cloudless blue sky.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1719261550970</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1719261550970"/>
    <title>Note 137</title>
    <published>2024-06-24T20:39:10Z</published>
    <updated>2024-06-24T20:39:10Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m in Toronto for Shopify Summit. &lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1719261485737.jpeg&quot; alt&#x3D;&quot;A crowd of people at food trucks with the Toronto skyline in the background. &quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1719272974071</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1719272974071"/>
    <title>Note 138</title>
    <published>2024-06-24T23:49:34Z</published>
    <updated>2024-06-24T23:49:34Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m in a queue for swag in the summer evening sun at a company event and the person in front of me says something including “€500”. It took every ounce of strength not to shout “YOU WONT SEE PENNY ONE FROM ME YOU SLAG” in a cockney accent. Nobody would get the reference and I would definitely be fired.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1719776524266</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1719776524266"/>
    <title>Note 139</title>
    <published>2024-06-30T19:42:04Z</published>
    <updated>2024-06-30T19:42:04Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Voice interfaces are so bland. I want Hetfield doing the beasts-under-your-bed voice from Enter Sandman.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1721133909179</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1721133909179"/>
    <title>Note 140</title>
    <published>2024-07-16T12:45:09Z</published>
    <updated>2024-07-16T12:45:09Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A couple of weeks ago I took part in an unofficially record breaking (number of participants) hackathon, with around 6000 others as part of a work summit.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1721133597424.jpeg&quot; alt&#x3D;&quot;A large display hanging by from a conference hall ceiling. It wraps around on itself into a tube, so the image can horizontally scroll around on itself. The screen reads “We’ve (unofficially) broken the world record”.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1722690472447</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1722690472447"/>
    <title>Note 141</title>
    <published>2024-08-03T13:07:52Z</published>
    <updated>2024-08-03T13:07:52Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;My partner treated me to a couple of days stay in the Tokyo Station Hotel for my birthday. We’re in the south dome, and I can see the gates and folk come and go from up here. I don’t know how they did it, but it’s totally silent here. Absolutely no station noise at all!&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1722690173183.jpeg&quot; alt&#x3D;&quot;The view from a room in the south dome of the Tokyo Station Hotel. It’s a few stories above the concourse, where people can be seen entering the automated gates. Ticket machines can also be seen.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1723257417216</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1723257417216"/>
    <title>Note 142</title>
    <published>2024-08-10T02:36:57Z</published>
    <updated>2024-08-10T02:36:57Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m back at Glitch Coffee in 神保町. Delicious.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1723257215084.jpeg&quot; alt&#x3D;&quot;Black coffee served in a small black cup and a small fluted jug, both upon a black slab with a card with tasting notes on.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1723368452578</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1723368452578"/>
    <title>Note 143</title>
    <published>2024-08-11T09:27:32Z</published>
    <updated>2024-08-11T09:27:32Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Back in Hitachino Brewing Lab in the old Manseibashi Station building, just outside Akiba&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1723368292861.jpeg&quot; alt&#x3D;&quot;A half drained glass of session IPA, on a counter with a built-in strip lamp against a red brick wall. The counter is varnished wood.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1723458371793</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1723458371793"/>
    <title>Note 144</title>
    <published>2024-08-12T10:26:11Z</published>
    <updated>2024-08-12T10:26:11Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Yeah, fuck ‘im up Shinji. Now get back in the fucking robot.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1723458271328.jpeg&quot; alt&#x3D;&quot;A photo of a diorama of the Evangelion scene in the school yard where Shinji gets hit in the face, and then goaded into hitting back.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1723599281650</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1723599281650"/>
    <title>Note 145</title>
    <published>2024-08-14T01:34:41Z</published>
    <updated>2024-08-14T01:34:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Iced matcha and a fireworks themed Japanese confection at a tea house in Rikugien (garden).&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1723599086492.jpeg&quot; alt&#x3D;&quot;A large, bowl shaped cup of iced green matcha next to a confection, on a plastic (lacquer effect) tray. The confection is on a rectangle of paper, beside a simple wooden knife to eat it with. The tray is on a wooden bench.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1723599483863</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1723599483863"/>
    <title>Note 146</title>
    <published>2024-08-14T01:38:03Z</published>
    <updated>2024-08-14T01:38:03Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;My view from the Rikugien tea house.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1723599325433.jpeg&quot; alt&#x3D;&quot;In the foreground (close to me) is the trunk of a gnarled, ornamental pine tree. Below it is grass. Its branches and needles stretch out over a calm lake. The green opposing shore can be seen through the branches. There other side is grassy with woodland behind.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/favourite-music</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/favourite-music"/>
    <title>Favourite music</title>
    <published>2024-08-15T02:45:00Z</published>
    <updated>2024-12-28T14:25:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Here are a selection of albums which are all-time favourites of mine. They stand
up as a whole (not just as single tracks). I don’t claim to have good taste in
music, or that my taste is better than anyone else’s! With each album I’ve
written a little about times and places I associate with it. The albums are
presented in no particular order. I’ll garden this post over time and add new
material. I&amp;#39;ve linked to music services where I can find each album available,
but with minimal effort. There are other services, but they&amp;#39;re either behind
paywalls or incomprehensible to me.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;devin-townsend--terria&quot;&gt;Devin Townsend – Terria&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/terria.webp, /images/fair-use/terria-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/terria.avif, /images/fair-use/terria-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/terria.jpeg&quot; alt&#x3D;&quot;The album cover for Terria&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://devintownsendofficial.bandcamp.com/album/terria&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/6w2kw48F3xyHLgY4GDwFRV&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/terria/1045112739&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was the first music from Devin Townsend which I was exposed to. It was in
2002 I think, and I was listening more or less exclusively to bad metal at the
time. The weight of the sound of this album was what lured me in (particularly
Earth Day), but the progressive rock feeling to it led to a diversification of
my taste. This album evokes a feeling of vast landscapes and greenery. It, and
Ocean Machine remind me of my undergraduate years at Sussex University.&lt;/p&gt;
&lt;p&gt;To this day I’m a big fan of Townsend. No two offerings are the same, and some
of his stuff is really out there.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;strapping-young-lad--city&quot;&gt;Strapping Young Lad – City&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/city.webp, /images/fair-use/city-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/city.avif, /images/fair-use/city-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/city.jpeg&quot; alt&#x3D;&quot;The album cover for City&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;149&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://centurymedia.bandcamp.com/album/city-remastered-demo-versions&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/78Y2OaDAdvEqs3TRdCRdZc&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/city/1045623722&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I sneaked in another Devin Townsend album. Until about the mid 2000s Devin
Townsend also headed the metal band Strapping Young Lad (SYL). I’m less fond of
the output of SYL these days, but I still come back to City. It’s probably the
finest industrial metal album out there. This album also reminds me of my time
at Sussex University. That, and my questionable taste in clothing at the time
(very baggy, not a lot of colour, tassels).&lt;/p&gt;
&lt;h2 id&#x3D;&quot;portishead--dummy&quot;&gt;Portishead – Dummy&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/dummy.webp, /images/fair-use/dummy-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/dummy.avif, /images/fair-use/dummy-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/dummy.jpeg&quot; alt&#x3D;&quot;The album cover for Dummy&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/3539EbNgIdEDGBKkUf4wno&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/dummy/1440653096&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While at university I had a complicated relationship with a woman who introduced
me to Portishead. Things between us ended poorly, and this album reminds me of a
time when my head was in bad shape. I can listen to it now with more
objectivity, even if it makes me uncomfortable. The music should require no
introduction. The hauntingly beautiful vocals of Beth Gibbons and trip-hop
sound are timeless.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;isis-the-band--panopticon&quot;&gt;ISIS (the band) – Panopticon&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/panopticon.webp, /images/fair-use/panopticon-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/panopticon.avif, /images/fair-use/panopticon-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/panopticon.jpeg&quot; alt&#x3D;&quot;The album cover for Panopticon&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://isistheband.bandcamp.com/album/panopticon-remastered&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/4YVSY6TwnXWH7Jz4olWO1e&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/panopticon-remastered/1001664659&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While a postgrad at Leeds University my like of metal and progressive rock
mutated into a like of post-rock. ISIS (long before the terrorist group
sometimes affiliated with that initialism came to be) was a heavy post-rock
/ metal group which produced some extraordinarily heavy music. Panopticon is my
favourite of their albums, to the point that I named my first open source
offering after it. The stand-out track for me is In Fiction. It&amp;#39; takes an age
to build, but when it finally gets there it&amp;#39;s the deepest, heaviest, most
crushing sound. Amazing pay-off.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;opeth--blackwater-park&quot;&gt;Opeth – Blackwater Park&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/blackwater-park.webp, /images/fair-use/blackwater-park-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/blackwater-park.avif, /images/fair-use/blackwater-park-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/blackwater-park.jpeg&quot; alt&#x3D;&quot;The album cover for Blackwater Park&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://opeth.omerch.com/products/opeth-blackwater-park-cd&quot;&gt;Omerch (buy)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/3CCkWrqhWcKU7qXK3ooEEo&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/blackwater-park/363715800&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the first Opeth album I got into was Still Life (and Deliverance &amp;amp;
Damnation come close), my favourite remains Blackwater Park. Most of the music I
listen to avoids guitar solos and such. Opeth manage to do the reverse, and have
moments when every instrument seems to be executing a solo at the same time and
maintain coherence. I was originally hooked by The Drapery Falls, but the entire
album is exquisite. Give it a try, even if the thought of death-growl vocals is
off putting to you. It’s worth it.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;múm--finally-we-are-no-one&quot;&gt;Múm – Finally We Are No One&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/finally-we-are-no-one.webp, /images/fair-use/finally-we-are-no-one-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/finally-we-are-no-one.avif, /images/fair-use/finally-we-are-no-one-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/finally-we-are-no-one.jpeg&quot; alt&#x3D;&quot;The album cover for Finally We Are No One&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://fatcatrecords.bandcamp.com/album/finally-we-are-no-one&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/2XCcnYJJQXYoWm5oc20x9k&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/finally-we-are-no-one/285318218&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was introduced to this me by an Italian I met in Tokyo (she introduced me to a
lot of really great music, I should thank her again). This album should sound
light and airy, but comes across and close and comforting, like being indoors by
a fire during a coastal storm. While I first listened to it in Tokyo I don’t
associate it with there. Its aesthetic is so strong that it tries to invoke
memories of a particular balance that I’ve not actually experienced beyond
listening to this album.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;the-avalanches--since-i-left-you&quot;&gt;The Avalanches – Since I Left You&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/since-i-left-you.webp, /images/fair-use/since-i-left-you-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/since-i-left-you.avif, /images/fair-use/since-i-left-you-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/since-i-left-you.jpeg&quot; alt&#x3D;&quot;The album cover for Since I Left You&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;130&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/0CvU96jYCiNP4c9u8dWHoI&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/since-i-left-you/1450114829&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This album is famed for the extreme lengths the creators went to make it out of
samples. I was aware of it long before I listened to it (it was on in various
student pubs while I was doing my undergrad). I first really listened to it one
Summer I was living in Tokyo. It reminds me of the heat and humidity of the
summer there, but also the detachment I felt while I lived there. I moved to
Japan at relatively short notice after a difficult break up, and struggled to
find my footing for a long time after I arrived. This album reminds me of the
joy of summer and feeling at ease with myself and my freedom from my former life
and my culture. It probably marks the point at which I stopped identifying
myself as a nationality or a job, and started thinking of myself as a standard,
awkward mess of a person.&lt;/p&gt;
&lt;p&gt;This album reminds me that while we’re all a mishmash of different things, it’s
important to remember the difference between what we are, and what our
environments try to pigeonhole us as. Even more importantly, a mishmash can be a
beautiful and coherent whole.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;portico-quartet--memory-streams&quot;&gt;Portico Quartet – Memory Streams&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/memory-streams.webp, /images/fair-use/memory-streams-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/memory-streams.avif, /images/fair-use/memory-streams-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/memory-streams.jpeg&quot; alt&#x3D;&quot;The album cover for Memory Streams&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://porticoquartet.bandcamp.com/album/memory-streams&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/7rUuKsh6pJLcb44d1NBV81&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/memory-streams/1583551298&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a fairly recent edition, and I find it difficult to categorize. The band
makes instrumental music with heavy use of &lt;a href&#x3D;&quot;https://en.wikipedia.org/wiki/Hang_(instrument)&quot;&gt;the Hang&lt;/a&gt; (a form of steel drum),
saxophones, and synths. I guess it&amp;#39;s jazz, but I don&amp;#39;t know enough about jazz to
really say. I bought the record as an impulse purchase shortly before the
pandemic lockdowns started. My son was still a baby then, so I was looking for
something to relax to. Due to the timing, it became the sound of the early
pandemic for me. Back then, it felt like the world was just the three of us.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;boards-of-canada--tomorrows-harvest&quot;&gt;Boards of Canada – Tomorrow&amp;#39;s Harvest&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/tomorrows-harvest.webp, /images/fair-use/tomorrows-harvest-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/tomorrows-harvest.avif, /images/fair-use/tomorrows-harvest-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/tomorrows-harvest.jpeg&quot; alt&#x3D;&quot;The album cover for Tomorrow&amp;#x27;s Harvest&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://boardsofcanada.bandcamp.com/album/tomorrows-harvest&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/159ORixBSSemxiualv1Woj&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/tomorrows-harvest/641229267&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was an impulse buy, pretty much the day it came out in Japan. I used to
spend a lot of time trying to read the staff recommendations in the Tower
Records in Yodobashi Akihabara. Unfortunately that Tower Records has been gone
for a few years now. I had listened to Boards of Canada&amp;#39;s earlier work, but
Tomorrow&amp;#39;s Harvest blew my mind. The vintage sound and sparseness gives it a
brooding and foreboding feeling. This album is excellent to work to.&lt;/p&gt;
&lt;p&gt;I bought this album shortly before moving back to the UK from Japan, so it used
to remind me of that time. I&amp;#39;ve played it so often since that any nostalgia for
that time has long since gone, but I do still associate it with a change in
mindset that came with changing career from a physics academic to a software
engineer.&lt;/p&gt;
&lt;h2 id&#x3D;&quot;the-devin-townsend-project---addicted&quot;&gt;The Devin Townsend Project - Addicted&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/addicted.webp, /images/fair-use/addicted-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/addicted.avif, /images/fair-use/addicted-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/addicted.jpeg&quot; alt&#x3D;&quot;The album cover for Addicted&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://devintownsendofficial.bandcamp.com/album/addicted&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/4vaF198cLJTlSZ49v1N1mk&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/addicted/1045214857&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yes, another Devin Townsend album. This album was released just days before I
moved to Tokyo from the UK, and I listened to it a lot. I strongly associate it
with my first year in Japan. In particular with difficulty settling into the
time zone in the first few weeks, but also the fun little dance parties I had
with housemates and friends in the share house I was renting a room in. The
track &lt;em&gt;Bend it like Bender!&lt;/em&gt; was a favourite of one of my Italian friends. That
friendship ultimately led to me meeting my partner.&lt;/p&gt;
&lt;p&gt;Life is full of these incredibly unlikely little sequences of events with
important outcomes. What if that album came out a day later? I&amp;#39;d have been too
busy to buy it. What if I had chosen a different share house? What if the
aforementioned friend hadn&amp;#39;t started a business with a friend of my (now)
partner and set up a Roman Holiday themed club night beneath a Fiat showroom
which we were both &amp;quot;encouraged&amp;quot; to attend to recoup some losses? What if my
partner and I hadn&amp;#39;t been snacking on arancini next to each other and struck up
a conversation? What if...&lt;/p&gt;
&lt;h2 id&#x3D;&quot;marconi-union---signals&quot;&gt;Marconi Union - Signals&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;
    &lt;source type&#x3D;&quot;image/webp&quot; srcset&#x3D;&quot;/images/fair-use/signals.webp, /images/fair-use/signals-2x.webp 2x&quot;&gt;
    &lt;source type&#x3D;&quot;image/avif&quot; srcset&#x3D;&quot;/images/fair-use/signals.avif, /images/fair-use/signals-2x.avif 2x&quot;&gt;
    &lt;img class&#x3D;&quot;u-photo&quot; src&#x3D;&quot;/images/fair-use/signals.jpeg&quot; alt&#x3D;&quot;The album cover for Signals&quot; width&#x3D;&quot;150&quot; height&#x3D;&quot;150&quot; loading&#x3D;&quot;lazy&quot;&gt;
  &lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Another relatively recent addition. I&amp;#39;d listened to some of their music before
and found it good to work to, so when Signals was released I bought it straight
away. It&amp;#39;s an exceptional album. Tracks are repetitive, build slowly, and at
times feel meditative. There&amp;#39;s a thrumming, almost dangerous quality to it.
Stand out tracks for me are Strata, and A Citizen&amp;#39;s Dream. The latter I find
very affecting. It feels like longing, sadness, and hope, sometimes to the point
of tears when I&amp;#39;m actively listening to it. I have no idea why that is though!
Sometimes my reaction to music can&amp;#39;t be explained. It&amp;#39;s like it can be a key to
a random emotional door in my mind.&lt;/p&gt;
&lt;p&gt;I tend to react differently to others when I listen to music. I find quite
heavy, negative, loud music to be uplifting, and more pop-like music intended
to be uplifting will make me feel quite upset and angry.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://marconiunion.bandcamp.com/album/signals&quot;&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://open.spotify.com/album/1z6YgGKHAEJ9FIV93LP4SI?si&#x3D;8b1a-X2oRJKqsCAjFSfkfQ&quot;&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https://music.apple.com/gb/album/signals/1580742497&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1724158850059</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1724158850059"/>
    <title>Note 147</title>
    <published>2024-08-20T13:00:50Z</published>
    <updated>2024-08-20T13:00:50Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The super blue moon over Tokyo.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1724158746722.jpeg&quot; alt&#x3D;&quot;The moon over Tokyo at night, as seen from the Sky Tree. It’s shortly after moonrise, so it’s bright orange in colour.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1724284527697</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1724284527697"/>
    <title>Note 148</title>
    <published>2024-08-21T23:55:27Z</published>
    <updated>2024-08-21T23:55:27Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;HND ➡️ LHR&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1725177408421</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1725177408421"/>
    <title>Note 149</title>
    <published>2024-09-01T07:56:48Z</published>
    <updated>2024-09-01T07:56:48Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;This sparrowhawk caught a sparrow right in front of my eyes. I managed to take a picture of it through the back door before it vanished again.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1725177270982.jpeg&quot; alt&#x3D;&quot;A sparrowhawk standing on grass, with a sparrow under one foot.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1726398668175</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1726398668175"/>
    <title>Note 150</title>
    <published>2024-09-15T11:11:08Z</published>
    <updated>2024-09-15T11:11:08Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m challenging myself to walk 100k steps this week. I have about 15000 to do today, and I’m half way through that, so I’m taking a coffee break. Portland Coffee in Kemptown, Brighton is super chill. Look at that stained glass, too.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1726398343266.jpeg&quot; alt&#x3D;&quot;A photo taken inside the cafe, from the back, looking toward the front. It’s sunny outside and whitewashed walls can be seen opposite, giving it a Mediterranean feel. Above the door is a stained glass window depicting a seagull standing on pebbles, with the sea an a windmill behind. Paintings for sale are on the wall to the right. People sitting by the window are chatting.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/likes/1726860619695</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/likes/1726860619695"/>
    <title>Like of https://catgirlin.space/activity/111/</title>
    <published>2024-09-20T19:30:19Z</published>
    <updated>2024-09-20T19:30:19Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Like of &lt;a href&#x3D;&quot;https://catgirlin.space/activity/111/&quot;&gt;https://catgirlin.space/activity/111/&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1728596764525</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1728596764525"/>
    <title>Note 151</title>
    <published>2024-10-10T21:46:04Z</published>
    <updated>2024-10-10T21:46:04Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;It’s not much, but I’ve waited so long to see it. I caught a shooting star while waiting too.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1728596702533.jpeg&quot; alt&#x3D;&quot;A photograph of a weak aurora over houses. The pink can be clearly seen.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1728720646687</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1728720646687"/>
    <title>Link to https://htmlforpeople.com/</title>
    <published>2024-10-12T08:10:46Z</published>
    <updated>2024-10-12T08:10:46Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Wonderfully written and beautifully presented. This reminds me of my own first efforts on the web using a basic text editor. I&amp;#x27;m going to share this with anyone and everyone who wants to build their own first website. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1728720646687&quot;&gt;HTML for People&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1730457486729</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1730457486729"/>
    <title>Note 152</title>
    <published>2024-11-01T10:38:06Z</published>
    <updated>2024-11-01T10:38:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Spotted my first jay of the autumn! I think this is probably as close as I’ve ever been to one.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1730457375138.jpeg&quot; alt&#x3D;&quot;A jay standing, its left side facing the camera, on some grass. Lots of leaf litter too.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1731228825541</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1731228825541"/>
    <title>Link to https://blog.kizu.dev/my-mastodon-starter-pack/</title>
    <published>2024-11-10T08:53:45Z</published>
    <updated>2024-11-10T08:53:45Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A really great how-to article for getting started with Mastodon. &lt;a href&#x3D;&quot;https://qubyte.codes/links/1731228825541&quot;&gt;My Mastodon Starter Pack — Roma’s Unpolished Posts&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/how-i-syndicate-links-and-notes-to-bluesky-with-github-actions</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/how-i-syndicate-links-and-notes-to-bluesky-with-github-actions"/>
    <title>How I syndicate links and notes to Bluesky with GitHub Actions</title>
    <published>2024-11-25T02:15:00Z</published>
    <updated>2024-11-25T02:15:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Inspired by &lt;a href&#x3D;&quot;https://adactio.com/journal/21570&quot;&gt;Jeremy&amp;#39;s post on how he syndicates to Bluesky&lt;/a&gt;, I
thought I&amp;#39;d follow suit (many examples are useful when it comes to API
integration work). A disclaimer though... I&amp;#39;m dubious of the long term prospects
of Bluesky for reasons I won&amp;#39;t go into here. That being said, it&amp;#39;s currently
a vibrant place, and &lt;a href&#x3D;&quot;https://indieweb.org/POSSE&quot;&gt;syndicating from my site to other places&lt;/a&gt; keeps my
content&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-1&quot; href&#x3D;&quot;#footnote-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; in my hands.&lt;/p&gt;
&lt;p&gt;My setup is a bit of a Rube Goldberg machine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I publish notes (optionally with a photo) and bookmarks using &lt;a href&#x3D;&quot;https://www.w3.org/TR/micropub/&quot;&gt;Micropub&lt;/a&gt;
endpoints.&lt;/li&gt;
&lt;li&gt;The endpoints add the note or bookmark as a &lt;a href&#x3D;&quot;https://jf2.spec.indieweb.org&quot;&gt;JF2&lt;/a&gt; JSON file to the &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes&quot;&gt;git
repository of my personal site on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/syndicate-to-bluesky.yml&quot;&gt;GitHub Actions workflow&lt;/a&gt; is triggered by the addition of a note
or bookmark.&lt;/li&gt;
&lt;li&gt;The workflow calls a Node.js script. It calls the script using my Bluesky
handle, which is &lt;code&gt;qubyte.codes&lt;/code&gt; for me, and an &lt;a href&#x3D;&quot;https://bsky.app/settings/app-passwords&quot;&gt;app password&lt;/a&gt;. Do &lt;em&gt;not&lt;/em&gt; use
your actual password! Bluesky makes it pretty easy to create an
&lt;a href&#x3D;&quot;https://bsky.app/settings/app-passwords&quot;&gt;app password&lt;/a&gt;, and when you have one you can add it as a secret for Actions
to use at the &lt;code&gt;./settings/secrets/actions&lt;/code&gt; path of your repo site. I&amp;#39;ve called
my secret &lt;code&gt;BLUESKY_APP_PASSWORD&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rest of this post is about &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/main/scripts/syndicate-to-bluesky.js&quot;&gt;the script itself&lt;/a&gt;. As a rule of thumb,
I keep bits of shell glue in Actions workflows, and store the bulk of any logic
in a discrete script. That makes it easy to call the script by hand for testing.&lt;/p&gt;
&lt;p&gt;I publish notes (optionally with an image) and bookmarks using &lt;a href&#x3D;&quot;https://www.w3.org/TR/micropub/&quot;&gt;Micropub&lt;/a&gt;
endpoints. The note or bookmark is added as a &lt;a href&#x3D;&quot;https://jf2.spec.indieweb.org&quot;&gt;JF2&lt;/a&gt; JSON file to the &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes&quot;&gt;git
repository of my personal site on GitHub&lt;/a&gt;. When such a file is created
there, a &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/syndicate-to-bluesky.yml&quot;&gt;GitHub Actions workflow&lt;/a&gt; is triggered, and this workflow
in turn calls a Node.js script.&lt;/p&gt;
&lt;p&gt;The script is a vanilla Node.js script. While Bluesky does provide API client
libraries, I didn&amp;#39;t find it necessary to use one. I managed to write this
without any third party libraries. API work is all achieved with plain old
&lt;code&gt;fetch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are a couple of local libraries which I&amp;#39;ve extracted into their own
modules. The first is &lt;code&gt;blueskyAuth&lt;/code&gt;, which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class&#x3D;&quot;language-javascript&quot;&gt;&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; createSessionUrl &#x3D; &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;https://bsky.social/xrpc/com.atproto.server.createSession&amp;#x27;&lt;/span&gt;;

&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;default&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;blueskyAuth&lt;/span&gt;(&lt;span class&#x3D;&quot;hljs-params&quot;&gt;handle, appPassword&lt;/span&gt;) {
  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; res &#x3D; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;fetch&lt;/span&gt;(createSessionUrl, {
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;headers&lt;/span&gt;: { &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;content-type&amp;#x27;&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;application/json&amp;#x27;&lt;/span&gt; },
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;method&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&amp;#x27;POST&amp;#x27;&lt;/span&gt;,
    &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;body&lt;/span&gt;: &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;stringify&lt;/span&gt;({ &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;identifier&lt;/span&gt;: handle, &lt;span class&#x3D;&quot;hljs-attr&quot;&gt;password&lt;/span&gt;: appPassword })
  });

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!res.&lt;span class&#x3D;&quot;hljs-property&quot;&gt;ok&lt;/span&gt;) {
    &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(
      &lt;span class&#x3D;&quot;hljs-string&quot;&gt;&#x60;Bluesky responded with an unexpected status: &lt;span class&#x3D;&quot;hljs-subst&quot;&gt;${res.status}&lt;/span&gt; &lt;span class&#x3D;&quot;hljs-subst&quot;&gt;${&lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; res.text()}&lt;/span&gt;&#x60;&lt;/span&gt;
    );
  }

  &lt;span class&#x3D;&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; res.&lt;span class&#x3D;&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;(); &lt;span class&#x3D;&quot;hljs-comment&quot;&gt;// { accessJwt, refreshJwt, did }&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bluesky mostly speaks JSON, which is convenient when working in Node.js. To
create a session with Bluesky, you JSON encode an object containing your handle
and an app password. If successful, the response contains three things of
interest:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;accessJwt&lt;/code&gt;: A short-lived token used for API requests.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refreshJwt&lt;/code&gt;: A token which can be exchanged for a new &lt;code&gt;accessJwt&lt;/code&gt; and
&lt;code&gt;refreshJwt&lt;/code&gt; when the &lt;code&gt;accessJwt&lt;/code&gt; expires.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;did&lt;/code&gt;: An identifier. The initial &amp;quot;d&amp;quot;&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-2&quot; href&#x3D;&quot;#footnote-2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; stands for &amp;quot;distributed&amp;quot;, but I recently discovered that the DID &lt;code&gt;plc&lt;/code&gt;
method used by Bluesky is not actually distributed at all, thanks to Christine
Lemmer-Webber&amp;#39;s &lt;a href&#x3D;&quot;https://dustycloud.org/blog/how-decentralized-is-bluesky/&quot;&gt;excellent article on Bluesky and decentralization&lt;/a&gt;. I
&lt;em&gt;highly&lt;/em&gt; recommend reading it to dispel some myths around how decentralized
Bluesky is, by design.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My script uses the &lt;code&gt;accessJwt&lt;/code&gt; as a token and the &lt;code&gt;did&lt;/code&gt; as an identifier in
API requests. Since the session is only needed to post one document (and
possibly an image), there&amp;#39;s no need to think about refreshing the token, so I
don&amp;#39;t use the &lt;code&gt;refreshJwt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rest of the script is about one or two API requests. When an image is to be
uploaded, that happens first. There&amp;#39;s not much to say here beyond the size
restrictions being quite low, and being constrained to JPEGs and PNGs. I don&amp;#39;t
think they&amp;#39;re doing much processing of images, so I recommend removing metadata
from image uploads. The response of the image upload contains a &lt;code&gt;blob&lt;/code&gt; field,
which is used as a reference to the image in the second API request in the form
of an &lt;em&gt;&lt;a href&#x3D;&quot;https://docs.bsky.app/docs/advanced-guides/posts#images-embeds&quot;&gt;embedding&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The final request creates the &amp;quot;record&amp;quot; (the note or bookmark) on Bluesky.
There&amp;#39;s a lot to unpack here. The &lt;a href&#x3D;&quot;https://docs.bsky.app/docs/tutorials/creating-a-post&quot;&gt;&lt;em&gt;creating a post&lt;/em&gt;&lt;/a&gt; is full of
useful samples to help figure out the anatomy of a record creation body.&lt;/p&gt;
&lt;p&gt;One fascinating aspect of Bluesky is how text is composed. Rather than some sort
of markup language, it uses a more limited concept called &lt;em&gt;&lt;a href&#x3D;&quot;https://docs.bsky.app/docs/advanced-guides/post-richtext&quot;&gt;rich text facets&lt;/a&gt;&lt;/em&gt;.
Text is linear, and facets (for example, links) are attached to UTF-8 byte
ranges of the text. This is awkward for many languages! For example, JavaScript
uses UTF-16 to represent string internally (as was the trend in the mid-&amp;#39;90s),
so the range of characters you get with naïve string work in JS will give you
incorrect offsets. Thankfully Node.js has the venerable &lt;code&gt;Buffer&lt;/code&gt; class, which
can be used to represent strings as arrays of UTF-8 bytes. A &lt;code&gt;Buffer.byteLength&lt;/code&gt;
is all it takes to get the UTF-8 size of a string.&lt;/p&gt;
&lt;p&gt;Anyway, I&amp;#39;ve put lots of annotations in &lt;a href&#x3D;&quot;https://github.com/qubyte/qubyte-codes/blob/main/scripts/syndicate-to-bluesky.js&quot;&gt;the syndication script&lt;/a&gt;, so
hopefully it serves as a useful example!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1733403283960</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1733403283960"/>
    <title>Note 153</title>
    <published>2024-12-05T12:54:43Z</published>
    <updated>2024-12-05T12:54:43Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Back in Fahrenheit Coffee in Toronto.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1733403206602.jpeg&quot; alt&#x3D;&quot;Black coffee in a white cup and saucer on a wooden bar, facing a window onto the street.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1736889348150</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1736889348150"/>
    <title>Note 154</title>
    <published>2025-01-14T21:15:48Z</published>
    <updated>2025-01-14T21:15:48Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The ruins of Brighton West Pier near sunset.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1736889253403.jpeg&quot; alt&#x3D;&quot;A photo of the ruins of Brighton West Pier near sunset, taken from the shore in front of it. People are sat on the shingle in the foreground, looking away from the camera toward the pier. In the distance offshore windmills can just about be seen.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1741715923393</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1741715923393"/>
    <title>Note 155</title>
    <published>2025-03-11T17:58:43Z</published>
    <updated>2025-03-11T17:58:43Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Photos never do an incredible sunset justice.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1741715867744.jpeg&quot; alt&#x3D;&quot;Sunset over Brighton. The sun is sending amber god-rays upward through the clouds.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1742652846015</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1742652846015"/>
    <title>Note 156</title>
    <published>2025-03-22T14:14:06Z</published>
    <updated>2025-03-22T14:14:06Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Once again in The Botanist in Brighton. One of my favourite occasional haunts. I’m in a new pair of heeled shoes and my feet are killing me, so I guess it’s time to read a book.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1742652656559.jpeg&quot; alt&#x3D;&quot;A latte in a fancy black textured ceramic cup with no handle. My hand is touching the cup and blue varnish is on my long thumb nail. Behind my hand are row plants sharing the bar, and behind the bar is a window to the road.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1744240124954</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1744240124954"/>
    <title>Note 157</title>
    <published>2025-04-09T23:08:44Z</published>
    <updated>2025-04-09T23:08:44Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Bright night! This is my shadow, as cast by the moon.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1744240037778.jpeg&quot; alt&#x3D;&quot;A shadow of myself, cast by the moon onto a path made of concrete slabs.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1745088319209</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1745088319209"/>
    <title>Note 158</title>
    <published>2025-04-19T18:45:19Z</published>
    <updated>2025-04-19T18:45:19Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’ve spoken with one or two people I know who are mostly supportive of trans rights, but expressed one of the usual tropes as a point of concern. Such moments elicit immediate anger from me, and I’m not sorry. I’m just so tired of supposed allies falling for that shit. I’m not here to talk you through it.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1745234339371</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1745234339371"/>
    <title>Note 159</title>
    <published>2025-04-21T11:18:59Z</published>
    <updated>2025-04-21T11:18:59Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’ve done it folks. I’ve achieved the final, most important step of coming out: ✨updating my LinkedIn profile ✨&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1746206997895</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1746206997895"/>
    <title>Note 160</title>
    <published>2025-05-02T17:29:57Z</published>
    <updated>2025-05-02T17:29:57Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I got my ears pierced. Just the lobes (my first piercings, very vanilla). The technician described the pain as “spicy”. Then she described the other ear as “spicier”. Apparently it’s an adrenaline thing. Anyway, a minor victory in an otherwise fucking appalling week but I’m still chalking it up.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1748037475101</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1748037475101"/>
    <title>Note 161</title>
    <published>2025-05-23T21:57:55Z</published>
    <updated>2025-05-23T21:57:55Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Today I celebrate four months on HRT.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1748037285222.jpeg&quot; alt&#x3D;&quot;A selfie of me at the pub. I’m drinking a pint of beer. The nails on my hand holding the beer are painted deep blue. I’m wearing glasses, behind which I’m wearing subtle black eyeliner with a small flick. My hair is pulled back in a black hairband, and I’m wearing a denim jacket. The photo is close enough that part of my face is out of shot. Behind be is a blackboard with various events advertised.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1748945430125</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1748945430125"/>
    <title>Note 162</title>
    <published>2025-06-03T10:10:30Z</published>
    <updated>2025-06-03T10:10:30Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;A scarlet tiger moth.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1748945331038.jpeg&quot; alt&#x3D;&quot;A close up photograph of a scarlet tiger moth on an uneven grey path. Its wings are closed. Its wings are iridescent black with yellow spots toward the front and white spots toward the back. A red spot can be seen between the wings where they meet at the back.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1753515293810</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1753515293810"/>
    <title>Note 163</title>
    <published>2025-07-26T07:34:53Z</published>
    <updated>2025-07-26T07:34:53Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Trans Pride Brighton was beautiful.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1753515111203.jpeg&quot; alt&#x3D;&quot;A person just ahead of me in the Trans Pride March waving the flag (blue pink white pink blue). Brighton Pavilion is below and behind it.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1754134342413</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1754134342413"/>
    <title>Note 164</title>
    <published>2025-08-02T11:32:22Z</published>
    <updated>2025-08-02T11:32:22Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Nipped into Presuming Ed’s for a coffee while I wait for the Pride parade.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1754134192587.jpeg&quot; alt&#x3D;&quot;The dimly lit interior of Presuming Ed’s cafe on London Road, Brighton. A pair of mannequin legs props up a table. Another mannequin is adorned with stickers and a trans pride 2024 t-shirt.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1756413500016</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1756413500016"/>
    <title>Note 165</title>
    <published>2025-08-28T20:38:20Z</published>
    <updated>2025-08-28T20:38:20Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Post-migraine Aura&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1756413407579.jpeg&quot; alt&#x3D;&quot;A picture of me sitting in an office chair next to a window. My eyes are wide but I’m otherwise expressionless. I’m wearing glasses, a black baseball cap, and a grey tank top.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/links/1756418141954</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/links/1756418141954"/>
    <title>Link to https://www.selfawaresoup.com/notes/2025/08/28/a-year-of-linux-on-the-desktop/</title>
    <published>2025-08-28T21:55:41Z</published>
    <updated>2025-08-28T21:55:41Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I love this. I&amp;#x27;ve not had a dedicated Linux rig for a while (not  counting a NAS and a couple of appliances I&amp;#x27;ve built using Raspberry Pis). With the decline in quality of MacOS, and the tentacles of LLMs getting everywhere in commercial OS&amp;#x27;s, Linux is increasingly attractive as a permanent home again... &lt;a href&#x3D;&quot;https://qubyte.codes/links/1756418141954&quot;&gt;A Year of Linux on the Desktop - selfaware soup&lt;/a&gt;&lt;/p&gt;
    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1758976642404</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1758976642404"/>
    <title>Note 166</title>
    <published>2025-09-27T12:37:22Z</published>
    <updated>2025-09-27T12:37:22Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’m at Taith Coffee in Lewes. Incredible vibes here.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1758976498048.jpeg&quot; alt&#x3D;&quot;A photo of the back room at Taith Coffee. Lots of brown tones. Wooden tables and wicker chairs. The room is dominated by the bar at the back with huge bifold windows that are open to an incredible view of the South Downs.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1760438545996</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1760438545996"/>
    <title>Note 167</title>
    <published>2025-10-14T10:42:25Z</published>
    <updated>2025-10-14T10:42:25Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I had a 2010 MacBook Air in a cupboard in my office. I was still using it up until a couple of years ago, and the hardware is still good. My kid needs a machine for homework, so I put Linux on it and it actually feels fast again. He loves it!&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1763370331918</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1763370331918"/>
    <title>Note 168</title>
    <published>2025-11-17T09:05:31Z</published>
    <updated>2025-11-17T09:05:31Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;Cold day! Fortunately, a short walk from dropping the kid off at school is Casco, one of my favourite cafes. It’s warm in here.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1763370121159.jpeg&quot; alt&#x3D;&quot;A photograph of a flat white in a matte black cup, on a black saucer, on a black wooden table. My gloves are on the table to the left of the coffee. My right hand, with long plum colour nails is resting on the rim of the saucer to the right.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1767569211134</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1767569211134"/>
    <title>Note 169</title>
    <published>2026-01-04T23:26:51Z</published>
    <updated>2026-01-04T23:26:51Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I’d do a year in review but it&amp;#39;d be mostly about trans stuff and all the people (the supreme court, the government, rich authors, etc.) who can fuck off.&lt;/p&gt;
&lt;p&gt;Or maybe it’d be about the surprising joy of being fully myself. Because it’s not like the aforementioned cowards and the genital obsessed made the slightest dent in my progress.&lt;/p&gt;
&lt;p&gt;Plus I didn&amp;#39;t leave the country this year because I was getting my passport updated, and also I was too preoccupied with side projects. I did learn Java though (after trying not to for years).&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1770336707659</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1770336707659"/>
    <title>Note 170</title>
    <published>2026-02-06T00:11:47Z</published>
    <updated>2026-02-06T00:11:47Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I forgot to post for the 1 year anniversary of being on HRT (end of January)! Have a photo of me looking a bit tired in a pub last Thursday.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1770336472409.jpeg&quot; alt&#x3D;&quot;A photograph of me sitting in The Basketmakers Arms in Brighton. I’m wearing a subdued floarl corduroy shirt, a fused glass amulet, and a black button up dress over the top. I’m also wearing cat-eye shaped glasses and a black yoga band.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/blog/what-happened-to-2025</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/blog/what-happened-to-2025"/>
    <title>What happened to 2025?</title>
    <published>2026-02-06T11:45:00Z</published>
    <updated>2026-02-06T11:45:00Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;I&amp;#39;ve not posted in over a year (the whole of 2025). I don&amp;#39;t have a readership
(there are good odds that nobody besides me will read this), so nobody has
missed my writing and I&amp;#39;ve felt no pressure to force a post.&lt;/p&gt;
&lt;p&gt;It &lt;em&gt;is&lt;/em&gt; unusual though. A handful of times in a typical year something will grab
my attention to the point that I take the time to write about it. So what was
different about 2025? And why write about it now?&lt;/p&gt;
&lt;p&gt;In short, I was very busy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I started coming out as trans in late November 2024. Three people close to me
by the end of the year.&lt;/li&gt;
&lt;li&gt;Over 2025 I came out to everyone else, and by the middle of April I was Aura
(hello!) full time in all contexts.&lt;/li&gt;
&lt;li&gt;In late January 2025 I started on hormone replacement therapy (HRT) which
means...&lt;/li&gt;
&lt;li&gt;I just passed the first full year on HRT (the anniversary which led me to
write this post)!&lt;/li&gt;
&lt;li&gt;Lots, lots more related stuff (laser hair removal, electrolysis, clothes (I
haven&amp;#39;t boymoded&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-1&quot; href&#x3D;&quot;#footnote-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; since April) legal stuff and ID, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The year was &lt;em&gt;a lot&lt;/em&gt;&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-2&quot; href&#x3D;&quot;#footnote-2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;. Each
trans person has their own path, and not all have access to or want HRT. For me
though, it was the most important step so far, to the point that I&amp;#39;m viewing it
like a birthday. In the years between realizing I&amp;#39;m trans and making the
decision to come out, I&amp;#39;d gone from the persistent feeling of wrongness and
disconnection I&amp;#39;d always had, to being in a very bad place. The decision to
transition was a necessary one, but the rewards astonishing. I&amp;#39;m healthier and
happier than I&amp;#39;ve ever been before. I&amp;#39;m still early in my journey, but I&amp;#39;ve come
&lt;em&gt;so far&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So that&amp;#39;s what happened in 2025, and why there&amp;#39;s a gap in the blog posts. I
might expand on individual parts of my experience in future posts. I&amp;#39;m not
certain yet. While it&amp;#39;s an extremely personal subject, every trans person&amp;#39;s
path is different, and perhaps someone reading this will see something of
themselves which helps them. I wouldn&amp;#39;t change my life so far (except skipping
the years between figuring it out and coming out), but I often wonder how things
might have been if I&amp;#39;d have read something like this and connected the dots
earlier.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re reading this, and your gender is on your mind,
&lt;a href&#x3D;&quot;https://genderdysphoria.fyi/en/am-i-trans&quot;&gt;read this&lt;/a&gt;&lt;sup class&#x3D;&quot;footnote-ref&quot;&gt;&lt;a id&#x3D;&quot;footnote-ref-3&quot; href&#x3D;&quot;#footnote-3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;. If
we&amp;#39;re acquainted, then you can talk to me, in confidence.&lt;/p&gt;

    
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1772275853624</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1772275853624"/>
    <title>Note 171</title>
    <published>2026-02-28T10:50:53Z</published>
    <updated>2026-02-28T10:50:53Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;The Conservatory at The Barbican. I’m here for Sate of the Browser.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1772275741951.jpeg&quot; alt&#x3D;&quot;A view of the tropical foliage in the Barbican Conservatory. It’s a delightful juxtaposition of green and brutalist concrete and glass.&quot;&gt;
    </content>
  </entry>
  <entry>
    <id>https://qubyte.codes/notes/1774434561384</id>
    <link type="text/html" rel="alternate" href="https://qubyte.codes/notes/1774434561384"/>
    <title>Note 172</title>
    <published>2026-03-25T10:29:21Z</published>
    <updated>2026-03-25T10:29:21Z</updated>
    <author>
      <name>qubyte</name>
      <uri>https://qubyte.codes</uri>
    </author>
    <content type="html">
    &lt;p&gt;One day I’ll take a selfie in which I smile without looking unhinged.&lt;/p&gt;

    &lt;img src&#x3D;&quot;https://qubyte.codes/images/1774434367608.jpeg&quot; alt&#x3D;&quot;A selfie of me in a cafe in Brighton. I’m sitting with a panelled wooden wall and a window behind me. I’m wearing my usual cat-eye shaped glasses and wide black headband.  I’m wearing a brown check dress with a plunging neckline and a black fabric corset / harness thing. I’m attempting to smile and looking pretty wild-eyed.&quot;&gt;
    </content>
  </entry>

</feed>