Qubyte Codes2026-02-28T10:50:53Zhttps://qubyte.codes/qubytehttps://qubyte.codes/blog/private-data-for-js-classes-with-weakmapPrivate data for JS classes with WeakMap2015-12-30T14:35:00Z2015-12-30T14:35:00Zqubytehttps://qubyte.codes
<p>Private data has always been awkward in JavaScript. It's particularly difficult when it comes to
constructors, and with ES2015 recently published, classes too. Let's say we have an example class,
exported by an ES2015 module:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Example</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">_privateDatum</span> = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>();
}
<span class="hljs-title function_">log</span>(<span class="hljs-params"></span>) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">_privateDatum</span>);
}
}
</code></pre><p>In the constructor, a field with some private data, <code>_privateDatum</code> 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'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's the problem?</p>
<p><em>You can't trust your users!</em></p>
<p>This isn'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're having, and you'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.</p>
<p>The solution is to hide the private data, removing the temptation. You can do this using a
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures"><em>closure</em></a>. Consider:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Example</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> privateDatum = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>();
<span class="hljs-variable language_">this</span>.<span class="hljs-property">log</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(privateDatum);
}
}
}
</code></pre><p>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 <code>log</code> method has to be attached to the instance inside the constructor, so that it can have
access to the variable. It's a real shame to lose the nice method syntax. It also make an individual
<code>log</code> method for each instance, which means objects will each use more memory.</p>
<p>This is where
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"><code>WeakMap</code></a>
comes in. An instance of <code>WeakMap</code> has keys which are objects of some kind, and values which can be
whatever you like. <code>WeakMap</code> 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 <em>could</em> simulate most aspects of <code>WeakMap</code> 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:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> privateDatum = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WeakMap</span>();
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Example</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
privateDatum.<span class="hljs-title function_">set</span>(<span class="hljs-variable language_">this</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>());
}
<span class="hljs-title function_">log</span>(<span class="hljs-params"></span>) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(privateDatum.<span class="hljs-title function_">get</span>(<span class="hljs-variable language_">this</span>));
}
}
</code></pre><p>The keys of <code>privateDatum</code> are the instances of the example class. If nothing else holds a reference
to an instance of the example class, the garbage collector doesn't count the reference in
<code>privateDatum</code> and can clear it up! Since the instances are keys, <code>this</code> can be used in any method
to access the private data. The <code>privateDatum</code> variable hidden by the module, so the user will have
no access to it!</p>
<p>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:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> privateDatum = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WeakMap</span>();
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Example</span>(<span class="hljs-params"></span>) {
privateDatum.<span class="hljs-title function_">set</span>(<span class="hljs-variable language_">this</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>());
}
<span class="hljs-title class_">Example</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">log</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(privateDatum.<span class="hljs-title function_">get</span>(<span class="hljs-variable language_">this</span>));
};
</code></pre><p>The good news is that <code>WeakMap</code> 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
<a href="http://kangax.github.io/compat-table/es6/#test-WeakMap">compatibility table</a>. If you're using a
maintained version of Node, you're good to go!</p>
<h3 id="addendum">Addendum</h3>
<p>It's still possible to gain access to private data stored in a WeakMap by patching
<code>WeakMap.prototype.set</code> or <code>WeakMap.prototype.get</code>. This should <em>absolutely never be done!</em> 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 <code>Object.freeze</code>
both <code>WeakMap</code> and <code>WeakMap.prototype</code> before any other code runs.</p>
https://qubyte.codes/blog/about-this-blogAbout this blog2016-01-03T12:22:00Z2016-01-03T12:22:00Zqubytehttps://qubyte.codes
<p>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
<a href="https://codeberg.org/qubyte/toisu-monorepo">wrote a server framework</a>. I took some holiday over the Christmas
period, so I decided to throw everything away and make something minimal.</p>
<p>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's about a hundred lines of code, mainly stitching together other
small modules. I used Node to make the generator, with the following modules:</p>
<table>
<thead>
<tr>
<th>module</th>
<th>explanation</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://www.npmjs.com/package/front-matter"><code>front-matter</code></a></td>
<td>For keeping YAML metadata at the top of post files.</td>
</tr>
<tr>
<td><a href="https://www.npmjs.com/package/marked"><code>marked</code></a></td>
<td>To compile post markdown to HTML.</td>
</tr>
<tr>
<td><a href="https://www.npmjs.com/package/highlight.js"><code>highlight.js</code></a></td>
<td>To highlight code listings.</td>
</tr>
<tr>
<td><a href="https://www.npmjs.com/package/handlebars"><code>handlebars</code></a></td>
<td>To render posts into templates and an index page.</td>
</tr>
<tr>
<td><a href="https://www.npmjs.com/package/remark"><code>remark</code></a></td>
<td>To pluck the first paragraph from each post to render into the index page.</td>
</tr>
<tr>
<td><a href="https://www.npmjs.com/package/slug"><code>slug</code></a></td>
<td>To make post URLs readable.</td>
</tr>
<tr>
<td><a href="https://www.npmjs.com/packages/clean-css"><code>clean-css</code></a></td>
<td>To compile CSS sources together.</td>
</tr>
</tbody></table>
<p>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'll have that working on
a GitHub hook soon.</p>
<p>I've no intention of posting the generator, since that would mean supporting it. It's strictly for
my use. I've linked the modules above since they've been very useful to me. If you're thinking about
setting up a blog and you're a programmer, I recommend this approach!</p>
https://qubyte.codes/blog/about-this-blog-2About this blog 22016-01-11T20:15:00Z2016-01-11T20:15:00Zqubytehttps://qubyte.codes
<p>I touched briefly on the technology used in this blog in
<a href="/blog/about-this-blog.html">a previous post</a>, but I didn't explain the motivation behind a lot of
the choices I made when building it. I'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.</p>
<h3 id="https">HTTPS</h3>
<p>SSL certificates are now free thanks to <a href="https://letsencrypt.org">Let's Encrypt</a>, so there is
absolutely no excuse to host blog content over plain HTTP. By doing this you honour the privacy of
your readers. It's a little clunky if you're using NGINX (as this blog is), but still far easier
than buying a domain name and hosting, sorting DNS etc.</p>
<h3 id="no-tracking">No tracking</h3>
<p>I don't currently use tracking or cookies, mainly because I don'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'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't need that now though.</p>
<h3 id="render-once">Render once</h3>
<p>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'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's kind to the server.</p>
<h3 id="responsiveness">Responsiveness</h3>
<p>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.</p>
<h3 id="dated-posts">Dated posts</h3>
<p>It's rare, but I do come across how-to style blog posts which lack dates. This is frustrating, since
it's difficult to know if the information in the post is stale.</p>
<h3 id="getting-to-the-point">Getting to the point</h3>
<p>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're also making the browser download that image, which probably takes more data than the
rest of the page combined. This blog doesn'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.</p>
<h3 id="mobile-friendly">Mobile friendly</h3>
<p>I'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.</p>
<h3 id="no-comment">No comment</h3>
<p>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'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.</p>
https://qubyte.codes/blog/how-i-schedule-posts-using-atdHow I schedule posts using atd2016-01-14T19:30:00Z2016-01-14T19:30:00Zqubytehttps://qubyte.codes
<p>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.</p>
<p>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 <em>schedule</em> the publication.
Many know about <code>cron</code> for scheduling repeated tasks, but fewer are aware of <code>atd</code>, which is for one
time scheduled tasks. The one liner I use is:</p>
<pre><code class="language-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"git -C /absolute/path/to/repo pull origin master"</span> | at 07:00 tomorrow
</code></pre><p><code>at</code> accepts input through stdin, which is why I'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.</p>
<p>There are companion utilities to manage scheduled jobs. The <code>man</code> page for <code>at</code> is extremely good,
so I won't try to better it. The purpose of this post is to show you that it exists and how simple
(especially when compared with <code>cron</code>) it is to use. You can use it to schedule pretty much
anything!</p>
https://qubyte.codes/blog/private-methods-for-js-classesPrivate methods for JS classes2016-01-31T02:00:00Z2016-01-31T02:00:00Zqubytehttps://qubyte.codes
<p>This is a short companion to an
<a href="/blog/private-data-for-js-classes-with-weakmap">earlier article I wrote</a> on using <code>WeakMap</code> 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:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">setContent</span>(<span class="hljs-params">content</span>) {
<span class="hljs-keyword">const</span> text = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createTextNode</span>(content);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">el</span>.<span class="hljs-title function_">appendChild</span>(text);
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Paragraph</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params">content</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">el</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'p'</span>);
setContent.<span class="hljs-title function_">call</span>(<span class="hljs-variable language_">this</span>, content);
}
<span class="hljs-title function_">replace</span>(<span class="hljs-params">content</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">el</span>.<span class="hljs-property">innerHTML</span> = <span class="hljs-string">''</span>;
setContent.<span class="hljs-title function_">call</span>(<span class="hljs-variable language_">this</span>, content);
}
}
</code></pre><p>This module exports a class which wraps a paragraph element (which is not a particularly useful
thing to do, but hopefully illustrates what I'm about to say).</p>
<p>Each instance of <code>Paragraph</code> is constructed with some content. The one public method it has is
<code>replace</code>, which allows the content to be replaced. Both the <code>constructor</code> and the <code>replace</code> methods
set content, so rather than repeat the code that does that, the code is placed in the private method
<code>setContent</code>. <code>setContent</code> operates on a <code>Paragraph</code> instance passed in as the context by using
<code>call</code>. You could just as easily pass the instance in by using a parameter. I've chosen the former
approach here to keep the difference between public and private methods to the minimum.</p>
<p>By virtue of the <code>setContent</code> 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't
make <code>replace</code> available, and the module doesn't export it, then nothing outside the module has
access. Thus <code>setContent</code> is a private method.</p>
<p>For clarity, <code>replace</code> being public means that I can do this:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">var</span> paragraph = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Paragraph</span>(<span class="hljs-string">'hello'</span>);
<span class="hljs-comment">// replace is pubic, so we can use it.</span>
paragraph.<span class="hljs-title function_">replace</span>(<span class="hljs-string">'goodbye'</span>);
</code></pre><p>And <code>setContent</code> being private means that I cannot do this:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">var</span> paragraph = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Paragraph</span>(<span class="hljs-string">'hello'</span>);
<span class="hljs-comment">// setContent is not a part of the Paragraph</span>
<span class="hljs-comment">// definition. We cannot do this.</span>
paragraph.<span class="hljs-title function_">setContent</span>(<span class="hljs-string">'goodbye'</span>);
</code></pre><p>So there you have it. There's not a lot to the pattern really. Earlier module types and patterns
allow the same thing to be done. I recommend reading the <a href="https://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript">section on module patterns</a>
in Addy Osmani's excellent book <em>Learning JavaScript Design Patterns</em> if you want to know more (and
particularly the sub section on the <em>module pattern</em>).</p>
https://qubyte.codes/blog/interfaces-for-javascriptInterfaces for JavaScript2016-03-13T10:45:00Z2016-03-13T10:45:00Zqubytehttps://qubyte.codes
<p>I use <code>instanceof</code> a lot in JavaScript. It's very handy when writing unit tests. It's easier to do
an <code>instanceof</code> check than it is to exhaustively probe an object.</p>
<p>Unfortunately <code>instanceof</code> 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.</p>
<p>This is why I'm excited about ES2015s <code>Symbol.hasInstance</code>. It allows you to tune the behaviour of
<code>instanceof</code> for a class. Here's a minimal example of an <em>interface class</em>:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">PositiveInteger</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'PositiveInteger is an interface class.'</span>);
}
<span class="hljs-keyword">static</span> [<span class="hljs-title class_">Symbol</span>.<span class="hljs-property">hasInstance</span>](value) {
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> value !== <span class="hljs-string">'number'</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-keyword">if</span> (value < <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-keyword">return</span> value % <span class="hljs-number">1</span> === <span class="hljs-number">0</span>;
}
}
assert.<span class="hljs-title function_">ok</span>(<span class="hljs-number">10</span> <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">PositiveInteger</span>); <span class="hljs-comment">// does not throw</span>
assert.<span class="hljs-title function_">ok</span>(-<span class="hljs-number">10</span> <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">PositiveInteger</span>); <span class="hljs-comment">// throws</span>
assert.<span class="hljs-title function_">ok</span>(<span class="hljs-string">'hi'</span> <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">PositiveInteger</span>); <span class="hljs-comment">// throws</span>
<span class="hljs-keyword">var</span> positiveInt = <span class="hljs-keyword">new</span> <span class="hljs-title class_">PositiveInteger</span>(); <span class="hljs-comment">// throws</span>
</code></pre><p>The class above exists <em>only</em> to provide this <code>instanceof</code> 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:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">View</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'View is an interface class.'</span>);
}
<span class="hljs-keyword">static</span> [<span class="hljs-title class_">Symbol</span>.<span class="hljs-property">hasInstance</span>](value) {
<span class="hljs-keyword">if</span> (!value) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> value.<span class="hljs-property">render</span> !== <span class="hljs-string">'function'</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> value.<span class="hljs-property">remove</span> !== <span class="hljs-string">'function'</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-keyword">return</span> value.<span class="hljs-property">element</span> <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">HTMLElement</span>;
}
}
</code></pre><p>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't have to be in the same prototype chain. In other words, it gives you a way to use <code>instanceof</code>
without invoking inheritance.</p>
<p>Sadly <code>hasInstance</code> will be one of the last ES2015 features to make it into browsers, so we'll have
to wait a while before we can use it. See
<a href="http://kangax.github.io/compat-table/es6/#test-well-known_symbols_Symbol.hasInstance">the compatibility table</a>.</p>
https://qubyte.codes/blog/a-presentation-on-async-await-and-toisuA presentation on async-await and Toisu!2016-04-06T22:00:00Z2016-04-06T22:00:00Zqubytehttps://qubyte.codes
<p>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'll introduce Toisu!
in a blog post soon, but until then here's the presentation:</p>
<div class="embed-container"><iframe src="https://www.youtube-nocookie.com/embed/XPMBAhiV5Wo?rel=0" frameborder="0" allowfullscreen=""></iframe></div>
https://qubyte.codes/blog/adding-missing-features-to-setAdding missing features to Set2016-05-22T16:30:00Z2016-05-22T16:30:00Zqubytehttps://qubyte.codes
<p>ES2015 bought a <code>Set</code> constructor to JavaScript. It'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 <code>indexOf</code> checking.</p>
<p>For example, abusing an array to act like a set is very common:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">var</span> myCollection = [];
<span class="hljs-comment">// Check if element is in the collection.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">has</span>(<span class="hljs-params">collection, element</span>) {
<span class="hljs-keyword">return</span> collection.<span class="hljs-title function_">indexOf</span>(element) !== -<span class="hljs-number">1</span>;
}
<span class="hljs-comment">// Add an element to the collection.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">add</span>(<span class="hljs-params">collection, element</span>) {
<span class="hljs-keyword">if</span> (!<span class="hljs-title function_">has</span>(collection, element)) {
collection.<span class="hljs-title function_">push</span>(element);
}
}
<span class="hljs-title function_">add</span>(myCollection, <span class="hljs-number">123</span>);
<span class="hljs-title function_">has</span>(myCollection, <span class="hljs-number">123</span>); <span class="hljs-comment">// returns true</span>
<span class="hljs-title function_">has</span>(myCollection, <span class="hljs-number">456</span>); <span class="hljs-comment">// returns false</span>
</code></pre><p>Using a set is more straight forward:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">var</span> myCollection = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>();
myCollection.<span class="hljs-title function_">add</span>(<span class="hljs-number">123</span>); <span class="hljs-comment">// add an element</span>
myCollection.<span class="hljs-title function_">has</span>(<span class="hljs-number">123</span>); <span class="hljs-comment">// returns true</span>
myCollection.<span class="hljs-title function_">has</span>(<span class="hljs-number">456</span>); <span class="hljs-comment">// returns false</span>
</code></pre><p>That's all grand, but a colleague of mine, a programmer dipping a toe into
modern JS, noted that <code>Set</code> lacks some functions that you might expect it to
have out of the box, such as <em>union</em>, <em>intersection</em> and (relative)
<em>complement</em>.</p>
<p>I'd implement these as static methods of <code>Set</code>, and shim them in. Hopefully
these methods will be added to the standard in the future. Until then, here are
some possible implementations.</p>
<h2 id="union">Union</h2>
<p>The union of two sets is defined as the set of elements found in either or both
sets.</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Set</span>.<span class="hljs-property">union</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">a, b</span>) {
<span class="hljs-keyword">const</span> unionSet = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> element <span class="hljs-keyword">of</span> a) {
unionSet.<span class="hljs-title function_">add</span>(element);
}
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> element <span class="hljs-keyword">of</span> b) {
unionSet.<span class="hljs-title function_">add</span>(element)
}
<span class="hljs-keyword">return</span> union;
};
</code></pre><p>This function iterates over both sets, adding their elements to a new set, which
is then returned.</p>
<p>As a one liner:</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Set</span>.<span class="hljs-property">union</span> = <span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>([...a, ...b]);
</code></pre><p>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.</p>
<h2 id="intersection">Intersection</h2>
<p>The intersection of two sets is defined as the set of elements they have in
common.</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Set</span>.<span class="hljs-property">intersection</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">a, b</span>) {
<span class="hljs-keyword">const</span> [small, big] = a.<span class="hljs-property">size</span> < b.<span class="hljs-property">size</span> ? [a, b] : [b, a];
<span class="hljs-keyword">const</span> intersectionSet = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> element <span class="hljs-keyword">of</span> small) {
<span class="hljs-keyword">if</span> (big.<span class="hljs-title function_">has</span>(element)) {
intersectionSet.<span class="hljs-title function_">add</span>(element);
}
}
<span class="hljs-keyword">return</span> intersectionSet;
};
</code></pre><p>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 <code>small</code> and the larger set to <code>big</code>.
The <code>intersectionSet</code> is then constructed, and <code>small</code> looped over to fill it
with elements which are also in the <code>big</code>.</p>
<p>If you want a one liner for this (and don't care about the size optimisation):</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Set</span>.<span class="hljs-property">intersection</span> = <span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>([...a].<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">element</span> =></span> b.<span class="hljs-title function_">has</span>(element)));
</code></pre><p>This uses similar tricks to <code>Set.union</code>. 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 <code>Set</code> to get the intersection set.</p>
<h2 id="relative-complement">Relative complement</h2>
<p>The relative complement of A in B is the set of those elements in B which are
not in A.</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Set</span>.<span class="hljs-property">complement</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">a, b</span>) {
<span class="hljs-keyword">const</span> complementSet = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> element <span class="hljs-keyword">of</span> b) {
<span class="hljs-keyword">if</span> (!a.<span class="hljs-title function_">has</span>(element)) {
complementSet.<span class="hljs-title function_">add</span>(element);
}
}
<span class="hljs-keyword">return</span> complementSet;
};
</code></pre><p>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.</p>
<p>As a one liner:</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Set</span>.<span class="hljs-property">complement</span> = <span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>([...b].<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">element</span> =></span> !a.<span class="hljs-title function_">has</span>(element)));
</code></pre><p>This uses similar tricks to the <code>Set.intersection</code> one liner. It expands the
second set as an array, filters it down to elements not in the first, and then
instantiates a new <code>Set</code> with the filtered array.</p>
https://qubyte.codes/blog/tip-customizing-npm-versionTip: customizing npm version2016-09-05T20:00:00Z2016-09-05T20:00:00Zqubytehttps://qubyte.codes
<p>The npm CLI has a bunch of useful utilities for managing projects. The obvious
one is <code>npm test</code> but there are others. I particularly like working with
<code>npm version</code> (the subject of this tip).</p>
<p>Without customization, <code>npm version</code> 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.</p>
<p>That's great, but what if you have a <code>bower.json</code> file, or some other task which
needs the new version before the tag is made? <code>npm version</code> updates the version
in <code>package.json</code> and it misses the version in the <code>bower.json</code> file. The
<code>bower.json</code> version is just an example, but one that illustrates this point.</p>
<p>This is where customizing <code>npm version</code> comes in. Continuing the <code>bower.json</code>
example, we can write a script which performs the version update, and
uses <code>git add</code> on it. In the <code>package.json</code> file:</p>
<pre><code class="language-javascript">{
<span class="hljs-string">"name"</span>: <span class="hljs-string">"my lovely app"</span>,
<span class="hljs-string">"version"</span>: <span class="hljs-number">1.0</span><span class="hljs-number">.0</span>,
<span class="hljs-string">"scripts"</span>: {
<span class="hljs-string">"version"</span>: <span class="hljs-string">"node update-bower-version.js && git add bower.json"</span>
}
}
</code></pre><p>To reiterate, <code>npm version</code> 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 <code>bower.json</code> file, then <code>git add</code>
the result.</p>
<pre><code class="language-javascript"><span class="hljs-comment">// update-bower-version.js</span>
<span class="hljs-keyword">var</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">var</span> bowerJsonPath = <span class="hljs-built_in">require</span>.<span class="hljs-title function_">resolve</span>(<span class="hljs-string">'./bower'</span>);
<span class="hljs-keyword">var</span> bowerJson = <span class="hljs-built_in">require</span>(bowerJsonPath);
bowerJson.<span class="hljs-property">version</span> = process.<span class="hljs-property">env</span>.<span class="hljs-property">npm_package_version</span>; <span class="hljs-comment">// npm injects this</span>
fs.<span class="hljs-title function_">writeFileSync</span>(bowerJsonPath, <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(bowerJson, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));
</code></pre><p>This script is going to be run by <code>npm</code> from the <code>package.json</code> file, which
handily <a href="https://docs.npmjs.com/misc/scripts#packagejson-vars">injects some environment variables</a>. One of these variables is the
version in the <code>package.json</code> file. This is great because it means that no
matter what the new version will be we have access to it in this script.</p>
<p>Since the <code>bower.json</code> file contains JSON it can be read and parsed by <code>require</code>
in one step. The version is updated, and the result stringified (I'm using two
space indentation here) and written back to the bower file.</p>
<p>With a script in place, you can use <code>npm version</code> without further thought. for
example, a major version bump looks like:</p>
<pre><code class="language-bash">npm version major
</code></pre><p>Enjoy!</p>
https://qubyte.codes/blog/progressive-enhancement-1Progressive enhancement #12016-10-15T18:00:00Z2016-10-15T18:00:00Zqubytehttps://qubyte.codes
<p>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't want to take up too much space which
could be used for content.</p>
<p>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:</p>
<pre><code class="language-css"><span class="hljs-selector-tag">h1</span> {
<span class="hljs-attribute">font-size</span>: <span class="hljs-number">2rem</span>;
<span class="hljs-attribute">margin</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">0</span>;
<span class="hljs-attribute">transition</span>: all <span class="hljs-number">0.3s</span>;
}
<span class="hljs-selector-class">.smaller</span> <span class="hljs-selector-tag">h1</span> {
<span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
<span class="hljs-attribute">margin</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">0</span>;
}
</code></pre><p>I'm a novice when it comes to CSS, so let me know if I'm missing a trick!</p>
<p>The markup for the top header (which has not changed), looks like:</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"top-header"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span>...<span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
<span class="hljs-tag"><<span class="hljs-name">nav</span>></span>...<span class="hljs-tag"></<span class="hljs-name">nav</span>></span>
<span class="hljs-tag"></<span class="hljs-name">header</span>></span>
</code></pre><p>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
<code>h1</code> text and margin smaller. When the page is scrolled down and then back to
the top, the header <code>h1</code> text returns to its original size.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">var</span> $header = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'.top-header'</span>);
<span class="hljs-keyword">function</span> <span class="hljs-title function_">checkHeaderSmallText</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">if</span> (<span class="hljs-variable language_">window</span>.<span class="hljs-property">pageYOffset</span> > <span class="hljs-number">0</span>) {
$header.<span class="hljs-property">classList</span>.<span class="hljs-title function_">add</span>(<span class="hljs-string">'smaller'</span>);
} <span class="hljs-keyword">else</span> {
$header.<span class="hljs-property">classList</span>.<span class="hljs-title function_">remove</span>(<span class="hljs-string">'smaller'</span>);
}
}
<span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'scroll'</span>, checkHeaderSmallText, <span class="hljs-literal">false</span>);
<span class="hljs-title function_">checkHeaderSmallText</span>();
</code></pre><p>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 <code>classList</code> 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.</p>
<p>In the future I have a more elaborate header animation in mind. If you're
interested in such things, watch this space!</p>
https://qubyte.codes/blog/progressive-enhancement-2Progressive enhancement #22016-11-12T03:00:00Z2019-01-17T16:00:00Zqubytehttps://qubyte.codes
<p>I recently attended ffconf, and was introduced to <code>position: sticky;</code>. 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.</p>
<p>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.</p>
<p>Update: You can view the video of Rachel Andrew's presentation including
<code>position: sticky;</code> <a href="https://youtu.be/uXYZbLT0j9c">here</a>.</p>
https://qubyte.codes/blog/promises-and-nodejs-event-emitters-dont-mixPromises and Node.js event emitters don't mix2016-12-24T17:00:00Z2016-12-24T17:00:00Zqubytehttps://qubyte.codes
<p>To many experienced Node developers, the title of this post will seem
intuitively obvious. Nevertheless, it's useful to see what unexpected behaviour
can occur when the two are used together. Here's an example:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> { <span class="hljs-title class_">EventEmitter</span> } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
<span class="hljs-keyword">const</span> emitter = <span class="hljs-keyword">new</span> <span class="hljs-title class_">EventEmitter</span>();
<span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">reject</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Oh noes!'</span>))
.<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">e</span> =></span> emitter.<span class="hljs-title function_">emit</span>(<span class="hljs-string">'error'</span>, e));
</code></pre><p>If you run this code (tested in Node v7.3 from a script, not REPL), what do you
expect to happen? I expected an <code>uncaughtException</code> event, since an event
emitter is emitting an <a href="https://nodejs.org/dist/latest-v7.x/docs/api/events.html#events_error_events">error event with no event handler</a>, but that's not
what happens at all. Instead you get an <code>UnhandledPromiseRejectionWarning</code>!</p>
<p>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
<code>uncaughtException</code> event. Uncaught exceptions <em>should</em> lead to the process
exiting, and a promise has just stifled it.</p>
<p>So, what happened? Here's a hint:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> { <span class="hljs-title class_">EventEmitter</span> } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
<span class="hljs-keyword">const</span> emitter = <span class="hljs-keyword">new</span> <span class="hljs-title class_">EventEmitter</span>();
<span class="hljs-keyword">try</span> {
emitter.<span class="hljs-title function_">emit</span>(<span class="hljs-string">'error'</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Oh noes!'</span>));
} <span class="hljs-keyword">catch</span> (e) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'caught'</span>);
}
</code></pre><p>What do you expect to happen here? Again, at first glance I would have expected
an <code>uncaughtException</code> event. <code>uncaughtException</code> 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.</p>
<p>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 <em>throws</em> the
error. Since event handlers are called synchronously upon event emissions, this:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> { <span class="hljs-title class_">EventEmitter</span> } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
<span class="hljs-keyword">const</span> emitter = <span class="hljs-keyword">new</span> <span class="hljs-title class_">EventEmitter</span>();
emitter.<span class="hljs-title function_">emit</span>(<span class="hljs-string">'error'</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Oh noes!'</span>));
</code></pre><p>is equivalent to</p>
<pre><code class="language-javascript"><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Oh noes!'</span>);
</code></pre><p>So, the very first snippet is equivalent to:</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">reject</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Oh noes!'</span>))
.<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">e</span> =></span> {
<span class="hljs-keyword">throw</span> e;
});
</code></pre><p>The promise chain captures the thrown error and wraps it as a rejected promise,
which leads to an <code>UnhandledPromiseRejectionWarning</code> since a second <code>.catch</code> is
needed in the chain to handle it.</p>
<p>If you want to ensure that unhandled error events lead to uncaught exceptions
which aren't captured by a <code>catch</code> or a promise chain, then you can wrap the
emission in a <code>setTimeout</code> or a <code>setImmediate</code>.</p>
https://qubyte.codes/blog/making-arcade-controls-arduino-leonardo-codeMaking arcade controls: Arduino Leonardo code2017-05-01T00:00:00Z2017-05-01T00:00:00Zqubytehttps://qubyte.codes
<p>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'll look when finished isn't important at the moment. It's enough now to say
that there'll be a joystick, six regular buttons, and two buttons for start and
select use.</p>
<p>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.</p>
<p>Usually when a programmer thinks "the only thing I need to do", 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've not written any
C/C++ in years. In addition to functions for setting key states, <code>keyboard.h</code>
provides a bunch of useful constants for special keys.</p>
<pre><code class="language-cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><Keyboard.h></span></span>
<span class="hljs-comment">// MAMEish. An array of pin-key pairs.</span>
<span class="hljs-keyword">struct</span> { <span class="hljs-type">int</span> pin; <span class="hljs-type">int</span> key; } pinkeys[] = {
{ <span class="hljs-number">2</span>, KEY_LEFT_ARROW },
{ <span class="hljs-number">3</span>, KEY_UP_ARROW },
{ <span class="hljs-number">4</span>, KEY_RIGHT_ARROW },
{ <span class="hljs-number">5</span>, KEY_DOWN_ARROW },
{ <span class="hljs-number">6</span>, KEY_LEFT_CTRL }, <span class="hljs-comment">// Fire 1</span>
{ <span class="hljs-number">7</span>, KEY_LEFT_ALT }, <span class="hljs-comment">// Fire 2</span>
{ <span class="hljs-number">8</span>, <span class="hljs-string">' '</span> }, <span class="hljs-comment">// Fire 3</span>
{ <span class="hljs-number">9</span>, <span class="hljs-string">'a'</span> }, <span class="hljs-comment">// Fire 4</span>
{ <span class="hljs-number">10</span>, <span class="hljs-string">'s'</span> }, <span class="hljs-comment">// Fire 5</span>
{ <span class="hljs-number">11</span>, <span class="hljs-string">'q'</span> }, <span class="hljs-comment">// Fire 6</span>
{ A0, <span class="hljs-string">'1'</span> }, <span class="hljs-comment">// start</span>
{ A1, KEY_ESC } <span class="hljs-comment">// select</span>
};
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// Set all used pins to handle input</span>
<span class="hljs-comment">// from arcade buttons.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> <span class="hljs-type">const</span> &pinkey : pinkeys) {
<span class="hljs-built_in">pinMode</span>(pinkey.pin, INPUT_PULLUP);
}
<span class="hljs-comment">// Initialize keyboard.</span>
Keyboard.<span class="hljs-built_in">begin</span>();
}
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// For each pin-key pair, check the</span>
<span class="hljs-comment">// state of the pin and set the</span>
<span class="hljs-comment">// associated key state to match.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> <span class="hljs-type">const</span> &pinkey : pinkeys) {
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">digitalRead</span>(pinkey.pin) == LOW) {
Keyboard.<span class="hljs-built_in">press</span>(pinkey.key);
} <span class="hljs-keyword">else</span> {
Keyboard.<span class="hljs-built_in">release</span>(pinkey.key);
}
}
}
</code></pre><p>Since the Arduino is programmed in a dialect of recent C++, range-based for
loops are available, as is type inference with <code>auto</code>. This meant I could use an
array of instances of an anonymous structure to express the pin-key pairs. Not a
<code>sizeof</code> in sight!</p>
https://qubyte.codes/blog/test-friendly-mixinsTest friendly mixins2017-07-20T13:30:00Z2017-07-20T13:30:00Zqubytehttps://qubyte.codes
<p>I've recently been attempting to code a clone of the classic game asteroids
using canvas in the browser. Since this is me, I've been distracted by all sorts
of programming detours.</p>
<p>This post is roughly the process I went through for one such detour.</p>
<p>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.</p>
<p>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'd
write a mixin function to copy a move method onto host objects.</p>
<p>I'm not a purist though. There'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 <code>Ship</code> child class. Other behaviours
will not be unique to child classes, so these can go into mixins.</p>
<p>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:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createMixin</span>(<span class="hljs-params">descriptors</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">mix</span>(<span class="hljs-params">obj</span>) {
<span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperties</span>(obj, descriptors);
};
}
</code></pre><p>The argument <code>descriptors</code> is an object with <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties">property descriptors</a>. I use
these rather than simple object fields since it allows me to create mixins which
are extremely configurable.</p>
<p>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 <code>move</code> method (which I suggested earlier in this post):</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> movable = <span class="hljs-title function_">createMixin</span>({
<span class="hljs-attr">move</span>: {
<span class="hljs-title function_">value</span>(<span class="hljs-params">dt</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">position</span>.<span class="hljs-property">x</span> += <span class="hljs-variable language_">this</span>.<span class="hljs-property">velocity</span>.<span class="hljs-property">x</span> * dt;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">position</span>.<span class="hljs-property">y</span> += <span class="hljs-variable language_">this</span>.<span class="hljs-property">velocity</span>.<span class="hljs-property">y</span> * dt;
},
<span class="hljs-attr">configurable</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// These three properties</span>
<span class="hljs-attr">enumerable</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// are like how class</span>
<span class="hljs-attr">writable</span>: <span class="hljs-literal">true</span> <span class="hljs-comment">// methods are set.</span>
}
});
</code></pre><p>Consider the ship at the centre of the game. We could code it like this:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> ship = {
<span class="hljs-attr">position</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">50</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">50</span> },
<span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">2</span> }
};
<span class="hljs-title function_">movable</span>(ship); <span class="hljs-comment">// Apply the mixin.</span>
ship.<span class="hljs-title function_">move</span>(<span class="hljs-number">2</span>); <span class="hljs-comment">// Now at x: 51, y: 52</span>
</code></pre><p>I want the ship to be a class extending a base class though:</p>
<pre><code class="language-javascript"><span class="hljs-comment">// Base class for all game objects.</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Entity</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params">{ position, velocity }</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">position</span> = { <span class="hljs-attr">x</span>: position.<span class="hljs-property">x</span>, <span class="hljs-attr">y</span>: position.<span class="hljs-property">y</span> };
<span class="hljs-variable language_">this</span>.<span class="hljs-property">velocity</span> = { <span class="hljs-attr">x</span>: velocity.<span class="hljs-property">x</span>, <span class="hljs-attr">y</span>: velocity.<span class="hljs-property">y</span> };
}
}
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Ship</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Entity</span> {
<span class="hljs-comment">// Ship specific behaviour in here.</span>
}
<span class="hljs-comment">// Give all Ship instances access to move.</span>
<span class="hljs-title function_">movable</span>(<span class="hljs-title class_">Ship</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>);
<span class="hljs-keyword">const</span> ship = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Ship</span>({
<span class="hljs-attr">position</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">50</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">50</span>},
<span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">2</span> }
});
<span class="hljs-title function_">movable</span>(ship); <span class="hljs-comment">// Apply the mixin.</span>
ship.<span class="hljs-title function_">move</span>(<span class="hljs-number">2</span>); <span class="hljs-comment">// Now at x: 51, y: 52</span>
</code></pre><p>All sorts of objects and classes may be composed with mixins.</p>
<p>I'm a professional JavaScript programmer, which means that each time I write
something there's a little voice in my head asking me how hard it'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:</p>
<pre><code class="language-javascript"><span class="hljs-title function_">describe</span>(<span class="hljs-string">'movable'</span>, <span class="hljs-function">() =></span> {
<span class="hljs-title function_">it</span>(<span class="hljs-string">'appends a single method "move"'</span>, <span class="hljs-function">() =></span> {
<span class="hljs-keyword">const</span> entity = {
<span class="hljs-attr">position</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
<span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> }
};
<span class="hljs-title function_">movable</span>(entity);
assert.<span class="hljs-title function_">equal</span>(<span class="hljs-keyword">typeof</span> entity.<span class="hljs-property">move</span>, <span class="hljs-string">'function'</span>);
});
<span class="hljs-title function_">it</span>(<span class="hljs-string">'updates the position given a dt'</span>, <span class="hljs-function">() =></span> {
<span class="hljs-keyword">const</span> entity = {
<span class="hljs-attr">position</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">6</span> },
<span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">2</span> }
};
<span class="hljs-title function_">movable</span>(entity);
entity.<span class="hljs-title function_">move</span>(<span class="hljs-number">3</span>);
assert.<span class="hljs-title function_">deepEqual</span>(entity.<span class="hljs-property">position</span>, { <span class="hljs-attr">x</span>: <span class="hljs-number">8</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">12</span> });
});
});
</code></pre><p>I'd be more exhaustive in real world tests, but I hope you get the gist.</p>
<p>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'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.</p>
<p>Revisiting the mixin creating function, the first go at such a mechanism looks
like:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createMixin</span>(<span class="hljs-params">descriptors</span>) {
<span class="hljs-comment">// References to objects the mixin has been</span>
<span class="hljs-comment">// applied to.</span>
<span class="hljs-keyword">const</span> mixed = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WeakSet</span>();
<span class="hljs-keyword">function</span> <span class="hljs-title function_">mix</span>(<span class="hljs-params">obj</span>) {
<span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperties</span>(obj, descriptors);
mixed.<span class="hljs-title function_">add</span>(obj);
};
mix.<span class="hljs-property">isMixed</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">obj</span>) {
<span class="hljs-keyword">return</span> mixed.<span class="hljs-title function_">has</span>(obj);
};
<span class="hljs-keyword">return</span> mix;
}
</code></pre><p>I've used a <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/WeakSet"><code>WeakSet</code></a> here. A <code>WeakSet</code> contains <em>weak</em> references to
objects. These are references which the garbage collector ignores. When no
strong references to an object in a <code>WeakSet</code> remain, the garbage collector can
clear the object. If I used a regular <code>Set</code> 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 <code>Set</code> or array be a strong reference, leading
it to grow as more and more asteroids are spawned.</p>
<p>Now we can ask the mixin if an object has had the mixin applied to it:</p>
<pre><code class="language-javascript">movable.<span class="hljs-title function_">isMixed</span>(entity); <span class="hljs-comment">// false</span>
<span class="hljs-title function_">movable</span>(entity);
movable.<span class="hljs-title function_">isMixed</span>(entity); <span class="hljs-comment">// true</span>
</code></pre><p>What about classes though? This won'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:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createMixin</span>(<span class="hljs-params">descriptors</span>) {
<span class="hljs-comment">// References to objects the mixin has been</span>
<span class="hljs-comment">// applied to.</span>
<span class="hljs-keyword">const</span> mixed = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WeakSet</span>();
<span class="hljs-keyword">function</span> <span class="hljs-title function_">mix</span>(<span class="hljs-params">obj</span>) {
<span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperties</span>(obj, descriptors);
mixed.<span class="hljs-title function_">add</span>(obj);
};
mix.<span class="hljs-property">isMixed</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">obj</span>) {
<span class="hljs-comment">// Walk up the prototype chain and check</span>
<span class="hljs-comment">// if each step has had the mixin applied</span>
<span class="hljs-comment">// to it.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> o = obj; o; o = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">getPrototypeOf</span>(o)) {
<span class="hljs-keyword">if</span> (mixed.<span class="hljs-title function_">has</span>(o)) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
}
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
};
<span class="hljs-keyword">return</span> mix;
}
</code></pre><p>This works perfectly well now, and any child classes can inherit the method as
expected and the <code>isMixed</code> 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:</p>
<pre><code class="language-javascript"><span class="hljs-title function_">it</span>(<span class="hljs-string">'is movable'</span>, <span class="hljs-function">() =></span> {
<span class="hljs-keyword">const</span> ship = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Ship</span>({
<span class="hljs-attr">position</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
<span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> }
});
assert.<span class="hljs-title function_">ok</span>(movable.<span class="hljs-title function_">isMixed</span>(ship));
});
</code></pre><p>One final adjustment can be made. It's a shame that we can use <code>instanceof</code> for
objects constructed by a class, but not objects which have had the mixin applied
to them. This can be achieved by using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance"><code>Symbol.hasInstance</code></a>. A first
attempt:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createMixin</span>(<span class="hljs-params">descriptors</span>) {
<span class="hljs-keyword">const</span> mixed = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WeakSet</span>();
<span class="hljs-keyword">function</span> <span class="hljs-title function_">mix</span>(<span class="hljs-params">obj</span>) {
<span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperties</span>(obj, descriptors);
mixed.<span class="hljs-title function_">add</span>(obj);
};
mix[<span class="hljs-title class_">Symbol</span>.<span class="hljs-property">hasInstance</span>] = <span class="hljs-keyword">function</span> (<span class="hljs-params">obj</span>) {
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> o = obj; o; o = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">getPrototypeOf</span>(o)) {
<span class="hljs-keyword">if</span> (mixed.<span class="hljs-title function_">has</span>(o)) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
}
};
<span class="hljs-keyword">return</span> mix;
}
</code></pre><p>Unfortunately this doesn't work because <code>Function.prototype[Symbol.hasInstance]</code>
is not writable. When appending a property to something, the field you're
appending it as is checked for writability, and that check propagates up the
prototype chain. Since <code>mix</code> is a function, it has a non-writable
<code>Symbol.hasInstance</code> field in its prototype chain, and we need to work around
that.</p>
<p>We can once again use <code>Object.defineProperty</code> to do it:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">createMixin</span>(<span class="hljs-params">descriptors</span>) {
<span class="hljs-keyword">const</span> mixed = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WeakSet</span>();
<span class="hljs-keyword">function</span> <span class="hljs-title function_">mix</span>(<span class="hljs-params">obj</span>) {
<span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperties</span>(obj, descriptors);
mixed.<span class="hljs-title function_">add</span>(obj);
};
<span class="hljs-title class_">Object</span>.<span class="hljs-title function_">defineProperty</span>(mix, <span class="hljs-title class_">Symbol</span>.<span class="hljs-property">hasInstance</span>, {
<span class="hljs-title function_">value</span>(<span class="hljs-params">obj</span>) {
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> o = obj; o; o = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">getPrototypeOf</span>(o)) {
<span class="hljs-keyword">if</span> (mixed.<span class="hljs-title function_">has</span>(o)) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
}
},
<span class="hljs-attr">configurable</span>: <span class="hljs-literal">false</span>,
<span class="hljs-attr">enumerable</span>: <span class="hljs-literal">false</span>,
<span class="hljs-attr">writable</span>: <span class="hljs-literal">false</span>
});
<span class="hljs-keyword">return</span> mix;
}
</code></pre><p>Finally we can do this:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Ship</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Entity</span> { <span class="hljs-comment">/* ... */</span> }
<span class="hljs-title function_">movable</span>(<span class="hljs-title class_">Ship</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>);
<span class="hljs-keyword">const</span> ship = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Ship</span>({ <span class="hljs-comment">/* ... */</span> });
ship <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">Entity</span>; <span class="hljs-comment">// true</span>
ship <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">Ship</span>; <span class="hljs-comment">// true</span>
ship <span class="hljs-keyword">instanceof</span> movable; <span class="hljs-comment">// true</span>
</code></pre><p>So the test suite for a ship can include tests like this:</p>
<pre><code class="language-javascript"><span class="hljs-title function_">it</span>(<span class="hljs-string">'is movable'</span>, <span class="hljs-function">() =></span> {
<span class="hljs-keyword">const</span> ship = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Ship</span>({
<span class="hljs-attr">position</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> },
<span class="hljs-attr">velocity</span>: { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> }
});
assert.<span class="hljs-title function_">ok</span>(ship <span class="hljs-keyword">instanceof</span> movable);
});
</code></pre><p>So there you have it! All the test friendliness of classes with the
composability of mixins.</p>
<p>I wrapped this code up in a module called <a href="https://www.npmjs.com/package/mixomatic">mixomatic</a>, which I intend to use
heavily in my gaming codebases.</p>
https://qubyte.codes/blog/my-first-custom-elementMy first custom element2017-11-16T01:45:00Z2017-11-16T01:45:00Zqubytehttps://qubyte.codes
<p>After some years of browser vendors working out what web components should look
like, they'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'll demonstrate a custom element for fuzzy counting.</p>
<p>Custom elements are created in two parts. Firstly we need to extend an element
with a JavaScript class.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">FuzzyCount</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">HTMLElement</span> {
}
</code></pre><p>So far this describes no custom behaviour. All this is is the extended class,
with identical behaviour to an <code>HTMLElement</code>. 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'll use an attribute called <code>count</code>.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">FuzzyCount</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">HTMLElement</span> {
<span class="hljs-title function_">constructor</span>(<span class="hljs-params"></span>) {
<span class="hljs-comment">// The parent constructor must be called</span>
<span class="hljs-comment">// before using `this`.</span>
<span class="hljs-variable language_">super</span>();
<span class="hljs-comment">// rawCount is a string.</span>
<span class="hljs-keyword">const</span> rawCount = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">getAttribute</span>(<span class="hljs-string">'count'</span>);
<span class="hljs-keyword">const</span> count = <span class="hljs-built_in">parseInt</span>(rawCount, <span class="hljs-number">10</span>);
<span class="hljs-comment">// Set text content based on the count.</span>
<span class="hljs-keyword">if</span> (count === <span class="hljs-number">0</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'none'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count === <span class="hljs-number">1</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'one'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count === <span class="hljs-number">2</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'a couple'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count < <span class="hljs-number">5</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'a few'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count < <span class="hljs-number">10</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'several'</span>;
} <span class="hljs-keyword">else</span> {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'lots'</span>;
}
}
}
</code></pre><p>The element reads and parses the <code>count</code> attribute, giving itself a text content
accordingly.</p>
<p>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're going to register
our element as <code>fuzzy-count</code>, but in the real world you should prefix it with
a namespace. For example, if we're working at a place called Funky Corp, we
could name the element <code>funkycorp-fuzzy-count</code>.</p>
<pre><code class="language-javascript">customElements.<span class="hljs-title function_">define</span>(<span class="hljs-string">'fuzzy-count'</span>, <span class="hljs-title class_">FuzzyCount</span>);
</code></pre><p>Now we can use this to make elements on the page!</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">fuzzy-count</span> <span class="hljs-attr">count</span>=<span class="hljs-string">"3"</span>></span><span class="hljs-tag"></<span class="hljs-name">fuzzy-count</span>></span>
</code></pre><p>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.</p>
<p>But what if we want to create elements in JS? We can try:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> fuzzyCount = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'fuzzy-count'</span>);
</code></pre><p>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'd
have an element with no <code>count</code> attribute, and so we'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.</p>
<p>Thankfully we can make a few changes to defer the setting of text content.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">FuzzyCount</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">HTMLElement</span> {
<span class="hljs-comment">// Called when the element is inserted into</span>
<span class="hljs-comment">// the document or upgraded.</span>
<span class="hljs-title function_">connectedCallback</span>(<span class="hljs-params"></span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setTextContent</span>();
}
<span class="hljs-title function_">setTextContent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> rawCount = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">getAttribute</span>(<span class="hljs-string">'count'</span>);
<span class="hljs-keyword">if</span> (!rawCount) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">''</span>;
<span class="hljs-keyword">return</span>;
}
<span class="hljs-keyword">const</span> count = <span class="hljs-built_in">parseInt</span>(rawCount, <span class="hljs-number">10</span>);
<span class="hljs-keyword">if</span> (count === <span class="hljs-number">0</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'none'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count === <span class="hljs-number">1</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'one'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count === <span class="hljs-number">2</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'a couple'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count < <span class="hljs-number">5</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'a few'</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count < <span class="hljs-number">10</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'several'</span>;
} <span class="hljs-keyword">else</span> {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">'lots'</span>;
}
}
}
</code></pre><p>The logic which sets the <code>textContent</code> has moved to its own method
<code>setTextContent</code>, and <code>connectedCallback</code> calls it when the element is
"connected" 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.</p>
<p>This is enough to get the behaviour we want, so long as the count attribute
of a new <code>fuzzy-count</code> element is set <em>before</em> it is appended to the document.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> fuzzy = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'fuzzy-count'</span>);
fuzzy.<span class="hljs-title function_">setAttribute</span>(<span class="hljs-string">'count'</span>, <span class="hljs-number">10</span>);
<span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>.<span class="hljs-title function_">appendChild</span>(fuzzy);
</code></pre><p>We can take one more step to make the element react to changes to the count
attribute...</p>
<pre><code class="language-javascript"><span class="hljs-keyword">class</span> <span class="hljs-title class_">FuzzyCount</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">HTMLElement</span> {
<span class="hljs-comment">// Called for each watched attribute when</span>
<span class="hljs-comment">// the element is added to the document or</span>
<span class="hljs-comment">// upgraded. Also fired for a watched</span>
<span class="hljs-comment">// attribute when it is added, updated, or</span>
<span class="hljs-comment">// removed.</span>
<span class="hljs-title function_">attributeChangedCallback</span>(<span class="hljs-params"></span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setTextContent</span>();
}
<span class="hljs-comment">// A static getter because we can't add a</span>
<span class="hljs-comment">// static value within a class declaration</span>
<span class="hljs-comment">// directly (yet).</span>
<span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title function_">observedAttributes</span>() {
<span class="hljs-keyword">return</span> [<span class="hljs-string">'count'</span>];
}
<span class="hljs-title function_">setTextContent</span>(<span class="hljs-params"></span>) {
<span class="hljs-comment">// Same as before.</span>
}
}
</code></pre><p><code>connectedCallback</code> has been replaced with <code>attributeChangedCallback</code>, 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 <code>attributeChangedCallback</code> is called. These changes
allow us to update elements before appending as before, and also <em>after</em>
appending.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> fuzzy = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'fuzzy-count'</span>);
<span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>.<span class="hljs-title function_">appendChild</span>(fuzzy);
fuzzy.<span class="hljs-title function_">setAttribute</span>(<span class="hljs-string">'count'</span>, <span class="hljs-number">10</span>);
</code></pre><p>Overall I'm impressed. I've only covered some of the API available here. <em>It is</em>
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.</p>
<p>Finally, a note on compatibility. At the time of writing Chrome and Safari
support custom elements with no additional effort. Other browsers must be
<a href="https://github.com/webcomponents/custom-elements">polyfilled</a> for support.</p>
https://qubyte.codes/blog/tip-arrayfromTip: Array.from2017-12-01T20:00:00Z2017-12-01T20:00:00Zqubytehttps://qubyte.codes
<p>SPOILER ALERT: If you're doing the advent of code this year, you may not want to
read onward. This post <em>does not</em> give any solutions away, but does contain
information about how I approached a part of the first challenge.</p>
<p>Onward!</p>
<p>Today I started doing the <a href="https://adventofcode.com">2017 advent of code</a> challenges (don't worry, I
won'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'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.</p>
<p>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's
ignore that option.</p>
<p>The first thing to come to mind was a loop:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> numbers = [];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < sequence.<span class="hljs-property">length</span>; i++) {
numbers.<span class="hljs-title function_">push</span>(<span class="hljs-built_in">parseInt</span>(sequence[i], <span class="hljs-number">10</span>));
}
</code></pre><p>There's a slightly nicer to read version of this introduced by ES2015:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> numbers = [];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> character <span class="hljs-keyword">of</span> sequence) {
numbers.<span class="hljs-title function_">push</span>(<span class="hljs-built_in">parseInt</span>(character, <span class="hljs-number">10</span>));
}
</code></pre><p>Both suffer from being a bit bulky though. ES2015 also introduced
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from"><code>Array.from</code></a> and the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator">spread operator (<code>...</code>)</a>, which offer a similar
way of creating an array when used in combination with <code>Array.prototype.map</code>:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> numbers = [...sequence].<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">char</span> =></span> <span class="hljs-built_in">parseInt</span>(char, <span class="hljs-number">10</span>));
<span class="hljs-comment">// or</span>
<span class="hljs-keyword">const</span> numbers = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(sequence).<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">char</span> =></span> <span class="hljs-built_in">parseInt</span>(char, <span class="hljs-number">10</span>));
</code></pre><p>The first spreads characters from the sequence into an array, and maps these
characters to a fresh array of numbers. The second uses <code>Array.from</code> to get an
array of characters from a string, and uses the same mapping to get an array
of integers.</p>
<p>I prefer the latter of the two (it'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 <code>Array.from</code>. It
turns out you can include a function to map elements to be inserted into the
returned array:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> numbers = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(sequence, <span class="hljs-function"><span class="hljs-params">char</span> =></span> <span class="hljs-built_in">parseInt</span>(char, <span class="hljs-number">10</span>));
</code></pre><p>Which is my tip! In essence, don't do this:</p>
<pre><code class="language-javascript">[...stuff].<span class="hljs-title function_">map</span>(func);
</code></pre><p>Or this:</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(stuff).<span class="hljs-title function_">map</span>(func);
</code></pre><p>Do this:</p>
<pre><code class="language-javascript"><span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(stuff, func);
</code></pre>
https://qubyte.codes/blog/advent-of-code-2017-day-20-task-2Advent of Code 2017 day 20 task 22018-01-02T20:50:00Z2018-01-02T20:50:00Zqubytehttps://qubyte.codes
<p>SPOILER ALERT: If you're doing the 2017 Advent of Code, you may not want to read
onward.</p>
<p>Over the holiday period I found a little time to work on the
<a href="https://adventofcode.com">2017 advent of code</a> 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'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.</p>
<p>At this point in the challenges, writing a parser for an input file is something
I'll skip over. The parser gives back an array of objects, each representing a
particle, with <math><mi>p</mi></math>, <math><mi>v</mi></math>, and <math><mi>a</mi></math> fields to represent position, velocity, and
acceleration respectively. These quantities are all represented as length three
arrays for the <math><mi>x</mi></math>, <math><mi>y</mi></math>, and <math><mi>z</mi></math> axes. The input looks like:</p>
<pre><code class="language-json"><span class="hljs-punctuation">[</span>
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">"p"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">-4897</span><span class="hljs-punctuation">,</span> <span class="hljs-number">3080</span><span class="hljs-punctuation">,</span> <span class="hljs-number">2133</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"v"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">-58</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-15</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-78</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"a"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">17</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-7</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">"p"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">395</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-997</span><span class="hljs-punctuation">,</span> <span class="hljs-number">4914</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"v"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">-30</span><span class="hljs-punctuation">,</span> <span class="hljs-number">66</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-69</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"a"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">1</span><span class="hljs-punctuation">,</span><span class="hljs-number">-2</span><span class="hljs-punctuation">,</span><span class="hljs-number">-8</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">"p"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">-334</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-754</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-567</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"v"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">-31</span><span class="hljs-punctuation">,</span> <span class="hljs-number">15</span><span class="hljs-punctuation">,</span> <span class="hljs-number">-34</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"a"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">3</span><span class="hljs-punctuation">,</span><span class="hljs-number">1</span><span class="hljs-punctuation">,</span><span class="hljs-number">4</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-punctuation">]</span>
</code></pre><p>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).</p>
<p>To calculate potential collisions, I used the following:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> collisions = [];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < input.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>; i++) {
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> j = i + <span class="hljs-number">1</span>; j < input[i].<span class="hljs-property">length</span>; j++) {
<span class="hljs-keyword">const</span> t = <span class="hljs-title function_">collisionTime</span>(input[i], input[j]);
<span class="hljs-keyword">if</span> (t !== <span class="hljs-literal">null</span>) {
<span class="hljs-keyword">if</span> (!collisions[t]) {
collisions[t] = [];
}
collisions[t].<span class="hljs-title function_">push</span>([i, j]);
}
}
}
</code></pre><p>I'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 <code>collisionTime</code>,
which returns either an integer (time of their first collision from <math><mrow><mi>t</mi><mo>=</mo></mrow><mrow><mn>0</mn></mrow></math>), or
<code>null</code> if they never collide. The <code>collisions</code> array is indexed by time step,
and populated with arrays of collisions. I'll come back to <code>collisionTime</code>
later, because that's where interesting things happen.</p>
<p>Next I create a set containing the IDs of all particles.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> particles = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>(<span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: input.<span class="hljs-property">length</span> }, <span class="hljs-function">(<span class="hljs-params">v, i</span>) =></span> i));
</code></pre><p>See <a href="/blog/tip-arrayfrom">my <code>Array.from</code> tip</a> for a partial explanation of this, and the
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set">MDN article on <code>Set</code></a> for the rest.</p>
<p>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.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> collisionsAtTime <span class="hljs-keyword">of</span> collisions) {
<span class="hljs-keyword">if</span> (!collisionsAtTime) {
<span class="hljs-keyword">continue</span>;
}
<span class="hljs-keyword">const</span> toDelete = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> [a, b] <span class="hljs-keyword">of</span> collisionsAtTime) {
<span class="hljs-keyword">if</span> (particles.<span class="hljs-title function_">has</span>(a) && particles.<span class="hljs-title function_">has</span>(b)) {
toDelete.<span class="hljs-title function_">add</span>(a);
toDelete.<span class="hljs-title function_">add</span>(b);
}
}
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> index <span class="hljs-keyword">of</span> toDelete) {
particles.<span class="hljs-title function_">delete</span>(index);
}
}
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(particles.<span class="hljs-property">size</span>);
</code></pre><p>I don't care about the time of each set of collisions; I only care about the
order. Since <code>collisions</code> is a sparse array (indexed by time step), I do have to
check for <code>undefined</code> elements and <code>continue</code> to skip those.</p>
<p>I'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 <code>toDelete</code>
set and removed after all collisions at a given time step have been checked.</p>
<p>Let's go back to the function <code>collisionTime</code>. For a given pair of particle
objects, it determines when their first collision is beginning at <math><mrow><mi>t</mi><mo>=</mo></mrow><mrow><mn>0</mn></mrow></math>. Here's
a naïve implementation:</p>
<pre><code class="language-javascript"><span class="hljs-comment">// Calculates the Manhatten distance between two particles.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">manhatten</span>(<span class="hljs-params">a, b</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">abs</span>(a[<span class="hljs-number">0</span>] - b[<span class="hljs-number">0</span>]) + <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">abs</span>(a[<span class="hljs-number">1</span>] - b[<span class="hljs-number">1</span>]) + <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">abs</span>(a[<span class="hljs-number">2</span>] - b[<span class="hljs-number">2</span>]);
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">vectorAdd</span>(<span class="hljs-params">a, b</span>) {
<span class="hljs-keyword">return</span> [a[<span class="hljs-number">0</span>] + b[<span class="hljs-number">0</span>], a[<span class="hljs-number">1</span>] + b[<span class="hljs-number">1</span>], a[<span class="hljs-number">2</span>] + b[<span class="hljs-number">2</span>]];
}
<span class="hljs-comment">// Returns a new particle which represents the evolution of a given particle by</span>
<span class="hljs-comment">// one time step.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">tick</span>(<span class="hljs-params">particle</span>) {
<span class="hljs-keyword">const</span> a = particle.<span class="hljs-property">a</span>.<span class="hljs-title function_">slice</span>();
<span class="hljs-keyword">const</span> v = <span class="hljs-title function_">vectorAdd</span>(particle.<span class="hljs-property">v</span>, a);
<span class="hljs-keyword">const</span> p = <span class="hljs-title function_">vectorAdd</span>(particle.<span class="hljs-property">p</span>, v);
<span class="hljs-keyword">return</span> { p, v, a };
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">collisionTime</span>(<span class="hljs-params">a, b</span>) {
<span class="hljs-keyword">let</span> nextD = <span class="hljs-title function_">manhatten</span>(a.<span class="hljs-property">p</span>, b.<span class="hljs-property">p</span>);
<span class="hljs-comment">// Trivial case in which particles are initially in the same place.</span>
<span class="hljs-keyword">if</span> (nextD === <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
<span class="hljs-keyword">let</span> t = <span class="hljs-number">0</span>;
<span class="hljs-keyword">let</span> d;
<span class="hljs-keyword">let</span> v;
<span class="hljs-keyword">let</span> nextV = <span class="hljs-title function_">manhatten</span>(a.<span class="hljs-property">v</span>, b.<span class="hljs-property">v</span>);
<span class="hljs-keyword">let</span> particle0 = a;
<span class="hljs-keyword">let</span> particle1 = b;
<span class="hljs-keyword">do</span> {
d = nextD;
v = nextV;
particle0 = <span class="hljs-title function_">tick</span>(particle0);
particle1 = <span class="hljs-title function_">tick</span>(particle1);
t++;
nextD = <span class="hljs-title function_">manhatten</span>(particle0.<span class="hljs-property">p</span>, particle1.<span class="hljs-property">p</span>);
<span class="hljs-keyword">if</span> (nextD === <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> t;
}
nextV = <span class="hljs-title function_">manhatten</span>(particle0.<span class="hljs-property">v</span>, particle1.<span class="hljs-property">v</span>);
<span class="hljs-comment">// Continue while the particles are moving together, or failing that,</span>
<span class="hljs-comment">// the rate at which they're moving apart is slowing (indicating that</span>
<span class="hljs-comment">// they will move toward each other in the future).</span>
} <span class="hljs-keyword">while</span> (nextD < d || nextV < v);
<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}
</code></pre><p>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's slow.</p>
<p>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 <em>should</em> 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).</p>
<p>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):</p>
<math display="block" class="tml-display" id="equation-1" aria-label="p_t = p_0 + v_0 t + a\sum_{n=0}^t n"><semantics><mrow><msub><mi>p</mi><mi>t</mi></msub><mo>=</mo><msub><mi>p</mi><mn>0</mn></msub><mo>+</mo><msub><mi>v</mi><mn>0</mn></msub><mi>t</mi><mo>+</mo><mi>a</mi><mrow><munderover><mo movablelimits="false">∑</mo><mrow><mi>n</mi><mo>=</mo><mn>0</mn></mrow><mi>t</mi></munderover></mrow><mi>n</mi></mrow><annotation encoding="application/x-tex">p_t = p_0 + v_0 t + a\sum_{n=0}^t n</annotation></semantics></math><p>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:</p>
<math display="block" class="tml-display" id="equation-2" aria-label="p_t = p_0 + v_0 t + a\frac{(t + 1)t}{2}"><semantics><mrow><msub><mi>p</mi><mi>t</mi></msub><mo>=</mo><msub><mi>p</mi><mn>0</mn></msub><mo>+</mo><msub><mi>v</mi><mn>0</mn></msub><mi>t</mi><mo>+</mo><mi>a</mi><mfrac><mrow><mo form="prefix" stretchy="false" lspace="0em" rspace="0em">(</mo><mi>t</mi><mo>+</mo><mn>1</mn><mo form="postfix" stretchy="false">)</mo><mi>t</mi></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">p_t = p_0 + v_0 t + a\frac{(t + 1)t}{2}</annotation></semantics></math><p>Armed with this equation, I need to calculate the difference in position
between two particles:</p>
<math display="block" class="tml-display" id="equation-3" aria-label="\Delta p_t = \Delta p_0 + \Delta v_0 t + \Delta a\frac{(t + 1)t}{2}"><semantics><mrow><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>p</mi><mi>t</mi></msub><mo>=</mo><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>p</mi><mn>0</mn></msub><mo>+</mo><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>v</mi><mn>0</mn></msub><mi>t</mi><mo>+</mo><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><mi>a</mi><mfrac><mrow><mo form="prefix" stretchy="false" lspace="0em" rspace="0em">(</mo><mi>t</mi><mo>+</mo><mn>1</mn><mo form="postfix" stretchy="false">)</mo><mi>t</mi></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\Delta p_t = \Delta p_0 + \Delta v_0 t + \Delta a\frac{(t + 1)t}{2}</annotation></semantics></math><p>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
<math><mi>t</mi></math> I want are when the left hand side (difference in position at time step <math><mi>t</mi></math>)
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 <math><mi>t</mi></math>.</p>
<math display="block" class="tml-display" id="equation-4" aria-label="0 = \Delta a t^2 + \left(2\Delta v_0 + \Delta a\right) t + 2\Delta p_0"><semantics><mrow><mn>0</mn><mo>=</mo><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><mi>a</mi><msup><mi>t</mi><mn class="tml-med-pad">2</mn></msup><mo>+</mo><mrow><mo fence="true" form="prefix">(</mo><mn>2</mn><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>v</mi><mn>0</mn></msub><mo>+</mo><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><mi>a</mi><mo fence="true" form="postfix">)</mo></mrow><mi>t</mi><mo>+</mo><mn>2</mn><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>p</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">0 = \Delta a t^2 + \left(2\Delta v_0 + \Delta a\right) t + 2\Delta p_0</annotation></semantics></math><p>This is a <a href="https://en.wikipedia.org/wiki/Quadratic_equation">quadratic equation</a>! When the acceleration is zero this collapses
down to a linear equation with the solution:</p>
<math display="block" class="tml-display" id="equation-5" aria-label="t = -\frac{\Delta p_0}{\Delta v_0}"><semantics><mrow><mi>t</mi><mo>=</mo><mo form="prefix" stretchy="false">−</mo><mfrac><mrow><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>p</mi><mn>0</mn></msub></mrow><mrow><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>v</mi><mn>0</mn></msub></mrow></mfrac></mrow><annotation encoding="application/x-tex">t = -\frac{\Delta p_0}{\Delta v_0}</annotation></semantics></math><p>When acceleration is non-zero, I have to solve a quadratic equation:</p>
<math display="block" class="tml-display" id="equation-6" aria-label="t = \frac{-b\pm\sqrt{b^2-4ac}}{2a}"><semantics><mrow><mi>t</mi><mo>=</mo><mfrac><mrow><mo lspace="0em" rspace="0em">−</mo><mi>b</mi><mo>±</mo><msqrt><mrow><msup><mi>b</mi><mn>2</mn></msup><mo>−</mo><mn>4</mn><mi>a</mi><mi>c</mi></mrow></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">t = \frac{-b\pm\sqrt{b^2-4ac}}{2a}</annotation></semantics></math><p>where</p>
<math display="block" class="tml-display" id="equation-7" aria-label="\begin{align*}
a &= \Delta a \\
b &= 2\Delta v_0 + \Delta a \\
c &= 2\Delta p_0
\end{align*}"><semantics><mtable displaystyle="true" columnalign="right left" class="tml-jot"><mtr><mtd class="tml-right"><mi>a</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><mi>a</mi></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>b</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mn>2</mn><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>v</mi><mn>0</mn></msub><mo>+</mo><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><mi>a</mi></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>c</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mn>2</mn><mpadded lspace="0"><mi mathvariant="normal">Δ</mi></mpadded><msub><mi>p</mi><mn>0</mn></msub></mrow></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
a &= \Delta a \\
b &= 2\Delta v_0 + \Delta a \\
c &= 2\Delta p_0
\end{align*}</annotation></semantics></math><p>I'm interested in solutions which are <a href="https://en.wikipedia.org/wiki/Natural_number">natural numbers</a> (real positive
integers). To get natural number solutions to quadratic equations, I wrote these
functions:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">isNaturalNumber</span>(<span class="hljs-params">num</span>) {
<span class="hljs-keyword">return</span> num >>> <span class="hljs-number">0</span> === num;
}
<span class="hljs-comment">// Provides only solutions which are natural numbers.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">solveQuadratic</span>(<span class="hljs-params">a, b, c</span>) {
<span class="hljs-keyword">if</span> (a === <span class="hljs-number">0</span>) {
<span class="hljs-keyword">const</span> solution = -c / b;
<span class="hljs-keyword">return</span> <span class="hljs-title function_">isNaturalNumber</span>(solution) ? [solution] : [];
}
<span class="hljs-keyword">const</span> rootDiscriminant = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">sqrt</span>(b * b - <span class="hljs-number">4</span> * a * c);
<span class="hljs-keyword">return</span> [
(-b + rootDiscriminant) / (<span class="hljs-number">2</span> * a),
(-b - rootDiscriminant) / (<span class="hljs-number">2</span> * a)
].<span class="hljs-title function_">filter</span>(isNaturalNumber);
}
</code></pre><p>The function <code>isNaturalNumber</code> 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 <code>NaN</code> will
become zero. Only positive integers will pass the strict equality check.
<code>solveQuadratic</code> 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.</p>
<p>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 <code>collisionTime</code>:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">collisionTime</span>(<span class="hljs-params">a, b</span>) {
<span class="hljs-keyword">let</span> commonSolutions;
<span class="hljs-comment">// Calculate the solutions for each axis and initialise or whittle down</span>
<span class="hljs-comment">// commonSolutions.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">3</span>; i++) {
<span class="hljs-keyword">const</span> dp = a.<span class="hljs-property">p</span>[i] - b.<span class="hljs-property">p</span>[i];
<span class="hljs-keyword">const</span> dv = a.<span class="hljs-property">v</span>[i] - b.<span class="hljs-property">v</span>[i];
<span class="hljs-keyword">const</span> da = a.<span class="hljs-property">a</span>[i] - b.<span class="hljs-property">a</span>[i];
<span class="hljs-keyword">const</span> axisSolutions = <span class="hljs-title function_">solveQuadratic</span>(da, <span class="hljs-number">2</span> * dv + da, <span class="hljs-number">2</span> * dp);
<span class="hljs-comment">// Most of the time there are no solutions, so this is a good</span>
<span class="hljs-comment">// optimisation.</span>
<span class="hljs-keyword">if</span> (axisSolutions.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}
<span class="hljs-keyword">if</span> (!commonSolutions) {
<span class="hljs-comment">// Initialise the set with solutions for the first axis.</span>
commonSolutions = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>(axisSolutions);
} <span class="hljs-keyword">else</span> {
<span class="hljs-comment">// Remove solutions for other axes which this axis does't have.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> solution <span class="hljs-keyword">of</span> commonSolutions) {
<span class="hljs-keyword">if</span> (!axisSolutions.<span class="hljs-title function_">includes</span>(solution)) {
commonSolutions.<span class="hljs-title function_">delete</span>(solution);
}
}
}
<span class="hljs-comment">// If the set of common solutions is ever empty, there will be no</span>
<span class="hljs-comment">// collision.</span>
<span class="hljs-keyword">if</span> (commonSolutions.<span class="hljs-property">size</span> === <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}
}
<span class="hljs-comment">// I only want the earliest collision.</span>
<span class="hljs-keyword">return</span> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">min</span>(...commonSolutions);
}
</code></pre><p>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 <code>null</code>. If any axis has
no solutions I can take a shortcut and immediately return <code>null</code>. 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!</p>
<p>On my machine, the naïve implementation takes ~3.2 seconds with the input I'm
given. With the improved implementation, it takes less than 0.1 seconds. The
full solution can be found <a href="https://gist.github.com/qubyte/d43432e0e1716bc5efca4426ee762071">in this gist</a>.</p>
https://qubyte.codes/blog/essential-tools-for-javascript-beginnersEssential tools for JavaScript beginners2018-02-11T17:00:00Z2018-02-11T17:00:00Zqubytehttps://qubyte.codes
<p>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.</p>
<p>In this post I'll talk a minimal set of tools common to all projects I use. This
consists of <a href="https://code.visualstudio.com/">Visual Studio Code</a>, <a href="https://nodejs.org/en/"><code>Node.js</code></a>, <a href="https://eslint.org/">ESLint</a>,
and <a href="http://editorconfig.org/">Editorconfig files</a>.</p>
<h2 id="visual-studio-code">Visual Studio Code</h2>
<p>There are many editors out there, and which one you use is a personal
preference. Consider this section optional, but recommended.</p>
<p><a href="https://code.visualstudio.com/">VS Code</a> is my current editor of choice. It integrates well with tools,
feels uncluttered, and doesn’t lag. It'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.</p>
<p>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.</p>
<p>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.</p>
<p>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 "Integrated Terminal".</p>
<h2 id="nodejs">Node.js</h2>
<p><a href="https://nodejs.org/en/">Node</a> 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.</p>
<p>All modern JavaScript tools are built to be executed by Node and installable
with <a href="https://www.npmjs.com/"><code>npm</code></a>, the JavaScript package manager, and executable with
<a href="https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b"><code>npx</code></a>, the JavaScript package runner, which are both installed along with
Node.</p>
<p>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're in there, run</p>
<pre><code class="language-shell">npm init
</code></pre><p>and answer the questions. You can use the defaults for most of the fields. This
initialises your project with a <a href="https://docs.npmjs.com/files/package.json"><code>package.json</code></a> 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're unsure, run</p>
<pre><code class="language-shell">npm help init
</code></pre><p>for some helpful information.</p>
<h2 id="eslint">ESLint</h2>
<p>The most important tool (after the editor) I use is <a href="https://eslint.org/">ESLint</a>. 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:</p>
<pre><code class="language-shell">npm install —-save-dev eslint eslint-config-qubyte
</code></pre><p>I'm suggesting my own ESLint config here, but there are lots of them to be
<a href="https://www.npmjs.com/browse/keyword/eslintconfig">found on npm</a>. You can create your own too!</p>
<p>When the command has finished, take a look in <code>package.json</code>. You’ll see a
<code>devDependencies</code> section with the installed packages and version numbers.
You’ll also see a new folder called <code>node_modules</code>. If you delete
<code>node_modules</code>, you can run:</p>
<pre><code class="language-shell">npm install
</code></pre><p>and npm will read <code>package.json</code> to know what to install. There may also be a
<code>package-lock.json</code>. 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're using git, add <code>node_modules</code> to your <code>.gitignore</code> file so
that git ignores that directory.</p>
<p>The <code>devDependencies</code> section is for packages used in development, so it's the
natural place for tools like ESLint. the <code>--save-dev</code> flag of <code>npm install</code>
tells <code>npm</code> to install as a development dependency.</p>
<p>With ESLint and an ESLint config installed, ESLint it must be told to use the
config. Create a file in your project directory called <code>.eslintrc.json</code> (the
leading <code>.</code> is important) and put the following into it:</p>
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"extends"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"qubyte"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"env"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"browser"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre><p>This minimal configuration tells ESLint that you're "extending" the config from
<code>eslint-config-qubyte</code>. You can find out more about configuring ESLint
<a href="https://eslint.org/docs/user-guide/configuring">here</a>. If you're using newer JavaScript features, try setting
<code>extends</code> to <code>"qubyte/ES2017"</code>.</p>
<p>VSCode has an "extension" 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
"ESLint" to find it, and install. From the view menu, click on "Problems" to
open a panel which shows you problems ESLint finds.</p>
<p>If you want to run ESLint from the terminal, navigate to your project directory
and run</p>
<pre><code class="language-shell">npx eslint .
</code></pre><p>Note the trailing dot, and that the command here is
<a href="https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b"><code>npx</code></a> not <code>npm</code>.</p>
<h2 id="editorconfig">Editorconfig</h2>
<p><a href="http://editorconfig.org/">Editorconfig</a> 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
<code>.editorconfig</code> (all lower case with a leading <code>.</code> as the first character in the
name). Put the following text into it and save:</p>
<pre><code class="language-toml"><span class="hljs-comment"># Apply settings to all text file</span>
<span class="hljs-comment"># types.</span>
<span class="hljs-section">[*]</span>
<span class="hljs-comment"># Windows likes to choose weird</span>
<span class="hljs-comment"># encodings sometimes. Use this to make</span>
<span class="hljs-comment"># sure that wherever the file is saved,</span>
<span class="hljs-comment"># it's in the expected encoding.</span>
<span class="hljs-attr">charset</span> = utf-<span class="hljs-number">8</span>
<span class="hljs-comment"># Use unix line endings everywhere for</span>
<span class="hljs-comment"># similar reasons as the charset above.</span>
<span class="hljs-attr">end_of_line</span> = lf
<span class="hljs-comment"># Wise to end on a new line character</span>
<span class="hljs-comment"># for unix.</span>
<span class="hljs-attr">insert_final_newline</span> = <span class="hljs-literal">true</span>
<span class="hljs-comment"># Indent using two spaces at a time.</span>
<span class="hljs-attr">indent_style</span> = space
<span class="hljs-attr">indent_size</span> = <span class="hljs-number">2</span>
<span class="hljs-comment"># Trims redundant spaces from the end</span>
<span class="hljs-comment"># of lines.</span>
<span class="hljs-attr">trim_trailing_whitespace</span> = <span class="hljs-literal">true</span>
</code></pre><p>Tip: If you prefer another editor, Editorconfig has broad support. It's always
worth having this file in your projects.</p>
<p>VS Code has an extension for Editorconfig files. To install it, open
"Extensions" via the icon on the left or via the view menu, and search for
"Editorconfig". The most downloaded (probably the top hit) is the one you want.
Once this is installed, there'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.</p>
<h2 id="finally">Finally</h2>
<p>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.</p>
https://qubyte.codes/blog/about-this-blog-3About this blog 32018-02-17T02:40:00Z2018-02-17T02:40:00Zqubytehttps://qubyte.codes
<p>It's been a while since <a href="/blog/about-this-blog-2">the last entry</a> about how I'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've made.</p>
<h3 id="netlify">Netlify</h3>
<p>The biggest shift has been the move from a DigitalOcean droplet, with a linux
install managed by me, to <a href="https://www.netlify.com/">Netlify</a>. 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).</p>
<h3 id="webmentions">Webmentions</h3>
<p>I've said before that I'm not a fan of comments. They're complex to manage and
open to abuse. However, I do like the IndieWeb convention of using
<a href="https://indieweb.org/Webmention">webmentions</a> from site a to site b to notify b that a links to it.
I use a Netlify deploy hook to <a href="https://glitch.com/edit/#!/send-webmentions">call a glitch</a>. This little
service scans the updated blog for new links to other sites, and sends those
sites webmentions. This is currently disabled and I'm monitoring the logs to
make sure it doesn't do anything unintentional. It'll probably go active this
weekend once I've enabled signature checking.</p>
<p>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
<code>rel="webmention"</code>, which is found in the head:</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/webmention"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"webmention"</span>></span>
</code></pre><p>Since I don't expect a large volume of mentions, I'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.</p>
<h3 id="payments">Payments</h3>
<p>A nice CLI utility called <a href="https://feross.org/introducing-thanks/"><code>thanks</code></a> 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.</p>
<p>There's an obvious scaling problem which is addressed in
<a href="https://github.com/feross/thanks/issues/2">this issue</a>. The issue suggests a new field in the
<code>package.json</code> file of a module which thanks can discover, shifting the burden
onto the module authors.</p>
<p>Since I'm keen on IndieWeb stuff, the issue led me to wonder if there is an
IndieWeb <code>rel</code> attribute for payments (much like that used for discovering
webmention endpoints). It turns out that there is and it's <code>rel="payment"</code>. It
doesn't see too much use in the wild, and it should not be the primary means of
discovering payment methods by <code>thanks</code>, but as a fallback it could be useful.
<code>thanks</code> would check for payment links on the project home page (from the
<code>homepage</code> property of the <code>package.json</code> file), followed by author URLs if
provided.</p>
<p>I've added a link tag with this attribute to the blog as well. I don't expect it
to get used, but one can always hope! Perhaps I'll become a professional
blogger.</p>
<h3 id="better-links">Better links</h3>
<p>I write my posts in markdown, and use <a href="https://www.npmjs.com/package/marked">marked</a> to process them. Marked
doesn't know which anchors will link out to external domains, so it compiles
then to <code><a></code> tags with only an <code>href</code> attribute. When linking to external
sources, the <a href="https://jakearchibald.com/2016/performance-benefits-of-rel-noopener/">current best practice</a> is to add a <code>rel="noopener"</code>
attribute. To do this, I've slightly customised the anchor renderer of marked to
add these in:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> marked = <span class="hljs-built_in">require</span>(<span class="hljs-string">'marked'</span>);
<span class="hljs-keyword">const</span> urlResolve = <span class="hljs-built_in">require</span>(<span class="hljs-string">'url'</span>).<span class="hljs-property">resolve</span>;
<span class="hljs-keyword">const</span> renderer = <span class="hljs-keyword">new</span> marked.<span class="hljs-title class_">Renderer</span>();
<span class="hljs-keyword">const</span> oldLinkRenderer = renderer.<span class="hljs-property">link</span>;
<span class="hljs-comment">// In the actual code, this is fed in to a function, not a module-scoped const.</span>
<span class="hljs-keyword">const</span> baseUrl = <span class="hljs-string">'https://qubyte.codes'</span>;
renderer.<span class="hljs-property">link</span> = <span class="hljs-function">(<span class="hljs-params">href, title, text</span>) =></span> {
<span class="hljs-keyword">const</span> fullyQualified = <span class="hljs-title function_">urlResolve</span>(baseUrl, href);
<span class="hljs-keyword">const</span> rendered = oldLinkRenderer.<span class="hljs-title function_">call</span>(renderer, href, title, text);
<span class="hljs-keyword">if</span> (fullyQualified.<span class="hljs-title function_">startsWith</span>(baseUrl)) {
<span class="hljs-keyword">return</span> rendered;
}
<span class="hljs-keyword">return</span> rendered.<span class="hljs-title function_">replace</span>(<span class="hljs-string">'<a '</span>, <span class="hljs-string">'<a target="_blank" rel="noopener" '</span>);
};
marked.<span class="hljs-title function_">setOptions</span>({
<span class="hljs-comment">// Some unrelated stuff skipped.</span>
renderer
});
</code></pre><p>It's a hack for sure, but it works!</p>
<h3 id="mathematics">Mathematics</h3>
<p>Toward the end of last year, I <a href="/blog/advent-of-code-2017-day-20-task-2">wrote a post</a> that required some
typeset mathematics. I used to write a lot in LaTeX, and so I already knew of
<a href="https://www.mathjax.org/">MathJax</a> (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't define new blocks. Instead I adjusted the
behaviour of code blocks to treat blocks labelled as <code>mathematics</code> differently:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> mathjax = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mathjax-node'</span>);
<span class="hljs-keyword">const</span> xml2js = <span class="hljs-built_in">require</span>(<span class="hljs-string">'xml2js'</span>);
<span class="hljs-keyword">const</span> marked = <span class="hljs-built_in">require</span>(<span class="hljs-string">'marked'</span>);
<span class="hljs-keyword">const</span> highlight = <span class="hljs-built_in">require</span>(<span class="hljs-string">'highlight.js'</span>);
<span class="hljs-keyword">const</span> renderer = <span class="hljs-keyword">new</span> marked.<span class="hljs-title class_">Renderer</span>();
<span class="hljs-keyword">const</span> codeRenderer = renderer.<span class="hljs-property">code</span>;
<span class="hljs-comment">// This function is synchronous, so I couldn't call MathJax</span>
<span class="hljs-comment">// in here (it's async), and must do in the highlight method.</span>
renderer.<span class="hljs-property">code</span> = <span class="hljs-keyword">function</span> (<span class="hljs-params">code, lang, escaped</span>) {
<span class="hljs-keyword">if</span> (lang === <span class="hljs-string">'mathematics'</span>) {
<span class="hljs-keyword">return</span> code;
}
<span class="hljs-keyword">return</span> codeRenderer.<span class="hljs-title function_">call</span>(<span class="hljs-variable language_">this</span>, code, lang, escaped);
};
<span class="hljs-keyword">function</span> <span class="hljs-title function_">highlight</span>(<span class="hljs-params">code, language, callback</span>) {
<span class="hljs-comment">// Non-mathematics code is syntax highlighted.</span>
<span class="hljs-keyword">if</span> (language !== <span class="hljs-string">'mathematics'</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-title function_">callback</span>(<span class="hljs-literal">null</span>, highlight.<span class="hljs-title function_">highlight</span>(language, code).<span class="hljs-property">value</span>);
}
<span class="hljs-comment">// Render with MathJax.</span>
mathjax.<span class="hljs-title function_">typeset</span>({ <span class="hljs-attr">math</span>: code, <span class="hljs-attr">format</span>: <span class="hljs-string">'TeX'</span>, <span class="hljs-attr">svg</span>: <span class="hljs-literal">true</span> }, <span class="hljs-function">(<span class="hljs-params">{ errors, svg }</span>) =></span> {
<span class="hljs-keyword">if</span> (errors) {
<span class="hljs-keyword">return</span> <span class="hljs-title function_">callback</span>(errors);
}
<span class="hljs-comment">// MathJax puts some unnecessary inline style in the output</span>
<span class="hljs-comment">// which my server response headers disagree with. The below</span>
<span class="hljs-comment">// strips those out, and adds a "mathematics" classname to</span>
<span class="hljs-comment">// the resultant SVG for styling.</span>
xml2js.<span class="hljs-title function_">parseString</span>(rendered, <span class="hljs-function">(<span class="hljs-params">err, xmlobj</span>) =></span> {
<span class="hljs-keyword">if</span> (err) {
<span class="hljs-keyword">return</span> <span class="hljs-title function_">callback</span>(err);
}
<span class="hljs-keyword">delete</span> xmlobj.<span class="hljs-property">svg</span>.<span class="hljs-property">$</span>.<span class="hljs-property">style</span>;
xmlobj.<span class="hljs-property">svg</span>.<span class="hljs-property">$</span>.<span class="hljs-property">class</span> = <span class="hljs-string">'mathematics'</span>;
<span class="hljs-keyword">const</span> builder = <span class="hljs-keyword">new</span> xml2js.<span class="hljs-title class_">Builder</span>({ <span class="hljs-attr">headless</span>: <span class="hljs-literal">true</span> });
<span class="hljs-title function_">callback</span>(<span class="hljs-literal">null</span>, builder.<span class="hljs-title function_">buildObject</span>(xmlobj));
});
});
},
marked.<span class="hljs-title function_">setOptions</span>({
highlight,
renderer
});
</code></pre><p>I'm very happy with how it turned out, but I do look forward to a more direct
means to add functionality to marked.</p>
<h3 id="not-a-progressive-web-app">Not a progressive web app</h3>
<p>For a while this blog had a service worker and an app manifest. You could even
"install" 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.</p>
<p>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.</p>
<h3 id="refactoring">Refactoring</h3>
<p>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.</p>
https://qubyte.codes/blog/putting-back-the-service-workerPutting back the service worker2018-03-08T01:20:00Z2018-03-08T01:20:00Zqubytehttps://qubyte.codes
<p>In the <a href="/blog/about-this-blog-3">last post about this blog</a> I wrote about why
I removed the service worker which made this blog a progressive web application.</p>
<p>The way my blog handles CSS predates the wide availability of service workers.
Since CSS link tags are blocking, it'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 <code>href</code> of the CSS link tag in each page
of this blog.</p>
<p>The server sends this header along with CSS it serves:</p>
<pre><code class="language-properties"><span class="hljs-attr">Cache-Control</span>: <span class="hljs-string">max-age=315360000, public, immutable</span>
</code></pre><p>The <code>immutable</code> tells the browser the file will never change while within the
<code>max-age</code> (so I make it very long). Chrome doesn't support <code>immutable</code>, but does
exhibit similar behaviour. The <code>public</code> allows proxies to similarly cache the
response.</p>
<p>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:</p>
<pre><code class="language-properties"><span class="hljs-attr">Cache-Control</span>: <span class="hljs-string">no-cache</span>
</code></pre><p>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).</p>
<p>Unfortunately this didn'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
<a href="https://www.npmjs.com/package/sw-precache">sw-precache</a>, 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<sup>†</sup> 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.</p>
<p>The net effect was minor CSS changes triggering a mass download of my blog. This
wasn't a good use of server or browser resources, so I removed the worker.</p>
<p>I recently attended a <a href="https://indieweb.org/Homebrew_Website_Club">Homebrew Website Club</a> held by
Jeremy Keith. By chance he'd written <a href="https://adactio.com/journal/13540">a blog post</a> on this
issue the day before, in which he provides a <em>minimum viable service worker</em>.</p>
<p>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.</p>
<p>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
<a href="/blog/test-friendly-mixins">that blog entry about mixins</a> you saw earlier and
are now bored enough to take another look at. If you're stuck <em>outside</em> of a
tunnel and I've updated the CSS<sup>‡</sup>, 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'll ever be necessary).</p>
<p>I'll still need to do <em>some</em> work though. As mentioned in that post, cleanup
isn't addressed. I'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...</p>
<p>In the meantime, I'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
(<code>EventSource</code>) connections. I use these in development to hot-reload my blog
when changes are made. The original script ignores all but GET requests:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">if</span> (request.<span class="hljs-property">method</span> !== <span class="hljs-string">'GET'</span>) {
<span class="hljs-keyword">return</span>;
}
</code></pre><p>I've extended this to also ignore server sent event connections:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> acceptHeader = request.<span class="hljs-property">headers</span>.<span class="hljs-title function_">get</span>(<span class="hljs-string">'Accept'</span>);
<span class="hljs-keyword">if</span> (request.<span class="hljs-property">method</span> !== <span class="hljs-string">'GET'</span> || acceptHeader.<span class="hljs-title function_">includes</span>(<span class="hljs-string">'text/event-stream'</span>)) {
<span class="hljs-keyword">return</span>;
}
</code></pre><p>You can see the current <a href="/sw.js">service worker code I'm using here</a>.</p>
<p><sup>†</sup> I had to add some CSS to handle superscripts, such as the one used
for this footnote.</p>
<p><sup>‡</sup> Perhaps the blog will be a different shade of beige...</p>
https://qubyte.codes/blog/a-brighter-shade-of-beigeA brighter shade of beige2018-04-02T22:00:00Z2018-04-02T22:00:00Zqubytehttps://qubyte.codes
<p>When I originally built this blog, I gave it a very simple <em>no nonsense</em> 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).</p>
<p>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've published some
changes, and this post is about those.</p>
<p>I took this as an opportunity to overhaul my CSS tooling along with the style. I
switched from <a href="https://www.npmjs.com/package/clean-css">clean-css</a> to <a href="http://postcss.org/">PostCSS</a> with
<a href="https://www.npmjs.com/package/postcss-import">import</a>, <a href="http://cssnext.io/">cssnext</a>, and <a href="http://cssnano.co/">cssnano</a> plugins. These do
the import inlining and minification I got from clean-css, but also allowed me
to compile out some <code>calc</code> 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.</p>
<p>For the palette, I opted to use HSL with CSS custom properties for the the
individual parameters, and <code>calc</code> 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 <a href="http://v6.robweychert.com/blog/2018/02/v6-color/">recent post by Rob Weychert</a> on colours
for his blog, but I decided to stick with CSS rather than move to Sass.</p>
<p>The result doesn't appear to be a large departure from what was there before.
I'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
<a href="https://twitter.com/cassiecodes/status/980818410010562560">described as <em>Battenberg chic</em></a>, which I rather like! If you'd like to
compare, the previous style can be seen <a href="https://web.archive.org/web/20180104055846/https://qubyte.codes">on the Wayback Machine</a>.</p>
<p>Here are the custom properties at the time of writing:</p>
<pre><code class="language-css"><span class="hljs-selector-pseudo">:root</span> {
<span class="hljs-attr">--base-background-hue</span>: <span class="hljs-number">45</span>;
<span class="hljs-attr">--base-background-sat</span>: <span class="hljs-number">100%</span>;
<span class="hljs-attr">--base-background-lum</span>: <span class="hljs-number">90%</span>;
<span class="hljs-attr">--base-foreground-hue</span>: <span class="hljs-built_in">calc</span>(<span class="hljs-built_in">var</span>(--base-background-hue) + <span class="hljs-number">180</span>);
<span class="hljs-attr">--base-foreground-sat</span>: <span class="hljs-number">100%</span>;
<span class="hljs-attr">--base-foreground-lum</span>: <span class="hljs-number">30%</span>;
<span class="hljs-attr">--background-color-main</span>: <span class="hljs-built_in">hsl</span>(
<span class="hljs-built_in">var</span>(--base-background-hue),
<span class="hljs-built_in">var</span>(--base-background-sat),
<span class="hljs-built_in">var</span>(--base-background-lum)
);
<span class="hljs-attr">--background-color-alt</span>: <span class="hljs-built_in">hsl</span>(
<span class="hljs-built_in">calc</span>(<span class="hljs-built_in">var</span>(--base-background-hue) - <span class="hljs-number">30</span>),
<span class="hljs-built_in">var</span>(--base-background-sat),
<span class="hljs-built_in">var</span>(--base-background-lum)
);
<span class="hljs-attr">--standout-color-main</span>: <span class="hljs-built_in">hsl</span>(
<span class="hljs-built_in">var</span>(--base-foreground-hue),
<span class="hljs-built_in">var</span>(--base-foreground-sat),
<span class="hljs-built_in">var</span>(--base-foreground-lum)
);
}
</code></pre><p>Where <code>--background-color-main</code> is the background colour of the body,
<code>--background-color-alt</code> is the background colour of the navigation bar (and
probably other things later), and <code>--standout-color-main</code> is the colour of the
text.</p>
<p>I'm looking forward to CSS color module level 4, which will introduce
<code>color-mod</code> to obtain one colour based on another (I found out about this via
<a href="https://www.lottejackson.com/learning/css-color-module-level-4">Charlotte Jackson's blog</a>). For example, the background colours
could be defined much more tersely as</p>
<pre><code class="language-css"><span class="hljs-selector-pseudo">:root</span> {
<span class="hljs-attr">--background-color-main</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-number">45</span>, <span class="hljs-number">100%</span>, <span class="hljs-number">90%</span>);
<span class="hljs-attr">--background-color-alt</span>: <span class="hljs-built_in">color-mod</span>(<span class="hljs-built_in">var</span>(--background-color-main) <span class="hljs-built_in">hue</span>(-<span class="hljs-number">30</span>));
}
</code></pre><p>so there would be less need for separate values for hue, saturation, and
lightness. I <em>could</em> 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't ratified yet anyway. I'm using
PostCSS solely to make the CSS of my blog available as a single, minified, and
backwards-compatible file.</p>
https://qubyte.codes/blog/update-on-webmentionsUpdate on webmentions2018-04-04T19:10:00Z2018-04-04T19:10:00Zqubytehttps://qubyte.codes
<p>In <a href="/blog/about-this-blog-3">a recent post</a> I wrote that I had integrated
webmentions, and some of that has since changed. Time for an update.</p>
<p>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 <code>POST</code> 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 <code>source</code> and <code>target</code>
parameters) did not.</p>
<p>I replaced this functionality by pointing the form and webmention link to
<a href="https://webmention.io">webmention.io</a>, which was simple since I already have
<a href="https://indieauth.com">IndieAuth</a> working with this blog. I may make a custom receiver for
webmentions later, but this is an excellent stopgap.</p>
<p>On to more exciting stuff... I've been enjoying <a href="https://mastodon.social">Mastodon</a> lately. An
<a href="https://github.com/tootsuite/mastodon/issues/6074">issue was recently opened</a> about having Mastodon dispatch
webmentions for links in status updates. Unfortunately (but I think for good
reasons), it didn't work out and the issue is now closed. I'm happy for my own
status updates to dispatch webmentions though, so I
<a href="https://glitch.com/edit/#!/mastodon-webmention-relay">put together a glitch</a> to do this for me! Take a look, and
remix if you like the idea.</p>
https://qubyte.codes/blog/content-security-policy-and-service-workersContent-Security-Policy and service workers2018-04-23T23:00:00Z2018-04-23T23:00:00Zqubytehttps://qubyte.codes
<p>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 <a href="/about">about</a> page. This post is the result of
<a href="https://twitter.com/jaffathecake/status/988402162312114177">a conversation I had with Jake Archibald</a> on twitter (with thanks for
helping me to understand what was going on).</p>
<p>When I originally built this blog I set the content security policy (omitting
policies which aren't pertinent):</p>
<pre><code class="language-properties"><span class="hljs-attr">Content-Security-Policy</span>: <span class="hljs-string">default-src 'self'; img-src *;</span>
</code></pre><p>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.</p>
<p>Until now this blog has had no images at all, so no issues with this content
security policy with respect to images were obvious.</p>
<p>When I added an image the browser refused to load it. Firefox wasn't much help
here, but Chrome gave me a useful error message in the console (the URL is
omitted for brevity):</p>
<pre><code class="language-plaintext">Refused to connect to '<URL>' because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'connect-src' was not explicitly set, so 'default-src' is used as a fallback.
</code></pre><p>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 <code>connect-src</code> policy when performing the request for the image, and not the
<code>image-src</code> policy I expected. <code>connect-src</code> is the policy used by <em>scripts</em>
making requests. Since I don't define a <code>connect-src</code> policy, the fallback is
<code>default-src</code>, which is limited to the domain of the site, and does not allow an
image to be downloaded from a CDN.</p>
<p>There is a quick solution, which is to add a <code>connect-src *;</code> 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:</p>
<pre><code class="language-toml"><span class="hljs-section">[[headers]]</span>
<span class="hljs-attr">for</span> = <span class="hljs-string">"/sw.js"</span>
<span class="hljs-section">[headers.values]</span>
<span class="hljs-attr">Content-Security-Policy</span> = <span class="hljs-string">"connect-src *;"</span>
</code></pre><p>But what's actually going on here? I was confused because I had expected
the fetch performed inside the worker to be subject to the <code>image-src</code> policy.
I even checked that the <a href="https://fetch.spec.whatwg.org/#concept-request-initiator">initiator</a> and <a href="https://fetch.spec.whatwg.org/#concept-request-destination">destination</a> of
the request in the fetch event handler were for an image.</p>
<p>The fetch event and handler looks something like the following:</p>
<pre><code class="language-javascript"><span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">fetchEvent</span> =></span> {
<span class="hljs-keyword">const</span> responseFromFetch = <span class="hljs-title function_">fetch</span>(fetchEvent.<span class="hljs-property">request</span>);
<span class="hljs-comment">// Other stuff omitted...</span>
});
</code></pre><p>I'm effectively proxying the request. I've omitted a bunch of stuff to do with
caching.</p>
<p>When fetch is called with a request object, the URL of the request is used to
make an entirely <a href="https://fetch.spec.whatwg.org/#fetch-method">new request before processing</a> it. This new request
lacks information about the initiator and destination, and so it is subject to
the <code>connect-src</code> policy.</p>
<p>This seemed bad to me at first. CSP being different with and without a service
worker would be bad because you'd have to test both each time a new resource
type is added.</p>
<p>Fortunately, it turns out that the browser also performs CSP checks on the
request <em>before</em> 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'm not doing). Restating for the example of an image, these three checks
are made:</p>
<ul>
<li>The initial request for is checked for <code>image-src</code> violation.</li>
<li>A request with the same URL is checked in the service worker for <code>connect-src</code> violation.</li>
<li>The response from the service worker is checked for <code>image-src</code> violation.</li>
</ul>
<p>This means that I'm safe with the <code>connect-src *;</code> policy for the service worker
mentioned above, since the browser was already applying the <code>image-src</code> policy
to image requests before the service worker saw them!</p>
https://qubyte.codes/blog/a-battenberg-in-svgA Battenberg in SVG2018-06-10T18:14:20Z2018-06-10T18:14:20Zqubytehttps://qubyte.codes
<p>In celebration of the Battenberg theme, here is an animated Battenberg! It's
made of two SVG paths composed of lines and arcs. These are calculated using
three angles and a bucket load of trigonometry (I'm not as good as I used to be
at trig). A <code>requestAnimationFrame</code> 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.</p>
<p>There are better ways to do this. I chose SVG to challenge myself!</p>
https://qubyte.codes/blog/automatic-announcement-of-new-blog-entriesAutomatic announcement of new blog entries2018-08-01T18:40:00Z2018-08-01T18:40:00Zqubytehttps://qubyte.codes
<p>It occurred to me a couple of days ago that it'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.</p>
<p>As part of the build process this blog generates a sitemap. All blog entries
hang off of the <code>/blog</code> path, so it's not difficult to filter the sitemap down
to only blog entires. By comparing a sitemap before and after deployment, it's
possible to know when one or more entries have been added, and should be
tweeted!</p>
<p>Glitch is a natural fit for this. It gives you a little persistent storage (a
directory called <code>.data</code>), which can be used to stash the sitemap after each
time it gets triggered.</p>
<p>In depth...</p>
<h2 id="step-1-commits-are-pushed">Step 1: Commits are pushed</h2>
<p>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.</p>
<p>GitHub then dispatches a notification to Netlify.</p>
<h2 id="step-2-netlify-receives-a-notification">Step 2: Netlify receives a notification</h2>
<p>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.</p>
<p>The new part is that a <a href="https://www.netlify.com/docs/webhooks/#outgoing-webhooks-and-notifications">notification</a> is configured to send a POST to glitch
when a deploy succeeds.</p>
<h2 id="step-3-a-glitch-app-receives-a-notification">Step 3: A glitch app receives a notification</h2>
<p>I built <a href="https://glitch.com/~tweet-new-blog-posts">this glitch app</a> 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.</p>
<p>For valid POSTs with a production context, the app makes a request for the
<a href="/sitemap.txt">sitemap</a> of this blog, and loads the previous sitemap from its
<code>.data</code> 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.</p>
<p>Finally, each new blog post is formatted as a tweet, and posted to twitter!</p>
<h2 id="lessons">Lessons</h2>
<p>While it'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'm glad I made it a newline separated list rather than an XML
monstrosity because it's so easy to parse this way.</p>
https://qubyte.codes/blog/routes-of-restful-services-with-nested-resourcesRoutes of RESTful services with nested resources2018-10-19T19:00:30Z2018-10-19T19:00:30Zqubytehttps://qubyte.codes
<p>Nested resources are common in real life, and when you'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.</p>
<p>Let's say we're building an API for carpenters. Their guild wants a way to help
the carpenters manage their clients, and the items which they're creating for
their clients (jobs). A set of routes for this might look like</p>
<pre><code class="language-properties"><span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/carpenters</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/clients</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/clients</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/clients/:clientId</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/jobs</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/jobs</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/jobs/:jobId</span>
</code></pre><p>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.</p>
<p>The problem with this layout is that, as a carpenter, you're rarely going to
want to see clients which aren't <em>your</em> 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'll want to filter it by the client. We can use query parameters to
supply additional context, but this seems a bit clumsy. Let's have another go</p>
<pre><code class="language-properties"><span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/carpenters</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId/clients</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/carpenters/:carpenterId/clients</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId/clients/:clientId</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId/clients/:clientId/jobs</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/carpenters/:carpenterId/clients/:clientId/jobs</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId/clients/:clientId/jobs/:jobId</span>
</code></pre><p>It's pretty clear now that these are nested resources. It's no longer possible
to select a collection without refining it by its parent resources.</p>
<p>The problem now is that it's a bit annoying to have to use <em>all</em> the parent IDs
of a list or a member. I recently realised (and I'm probably late to the party)
that a good solution is a compromise between the two</p>
<pre><code class="language-properties"><span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/carpenters</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/carpenters/:carpenterId/clients</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/carpenters/:carpenterId/clients</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/clients/:clientId</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/clients/:clientId/jobs</span>
<span class="hljs-attr">POST</span> <span class="hljs-string">/clients/:clientId/jobs</span>
<span class="hljs-attr">GET</span> <span class="hljs-string">/jobs/:jobId</span>
</code></pre><p>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't need to include its parent in the path. This includes other singular
operations such as PUT and DELETE.</p>
<p>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!</p>
https://qubyte.codes/blog/parsing-input-from-stdin-to-structures-in-rustParsing input from stdin to structures in Rust2019-01-01T23:30:00Z2019-04-22T13:40:00Zqubytehttps://qubyte.codes
<p>I've been working on this post for a while, but about a month ago my firstborn
arrived, and he's been getting the lion'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's a little rough around the edges!</p>
<p>In a post a while back I described in depth my solution to an
<a href="https://adventofcode.com/">Advent of Code</a> challenge. This year I took part again, but rather than
using Node.js to write programs to solve challenges I decided to use Rust.</p>
<p>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'll be focussing
on how I approached the last case for this article.</p>
<p>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 <code>Option</code> and
<code>Result</code> types, and type casting in the process. I'll use the challenge of
<a href="https://adventofcode.com/2018/day/10">day 10</a> for illustrative purposes.</p>
<p>To be unix-y, I decided to read input from stdin rather than reading from a
file.</p>
<pre><code class="language-shell">cargo run < input.txt
</code></pre><p>On day 10 the data looked like</p>
<pre><code class="language-plaintext">position=<-10427, -42253> velocity=< 1, 4>
position=< 21343, 42515> velocity=<-2, -4>
position=<-10417, -52846> velocity=< 1, 5>
</code></pre><p>Each line represents what I will call a particle, including its initial position
and velocity.</p>
<p>Rust ships with some nice functionality for working with input from stdin. The
<code>main</code> function for my solution to Day 10 looks like</p>
<pre><code class="language-rust"><span class="hljs-keyword">use</span> std::io::{stdin, BufRead};
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {
<span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">particles</span>: <span class="hljs-type">Vec</span><Particle> = <span class="hljs-title function_ invoke__">stdin</span>()
.<span class="hljs-title function_ invoke__">lock</span>()
.<span class="hljs-title function_ invoke__">lines</span>()
.<span class="hljs-title function_ invoke__">filter_map</span>(|line_result| line_result.<span class="hljs-title function_ invoke__">ok</span>()) <span class="hljs-comment">// #1</span>
.<span class="hljs-title function_ invoke__">filter_map</span>(|line| line.<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">ok</span>()) <span class="hljs-comment">// #2</span>
.<span class="hljs-title function_ invoke__">collect</span>(); <span class="hljs-comment">// #3</span>
<span class="hljs-comment">// use particles</span>
}
</code></pre><p>I like the readability of this chain. <code>stdin().lock().lines()</code> returns an
iterator which yields lines, each of which is a <a href="https://doc.rust-lang.org/std/result/enum.Result.html"><code>Result</code></a> containing
either a string or an error. The rest of this chain filters out errors (<code>#1</code>),
parses line strings into a custom <code>Particle</code> type (<code>#2</code>), and finally collects
elements into a vector of particles (<code>#3</code>)`.</p>
<p>Steps <code>#1</code> and <code>#2</code> both make use of <a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map">filter_map</a>. This is a method of
iterators which takes a closure. The closure takes an element and returns an
<code>Option</code>. When the option is <code>None</code> the element is filtered out. When it returns
<code>Some(value)</code> the element is mapped to <code>value</code> in the ongoing stream. Since
elements of the line iterator are <code>Result</code>s, I call <code>ok</code> on them, which returns
an <a href="https://doc.rust-lang.org/std/option/enum.Option.html"><code>Option</code></a> type which discards an <code>Err(error)</code> for a <code>None</code>.
<code>filter_map</code> then unwraps the value for the next step in the chain, or filters
out the <code>None</code> (so line errors are effectively ignored).</p>
<p>Early on in my journey into Rust I found <code>Result</code>s and <code>Options</code> quite annoying.
I wrote a lot of <code>if</code>s and <code>unwraps</code> to force values out of them. After I while
I came across things like <code>filter_map</code>, <code>if let</code>, and <code>match</code> for working with
them more fluently, I realised that they're really neat.</p>
<p>The next step (<code>#2</code>) is to parse the line string into a <code>Particle</code> structure
wrapped in a result, and again discard errors and unwrap. Finally (<code>#3</code>) the
results are collected into a vector for easy use in the rest of the program.</p>
<p>You might have noticed that I've skipped a big chunk of what's going on in <code>#2</code>.
How do I parse lines into a structure which I've defined? By implementing
<code>std::str::FromStr</code> for the <code>Particle</code> structure, <code>parse</code> gains the
ability to parse to <code>Particle</code>. In the chain above, the type of <code>particles</code> is
<code>Vec<Particle></code>, which is enough for Rust to infer that the call to <code>parse</code>
means to parse to <code>Particle</code>. This type annotation is also used by collect to
determine that we want <code>Particles</code> to be collected into a vector.</p>
<p>Here's the Particle definition:</p>
<pre><code class="language-rust"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Particle</span> {
position_x: <span class="hljs-type">isize</span>,
position_y: <span class="hljs-type">isize</span>,
velocity_x: <span class="hljs-type">isize</span>,
velocity_y: <span class="hljs-type">isize</span>
}
</code></pre><p>There's not a lot to see here. It's really just a wrapper for <code>x</code> and <code>y</code>
positions and velocities as read from the input. I assumed that <code>isize</code> will
be sufficient to contain any values read (though this probably isn't the correct
integer type to use).</p>
<p>It's possible for a string to <em>not</em> parse to a <code>Particle</code>. For this, a new type
of error needs to be defined. I'm just going to put it below here since there's
a lot I don't understand about defining errors in Rust yet.</p>
<pre><code class="language-rust"><span class="hljs-keyword">use</span> std::{
fmt::{<span class="hljs-keyword">self</span>, Display, Formatter},
error::Error,
num::ParseIntError
};
<span class="hljs-meta">#[derive(Debug, Clone)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">ParseParticleError</span>;
<span class="hljs-keyword">impl</span> <span class="hljs-title class_">Display</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">ParseParticleError</span> {
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">fmt</span>(&<span class="hljs-keyword">self</span>, f: &<span class="hljs-keyword">mut</span> Formatter) <span class="hljs-punctuation">-></span> fmt::<span class="hljs-type">Result</span> {
<span class="hljs-built_in">write!</span>(f, <span class="hljs-string">"Unable to parse to a Particle."</span>)
}
}
<span class="hljs-keyword">impl</span> <span class="hljs-title class_">Error</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">ParseParticleError</span> {
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">description</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> &<span class="hljs-type">str</span> {
<span class="hljs-string">"Unable to parse to a Particle."</span>
}
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">cause</span>(&<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Option</span><&Error> {
<span class="hljs-literal">None</span>
}
}
<span class="hljs-keyword">impl</span> <span class="hljs-title class_">From</span><ParseIntError> <span class="hljs-keyword">for</span> <span class="hljs-title class_">ParseParticleError</span> {
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">from</span>(_error: ParseIntError) <span class="hljs-punctuation">-></span> <span class="hljs-keyword">Self</span> {
ParseParticleError
}
}
</code></pre><p>The one part I will dwell on is the final implementation. It allows
<code>ParseIntError</code> errors to be cast to <code>ParseParticleError</code>. This becomes handy in
the <code>FromStr</code> implementation:</p>
<pre><code class="language-rust"><span class="hljs-keyword">use</span> regex::Regex;
<span class="hljs-keyword">use</span> lazy_static::lazy_static;
<span class="hljs-keyword">use</span> std::<span class="hljs-type">str</span>::FromStr;
<span class="hljs-keyword">const</span> PARTICLE_REGEX: &<span class="hljs-type">str</span> = <span class="hljs-string">r"<\s*(-?\d+),\s*(-?\d+)>.*<\s*(-?\d+),\s*(-?\d)>"</span>;
<span class="hljs-keyword">impl</span> <span class="hljs-title class_">FromStr</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">Particle</span> {
<span class="hljs-keyword">type</span> <span class="hljs-title class_">Err</span> = ParseParticleError;
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">from_str</span>(particle_str: &<span class="hljs-type">str</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">Result</span><<span class="hljs-keyword">Self</span>, <span class="hljs-keyword">Self</span>::<span class="hljs-literal">Err</span>> {
lazy_static! {
<span class="hljs-keyword">static</span> <span class="hljs-keyword">ref</span> regex: Regex = Regex::<span class="hljs-title function_ invoke__">new</span>(PARTICLE_REGEX).<span class="hljs-title function_ invoke__">unwrap</span>();
}
regex.<span class="hljs-title function_ invoke__">captures</span>(particle_str)
.<span class="hljs-title function_ invoke__">ok_or</span>(ParseParticleError)
.<span class="hljs-title function_ invoke__">and_then</span>(|cap| <span class="hljs-title function_ invoke__">Ok</span>(Particle {
position_x: cap[<span class="hljs-number">1</span>].<span class="hljs-title function_ invoke__">parse</span>()?,
position_y: cap[<span class="hljs-number">2</span>].<span class="hljs-title function_ invoke__">parse</span>()?,
velocity_x: cap[<span class="hljs-number">3</span>].<span class="hljs-title function_ invoke__">parse</span>()?,
velocity_y: cap[<span class="hljs-number">4</span>].<span class="hljs-title function_ invoke__">parse</span>()?
}))
}
}
</code></pre><p>While Rust doesn't come with a library for working with regular expressions,
there is the excellent <a href="https://crates.io/crates/regex"><code>regex</code></a> crate on <a href="hhttps://crates.io">crates.io</a>. I used this
and <a href="https://crates.io/crates/lazy_static"><code>lazy_static</code></a> 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 <a href="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">regular expression
unsophisticated</a> I decided it was a reasonable course.</p>
<p>The creation of the regular expression is wrapped in <code>lazy_static</code>, which means
that this only occurs once, and subsequent calls to <code>from_str</code> will reuse it.
The documentation for the <code>regex</code> library suggests the use of <code>lazy_static</code>, so
there was no ingenuity on my part.</p>
<p>The regular expression matches lines which look like lines of the above input
sample, capturing the numbers (including a leading <code>-</code> if there is one).</p>
<p>Onto the chain! The regular expression is applied to <code>particle_str</code>. The call
to <code>captures</code> returns an option. The <code>ok_or</code> step in the chain turns a <code>None</code>
into a <code>ParseParticleError</code>. When the regular expression finds no match in a
string then it cannot be parsed to a particle!</p>
<p>The next step is an <code>and_then</code>. This unwraps <code>Some(value)</code> to <code>value</code> and passes
the value to its closure. Errors are waved past by this step. The return value
of the closure is a <code>Result</code>. 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 <code>isize</code>. <a href="https://rust-lang-nursery.github.io/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html">The <code>?</code> operator</a> unwraps result so
that a <code>Particle</code> structure can be composed. If any isize parse results in an
error, the error is immediately returned (and cast to a <code>ParseParticleError</code>).
Since the return value of this closure must be a <code>Result</code>, the particle is
wrapped in an <code>Ok</code>.</p>
<p>All of this effort to implement string parsing for <code>Particle</code> might seem
redundant given that errors are filtered out by the main function anyway, but I
enjoyed exploring this functionality of Rust. If you're interested in the full
code of my solution to day 10, you can find it <a href="https://gist.github.com/qubyte/f5a68779d4c7f14f2d722a5d13815bb4">here</a>.</p>
<h2 id="update-2019-04-22">Update 2019-04-22</h2>
<p>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 <a href="https://crates.io/crates/recap">recap</a> crate neatly avoids
the need to manually apply a regular expression and extract elements from it,
parsing each individually.</p>
<p>Adding parsing of lines to particle structures now looks like:</p>
<pre><code class="language-rust"><span class="hljs-keyword">use</span> recap::Recap;
<span class="hljs-keyword">use</span> serde::Deserialize;
<span class="hljs-meta">#[derive(Debug, Clone, Deserialize, Recap)]</span>
<span class="hljs-meta">#[recap(regex = r<span class="hljs-string">"<\s*(?P<position_x>-?\d+),\s*(?P<position_y>-?\d+)>.*<\s*(?P<velocity_x>-?\d+),\s*(?P<velocity_y>-?\d)>"</span>)]</span>
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">Particle</span> {
position_x: <span class="hljs-type">isize</span>,
position_y: <span class="hljs-type">isize</span>,
velocity_x: <span class="hljs-type">isize</span>,
velocity_y: <span class="hljs-type">isize</span>
}
</code></pre><p>That's it! Errors produced (or lack thereof) might be quite different, I'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 <code>struct</code> is used to parse each. A gist containing
the complete updated solution can be found <a href="https://gist.github.com/qubyte/d259676b4927a6fbe8fd5f99e61d2e62">here</a>.</p>
https://qubyte.codes/blog/my-new-year-resolutionMy new year resolution2019-01-16T23:27:12Z2019-01-16T23:27:12Zqubytehttps://qubyte.codes
<p>My resolution this year was to work on my Japanese speaking ability.</p>
<p>I've already failed.</p>
<p>The keys to making a good resolution are to make it measurable, and incremental.
To achieve it you need to define what "progress" and "done" mean for it. A vague
promise to get better at something does neither.</p>
<p>So, how do I rescue it?</p>
<p>It's never too late! I'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's the biggest reason
for me to improve my Japanese (we want him to be bilingual), he's also a time
sink (in a good way!)</p>
<p>First I must ask myself what's been holding me back. I've been learning Japanese
on and off since around 2001, and I still can'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.</p>
<p>Perfectionism is easy to explain. I'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.</p>
<p>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't write some code to learn it for me. Not yet anyway.</p>
<p>Role-play is more difficult to explain. A language isn't just words. It has a
personality, and you have to act it as much as you speak it. I'm bad at
role-play. It terrifies me.</p>
<p>These three issues are holding me back, and I don't think there's a solution to
that. I'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.</p>
<p>So, how should I define progress? What does measurable look like for the ability
to speak a language? I'd love to know your thoughts!</p>
https://qubyte.codes/blog/a-recent-contribution-i-made-for-nodejsA recent contribution I made for Node.js2019-02-27T23:05:00Z2019-02-27T23:05:00Zqubytehttps://qubyte.codes
<p>I'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 <a href="https://github.com/nodejs/node/pull/25974">a contribution</a> which might be noticed though.</p>
<p>Most Node developers are familiar with <a href="http://expressjs.com/">Express</a>. When responding to a client
request in Express, <em>chaining</em> makes setting the status and sending a body
fluent. It is possible to chain the call to <code>send</code> after the call to <code>status</code>
because <code>status</code> returns <code>res</code>.</p>
<pre><code class="language-javascript"><span class="hljs-comment">// Express version.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">requestHandler</span>(<span class="hljs-params">req, res</span>) {
res.<span class="hljs-title function_">status</span>(<span class="hljs-number">200</span>).<span class="hljs-title function_">send</span>(<span class="hljs-string">'Hello, world!'</span>);
}
</code></pre><p>The vanilla Node analogue is a little more verbose because <code>writeHead</code> had no
explicit return (which means it returned <code>undefined</code>).</p>
<pre><code class="language-javascript"><span class="hljs-comment">// Vanilla Node.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">requestHandler</span>(<span class="hljs-params">req, res</span>) {
res.<span class="hljs-title function_">writeHead</span>(<span class="hljs-number">200</span>);
res.<span class="hljs-title function_">end</span>(<span class="hljs-string">'Hello, world!'</span>);
}
</code></pre><p>I find the repetition a little annoying, and frequently tried to chain the call
to <code>end</code> onto the call to <code>writeHead</code> by mistake.</p>
<p>My contribution was to make <code>writeHead</code> return the response object <code>res</code>, to
enable method chaining.</p>
<pre><code class="language-javascript"><span class="hljs-comment">// Node 11.10.0+</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">requestHandler</span>(<span class="hljs-params">req, res</span>) {
res.<span class="hljs-title function_">writeHead</span>(<span class="hljs-number">200</span>).<span class="hljs-title function_">end</span>(<span class="hljs-string">'Hello, world!'</span>);
}
</code></pre><p>This makes the vanilla Node API for server responses just a little more
friendly.</p>
https://qubyte.codes/blog/weeknotes-1Weeknotes #12019-03-03T14:00:00Z2019-03-03T14:00:00Zqubytehttps://qubyte.codes
<p>This is my first go at writing a weeknotes entry, and this week has been a busy
one!</p>
<p>On Monday the Brighton Rust User Group held it's first meeting since November
(it'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'm looking forward to seeing
more!</p>
<p>On the topic of Rust, I <a href="https://twitter.com/cori/status/1101133644196917253?s=20">discovered this week</a> that
<a href="https://glitch.com">glitch</a> can deploy Rust! If I find the time, I'll reimplement one or
two of my personal bots (which are hosted by glitch in Node.js) in Rust.</p>
<p>On Thursday Codebar Brighton held their
<a href="http://www.codebar.io/events/brighton-talks-1">first edition of Codebar Talks</a>. <a href="https://adactio.com">Jeremy Keith</a>
gave a presentation on metaphores we use as programmers both for ourselves and
the tools we use, and the implications of them (I'm keen on this topic).
<a href="http://katebeard.co/">Kate Beard</a> gave a presentation Morse Code and the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a>. I love this approach to using browser
technologies such as Web Audio API to reproduce old tech like Morse code or the
<a href="https://youtu.be/lQMcZtiaD0A">ZX Spectrum loading screen</a>.</p>
<p>In non-web news, I've been thinking about books and records. I have lots of
books. I'm thinking of donating the fiction ones, but some of the text books I'd
like to keep around and have handy when the whim to pick one up takes me.
There's a perfect little space in my home office to place a small book case, but
I don't want the usual sort of bookcase, I want one with angled shelves so you
can actually see what's on them while still being a short piece of furniture.</p>
<p>There's a mid-century modern design that caught my eye, but they're rare in the
UK (they appear to have been more popular in the US). Since I can't find one, I
figured I'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'll post pictures
here!</p>
https://qubyte.codes/blog/weeknotes-2Weeknotes #22019-03-17T23:45:11Z2019-03-17T23:45:11Zqubytehttps://qubyte.codes
<p>This edition really covers the last two weeks.</p>
<p>The highlight of last week was <a href="https://www.bytesconf.co.uk">Bytes Conf</a>. 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.</p>
<p>I redeemed my winning raffle ticket for £50 off a print from <a href="https://www.theprivatepress.org">The Private Press</a>. I put it towards a beautiful print of the Welbeck Street Car Park (a soon to be demolished brutalist multi-storey car park) by <a href="https://www.paulcatherall.com">Paul Catherall</a>. I can’t wait to get it framed.</p>
<p>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.</p>
<p>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 <em>could</em> 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.</p>
<p>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!</p>
https://qubyte.codes/blog/weeknotes-3Weeknotes #32019-03-24T23:10:00Z2019-03-24T23:10:00Zqubytehttps://qubyte.codes
<p>This week started off a little boring. On the Thursday though, the rebooted Brighton <a href="https://indieweb.org/Homebrew_Website_Club">Homebrew Website Club</a> met at <a href="https://clearleft.com/">Clearleft</a>. After catching up with the folk there, I worked on an experiment to theme this blog, with the theme determined by a toggle in <code>localStorage</code>. By placing an inline script in the head right after the stylesheet link tag, I demonstrated that it'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've not implemented anything just yet.</p>
<p>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 <em>dark mode Battenberg</em> 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've baked a red velvet cake in a tray, but it came out as a regular chocolate cake so I'm going to use more red colouring for the next try. I'll post a picture if I ever actually make it.</p>
<p>I'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 <a href="https://www.netlify.com/">Netlify</a>. It's good to know that this solution appeals to more folk than just me. It's even better to see more unique stuff on the web, away from the silos owned by giants like Facebook.</p>
<p>On Sunday evening I enhanced my <a href="https://glitch.com/edit/#!/tweet-new-blog-posts">new blog post tweet bot</a> to include tags. Each post in this blog includes tags, and each of these is a link to a tag page with a <a href="http://microformats.org/wiki/rel-tag"><code>rel="tag"</code></a> attribute. I updated the bot to download the new entry and select <code>rel="tag"</code> elements for their text content. The selected text is then appended to the tweet as hash-tags.</p>
<p>On the home life side of things, the little one had his third set of <a href="https://www.nhs.uk/conditions/vaccinations/childhood-vaccines-timeline/?tabname=babies-and-toddlers">vaccinations</a> 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're painful, and can lead to a fever. We've spent most of this weekend trying to keep him comfortable, the fever low, and the injection sites as pain free as possible. He'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.</p>
https://qubyte.codes/blog/the-code-for-this-blog-is-now-publicThe code for this blog is now public2019-03-28T19:26:08Z2019-03-28T19:26:08Zqubytehttps://qubyte.codes
<p>I made the source code for this site public! You can find it
<a href="https://github.com/qubyte/qubyte-codes/">here</a>. I've
<a href="/tags/aboutthisblog">written at length</a> about how I've built this, and having
the code makes it easier to point to particular lines. I hope it'll
inspire <em>you</em> to do the same!</p>
https://qubyte.codes/blog/how-i-schedule-posts-using-github-actionsHow I schedule posts using GitHub Actions2019-06-15T22:00:00Z2019-06-15T22:00:00Zqubytehttps://qubyte.codes
<p>In the past <a href="/blog/how-i-schedule-posts-using-atd">I used atd</a> to schedule the
publication of my blog posts. When I moved to Netlify I lost the ability to
schedule posts, and didn't think about it until
<a href="https://twitter.com/qubyte/status/1139904277894369281">a recent conversation on twitter</a> with <a href="https://remysharp.com/">Remy Sharp</a>. Remy asked
how to schedule blog posts for static sites and it got me thinking.</p>
<p>I've been using GitHub actions recently, and one workflow trigger it provides
is a <a href="https://developer.github.com/actions/managing-workflows/creating-and-cancelling-a-workflow/#scheduling-a-workflow">cron-like schedule</a>. I wrote a little npm script to read a
directory full of scheduled posts. Each post has its publication date checked,
and if it'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 <a href="https://developer.github.com/actions/creating-github-actions/accessing-the-runtime-environment/#environment-variables">actions environment</a> to make two requests,
one to recreate the file in the posts directory, and one to delete the scheduled
file using the GitHub <a href="https://developer.github.com/v3/repos/contents/">Contents API</a>.</p>
<p>Sadly, there's no way to <em>move</em> 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
<a href="https://developer.github.com/v3/git/trees/">Trees API</a>, but that's a good deal more involved and I don't mind
a little noise in the commit history.</p>
<p><del>You can see the workflow using this script <a href="https://github.com/qubyte/qubyte-codes/blob/master/.github/main.workflow">here</a>, and the publisher
<a href="https://github.com/qubyte/qubyte-codes/blob/master/publish-scheduled.js">here</a></del>. These scripts have been replaced since GitHub actions have
changed! See <a href="/blog/cleaner-scheduled-posts-publication">the later post</a> for updated specifics.</p>
<p>Of course, the scheduler published this post. ;)</p>
https://qubyte.codes/blog/a-new-service-to-handle-webmention-dispatch-for-youA new service to handle webmention dispatch for you2019-06-18T23:45:00Z2019-06-18T23:45:00Zqubytehttps://qubyte.codes
<p>I really like webmentions. They provide a way to let folk know that you're
writing about their blog posts. I see them as an alternative to comments which
encourages better discourse.</p>
<p>Webmentions can be a pain to manage though, especially if you have a statically
generated site like this. For receiving mentions there's the venerable
<a href="https://webmention.io">webmention.io</a> service. I use this to collect mentions
and then I check periodically and copy mentions over to the metadata of the
targeted post.</p>
<p>To dispatch webmentions I
<a href="https://glitch.com/edit/#!/send-webmentions">made a glitch</a>, which I've
<a href="/blog/about-this-blog-3">written about before</a>. It's closely coupled to the URL
structure of my blog and how it compiles webmentions into pages, so I'll stick
with it but it's not so useful for others except as a reference.</p>
<p>If you've just decided to include webmentions on your blog, then Remy Sharp
created <a href="https://webmention.app">webmention.app</a> to automate searching for links
and dispatching webmentions for a page. If I were starting now I'd use it! You
can read more about it <a href="https://remysharp.com/2019/06/18/send-outgoing-webmentions">in Remy's own words</a>.</p>
https://qubyte.codes/blog/cleaner-scheduled-posts-publicationCleaner scheduled posts publication2019-06-27T17:00:00Z2019-06-27T17:00:00Zqubytehttps://qubyte.codes
<p>In <a href="/blog/how-i-schedule-posts-using-github-actions">a previous post</a> I talked about an npm script I had written to be executed by a GitHub action:</p>
<blockquote>
<p>Sadly, there'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's a good deal more involved and I don't mind a little noise in the commit history.</p>
</blockquote>
<p>I was fibbing! The additional commit does bother me, so I decided to rewrite the publisher to avoid noise in the git history.</p>
<p>My next move was to actually try using the Trees API. It was mostly working before I read <a href="https://remysharp.com/2019/06/26/scheduled-and-draft-11ty-posts">Remy Sharp's post on the same topic</a>. Remy uses the metadata of a post to determine if it should be published or not, rather than the directory it resides in.</p>
<p>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.</p>
<p>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.</p>
<h2 id="update-2019-08-11">Update 2019-08-11</h2>
<p>GitHub actions have been revised and are now written in YAML. The <a href="https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/publish-scheduled-posts.yml">workflow</a> which handles publications has been updated accordingly, and the publisher script <a href="https://github.com/qubyte/qubyte-codes/blob/main/scripts/publish-scheduled.js">moved to a scripts folder</a> to help keep the project directory clean.</p>
https://qubyte.codes/blog/updating-webmention-dispatchUpdating webmention dispatch2019-07-04T18:00:00Z2019-07-04T18:00:00Zqubytehttps://qubyte.codes
<p>I mentioned <a href="https://webmention.app">a new service which handles webmentions</a> in a
<a href="/blog/a-new-service-to-handle-webmention-dispatch-for-you">previous post</a>. I decided to replace
<a href="https://glitch.com/~send-webmentions">the glitch I've been using</a> for
<a href="https://glitch.com/~lean-send-webmentions">one which is much leaner</a>. It uses
<a href="https://github.com/remy/wm">the library which powers webmention.app</a> 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.</p>
<p>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.</p>
<p>The new glitch keeps a list of posts it'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't have any mentions yet. It'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 <a href="https://glitch.com/~lean-send-webmentions">take a look</a>!</p>
<p>P.S. All the glitches I use to add functionality to my blog can be found
<a href="https://glitch.com/@qubyte/qubyte-codes">here</a>.</p>
https://qubyte.codes/blog/dark-modeDark mode2019-10-12T01:30:00Z2019-10-12T01:30:00Zqubytehttps://qubyte.codes
<p>That's right! After more than a year of talking about adding a dark mode I
finally did it. The wider support for
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"><code>prefers-color-scheme</code></a> is what pushed me over the edge.
I'm also a slave to fashion.</p>
<p>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'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.</p>
<p>The additions to the CSS aren't too dramatic, but I learnt a few things along
the way</p>
<pre><code class="language-css"><span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-color-scheme</span>: dark) {
<span class="hljs-selector-pseudo">:root</span> {
<span class="hljs-attr">--background-color-main</span>: <span class="hljs-number">#000</span>;
<span class="hljs-attr">--background-color-alt</span>: <span class="hljs-number">#000</span>;
<span class="hljs-attr">--standout-color-main</span>: <span class="hljs-built_in">hsl</span>(
<span class="hljs-built_in">var</span>(--base-background-hue),
<span class="hljs-built_in">var</span>(--base-background-sat),
<span class="hljs-built_in">var</span>(--base-background-lum)
);
<span class="hljs-attr">--standout-color-alt</span>: <span class="hljs-built_in">hsl</span>(
<span class="hljs-built_in">calc</span>(<span class="hljs-built_in">var</span>(--base-background-hue) - <span class="hljs-number">30</span>),
<span class="hljs-built_in">var</span>(--base-background-sat),
<span class="hljs-built_in">var</span>(--base-background-lum)
);
}
<span class="hljs-selector-tag">img</span><span class="hljs-selector-pseudo">:not</span>(<span class="hljs-selector-attr">[src*=<span class="hljs-string">".svg"</span>]</span>) {
<span class="hljs-attribute">filter</span>: <span class="hljs-built_in">brightness</span>(.<span class="hljs-number">8</span>) <span class="hljs-built_in">contrast</span>(<span class="hljs-number">1.2</span>);
}
pre, <span class="hljs-selector-tag">input</span>, <span class="hljs-selector-tag">button</span> {
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#000</span>;
}
<span class="hljs-selector-tag">code</span> {
<span class="hljs-attribute">filter</span>: <span class="hljs-built_in">invert</span>();
}
}
<span class="hljs-selector-tag">body</span> > <span class="hljs-selector-tag">header</span> {
<span class="hljs-attribute">z-index</span>: <span class="hljs-number">1</span>; <span class="hljs-comment">/* works around stacking context issue introduced by filters */</span>
}
</code></pre><p>The media query applies the content only when the OS is in dark mode and the
browser supports the media query. The <code>:root</code> swaps the background colors to
standout colours, and makes the background black.</p>
<p>Non-SVG images have their brightness and contrast adjusted with a filter. I
borrowed a dark mode filter from <a href="https://melanie-richards.com/">Melanie Richards</a> as
<a href="https://adactio.com/journal/15941">suggested by Jeremy Keith</a>.</p>
<p>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.</p>
<p>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 <code>z-index</code>. While
the rule of thumb seems to be to avoid use of <code>z-index</code>, I believe it's
appropriate in this case.</p>
https://qubyte.codes/blog/indiewebcamp-brighton-2019IndieWebCamp Brighton 20192019-10-21T16:20:00Z2019-10-21T16:20:00Zqubytehttps://qubyte.codes
<p>I had a great time last weekend at IndieWebCamp (IWC) Brighton. The first day
was <a href="https://indieweb.org/2019/Brighton/Schedule">filled with discussions</a> on various IndieWeb related topics. I
attended discussions on:</p>
<ul>
<li>How IndieWebCamp should respond to the climate crisis.</li>
<li>Storage and display of personal tracking data.</li>
<li><em>Local first</em> web and how to define the term.</li>
<li>License detection.</li>
<li>Integrating Apple shortcuts (scripts) into your IndieWeb workflows.</li>
</ul>
<p>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.</p>
<p>Storage and display of tracking data was interesting, but perhaps less relevant
to me. I'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.</p>
<p>The local first discussion was one I was note taking for. I'm using my old
MacBook Air since I just finished a job and haven't started my new one yet, so
I'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. <a href="https://adactio.com">Jeremy Keith</a>
mentioned <a href="https://developer.mozilla.org/en-US/docs/Web/API/SyncManager">background sync</a> as an emerging option to do this (I
must look into this).</p>
<p>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'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.</p>
<p>The final session on the day was about integrating micropub with Apple
shortcuts. I found this extremely interesting. I'd considered it in passing, but
<a href="https://rosemaryorchard.com/">Rosemary Orchard</a> had a comprehensive set of actions to automate
IndieAuth and posting to <a href="https://indieweb.org/Micropub">micropub</a> endpoints. This would be the
inspiration for my hack day work.</p>
<p>The second day was for hacking on your own stuff, and helping each other out
when possible. My <a href="https://nodejs.org/">Node.js</a> knowledge came in handy a few times so I was
glad to be helpful!</p>
<p>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
<a href="https://glitch.com/~micropub-server">my micropub endpoint</a>, 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 <a href="https://developer.github.com/v3/repos/contents/">GitHub contents API</a> 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.</p>
<p>This made it possible to post likes and replies using existing micropub clients.
I use <a href="https://omnibear.com/">Omnibear</a> 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're used in. What I implemented is fairly primitive. The
shortcuts only use the URL of the current page. There's no elegant way to get
hold of the title of a page like Omnibear can, but I'll work on this later as an
enhancement.</p>
<p>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 <code>short-circuit-auth</code> 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.</p>
<p>I'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.</p>
<p>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'll be able to post pictures and not just
walls of text!</p>
https://qubyte.codes/blog/ffconf-2019ffconf 20192019-11-15T13:16:33Z2019-11-15T13:16:33Zqubytehttps://qubyte.codes
<p>The day was packed with interesting and diverse talks, and there's just too much
to talk about in a single post. It's definitely worth searching around for other
blog posts to see other's takes on the day!</p>
<p>The talk which I find myself thinking about the most was
<a href="https://charlottedann.com/">Charlotte Dann's</a>. The journey from exploration in generative
art to producing <a href="https://hexatope.io/">custom real-world jewelery</a> was inspiring! It seems
to have inspired others too, since it precipitated the creation of
<a href="https://mobile.twitter.com/GeneratorBtn">a creative coding meetup</a> here in brighton, which I hope to find
some time to get involved with.</p>
<p>In the spirit of that talk, here's a quick Conway's Game of Life demo (requires
JavaScript to be enabled). It loops around (so the world looks square but it's
really a torus). It's pretty basic! In the spirit of the talk, I hope to apply
similar rules to different tilings. Refresh to restart.</p>
https://qubyte.codes/blog/generative-art-piece-domains-of-points-with-spokesGenerative art piece: domains of points with spokes2019-11-30T21:15:00Z2019-11-30T21:15:00Zqubytehttps://qubyte.codes
<p>The graphic below is generated randomly and rendered as an SVG. Occasionally it
glitches, but that's all part of the fun! It was inspired by a graphic seen in
<a href="https://charlottedann.com/">Charlotte Dann's</a> <a href="https://www.youtube.com/watch?v=BZNKLvqh8ts&list=PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG">ffconf 2019 talk</a> at about <a href="https://www.youtube.com/watch?v=BZNKLvqh8ts&list=PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&t=385">6:25</a>
in. See <a href="/blog/the-maths-of-domains-of-points-with-spokes">the next post</a> for a description of the maths I used to do this.</p>
https://qubyte.codes/blog/the-maths-of-domains-of-points-with-spokesThe maths of Domains of points with spokes2019-12-02T00:00:00Z2019-12-02T00:00:00Zqubytehttps://qubyte.codes
<p>It's been a while since <a href="/blog/advent-of-code-2017-day-20-task-2">my last maths heavy article</a>. I
enjoy writing these but struggle to find the time to write many.</p>
<p>This article is on the generative art piece in <a href="/blog/generative-art-piece-domains-of-points-with-spokes">my last post</a>. I based
this on my memory of an image of a piece in <a href="https://www.youtube.com/watch?v=BZNKLvqh8ts&list=PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&t">a talk by</a>
<a href="https://charlottedann.com/">Charlotte Dann</a>. 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).</p>
<p>In hindsight, I should have realized that the domains around points are like a
<a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi diagram</a>. I forged ahead having forgotten all about them, so
please excuse my unorthodox terminology below. Looking back on <a href="https://www.youtube.com/watch?v=BZNKLvqh8ts&list=PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&t">the talk</a>
that inspired this, my memory of the drawing wasn't quite correct. See the real
deal at about <a href="https://www.youtube.com/watch?v=BZNKLvqh8ts&list=PLXmT1r4krsTrR6khetJSVQqulyFbxmZNG&t=385">6:25</a> in.</p>
<p>Based on my memory, the image was of a number of points. From each point lines
(I'll call these spokes) radiated outwards until they reached the edge of the
domain of a point. The "domain" 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.</p>
<p>The case of neighbouring points looks like:</p>
<div class="diagram">
<svg role="img" aria-labelledby="diagram-title-1" width="200" height="200" viewBox="0 0 100 100">
<title id="diagram-title-1">Spokes radiating from a point to the halfway line between the point and another point.</title>
<circle cx="5" cy="50" r="2"/>
<circle cx="95" cy="50" r="2"/>
<path d="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"/>
</svg>
</div>
<p>I'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.</p>
<div class="diagram">
<svg role="img" aria-labelledby="diagram-title-2" width="200" height="200" viewBox="0 0 100 100">
<title id="diagram-title-2">The boundary between two points.</title>
<circle cx="5" cy="50" r="2"/>
<circle cx="95" cy="50" r="2"/>
<path d="M 50 0 v 100" stroke-dasharray="5, 5"/>
</svg>
</div>
<p>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!</p>
<p>The equation of the boundary line is related to the equation of a line between
the two points, which is:</p>
<math display="block" class="tml-display" id="equation-1" aria-label="y(x) - y_0 = \frac{x_0 - x_1}{y_0 - y_1} (x - x_0)"><semantics><mrow><mi>y</mi><mo form="prefix" stretchy="false">(</mo><mi>x</mi><mo form="postfix" stretchy="false">)</mo><mo>−</mo><msub><mi>y</mi><mn>0</mn></msub><mo>=</mo><mfrac><mrow><msub><mi>x</mi><mn>0</mn></msub><mo>−</mo><msub><mi>x</mi><mn>1</mn></msub></mrow><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>−</mo><msub><mi>y</mi><mn>1</mn></msub></mrow></mfrac><mo form="prefix" stretchy="false">(</mo><mi>x</mi><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub><mo form="postfix" stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y(x) - y_0 = \frac{x_0 - x_1}{y_0 - y_1} (x - x_0)</annotation></semantics></math><p>Where the numeric subscripts <math><mn>0</mn></math> and <math><mn>1</mn></math> are for the two points, and we're
interested in the spokes radiating from point <math><mn>0</mn></math>.</p>
<p>Any two fixed points on the line can be used to place the line. For example,
it's just as valid to use the coordinates of the second point (note the
subscripts):</p>
<math display="block" class="tml-display" id="equation-2" aria-label="y(x) - y_1 = \frac{x_0 - x_1}{y_0 - y_1} (x - x_1)"><semantics><mrow><mi>y</mi><mo form="prefix" stretchy="false">(</mo><mi>x</mi><mo form="postfix" stretchy="false">)</mo><mo>−</mo><msub><mi>y</mi><mn>1</mn></msub><mo>=</mo><mfrac><mrow><msub><mi>x</mi><mn>0</mn></msub><mo>−</mo><msub><mi>x</mi><mn>1</mn></msub></mrow><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>−</mo><msub><mi>y</mi><mn>1</mn></msub></mrow></mfrac><mo form="prefix" stretchy="false">(</mo><mi>x</mi><mo>−</mo><msub><mi>x</mi><mn>1</mn></msub><mo form="postfix" stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y(x) - y_1 = \frac{x_0 - x_1}{y_0 - y_1} (x - x_1)</annotation></semantics></math><p>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:</p>
<math display="block" class="tml-display" id="equation-3" aria-label="\left(\frac{x_0 + x_1}{2}, \frac{y_0 + y_1}{2}\right)"><semantics><mrow><mo fence="true" form="prefix">(</mo><mfrac><mrow><msub><mi>x</mi><mn>0</mn></msub><mo>+</mo><msub><mi>x</mi><mn>1</mn></msub></mrow><mn>2</mn></mfrac><mo separator="true">,</mo><mfrac><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>+</mo><msub><mi>y</mi><mn>1</mn></msub></mrow><mn>2</mn></mfrac><mo fence="true" form="postfix">)</mo></mrow><annotation encoding="application/x-tex">\left(\frac{x_0 + x_1}{2}, \frac{y_0 + y_1}{2}\right)</annotation></semantics></math><p>The slope is perpendicular to the slope of the point-to-point line:</p>
<math display="block" class="tml-display" id="equation-4" aria-label="\frac{x_1 - x_0}{y_0 - y_1}"><semantics><mfrac><mrow><msub><mi>x</mi><mn>1</mn></msub><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub></mrow><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>−</mo><msub><mi>y</mi><mn>1</mn></msub></mrow></mfrac><annotation encoding="application/x-tex">\frac{x_1 - x_0}{y_0 - y_1}</annotation></semantics></math><p>Putting these together and rearranging gave me the equation for a boundary
between two points:</p>
<math display="block" class="tml-display" id="equation-5" aria-label="y(x) = \frac{x_1 - x_0}{y_0 - y_1}\left(x - \frac{x_0 + x_1}{2}\right) + \frac{y_0 + y_1}{2}"><semantics><mrow><mi>y</mi><mo form="prefix" stretchy="false">(</mo><mi>x</mi><mo form="postfix" stretchy="false">)</mo><mo>=</mo><mfrac><mrow><msub><mi>x</mi><mn>1</mn></msub><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub></mrow><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>−</mo><msub><mi>y</mi><mn>1</mn></msub></mrow></mfrac><mrow><mo fence="true" form="prefix">(</mo><mi>x</mi><mo>−</mo><mfrac><mrow><msub><mi>x</mi><mn>0</mn></msub><mo>+</mo><msub><mi>x</mi><mn>1</mn></msub></mrow><mn>2</mn></mfrac><mo fence="true" form="postfix">)</mo></mrow><mo>+</mo><mfrac><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>+</mo><msub><mi>y</mi><mn>1</mn></msub></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">y(x) = \frac{x_1 - x_0}{y_0 - y_1}\left(x - \frac{x_0 + x_1}{2}\right) + \frac{y_0 + y_1}{2}</annotation></semantics></math><p>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.</p>
<p>The equation for the boundary line is in Cartesian coordinates <math><mrow><mo form="prefix" stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo form="postfix" stretchy="false">)</mo></mrow></math>, but
given that we have an angle, and we're looking for a length, it's more natural
to think of a spoke in polar coordinates <math><mrow><mo form="prefix" stretchy="false">(</mo><mi>r</mi><mo separator="true">,</mo><mpadded lspace="0"><mi mathvariant="normal">Θ</mi></mpadded><mo form="postfix" stretchy="false">)</mo></mrow></math>. In fact, taking the point as
the origin, <math><mi>r</mi></math> is the length we're actually looking for.</p>
<p>A line in polar coordinates can be connected to Cartesian coordinates using the
following equations:</p>
<math display="block" class="tml-display" id="equation-6" aria-label="\begin{align*}
x - x_0 &= r \cos \theta\\
y - y_0 &= r \sin \theta
\end{align*}"><semantics><mtable displaystyle="true" columnalign="right left" class="tml-jot"><mtr><mtd class="tml-right"><mrow><mi>x</mi><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub></mrow></mtd><mtd class="tml-left"><mrow><mo>=</mo><mi>r</mi><mrow><mspace width="0.1667em"></mspace><mi>cos</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi></mrow></mtd></mtr><mtr><mtd class="tml-right"><mrow><mi>y</mi><mo>−</mo><msub><mi>y</mi><mn>0</mn></msub></mrow></mtd><mtd class="tml-left"><mrow><mo>=</mo><mi>r</mi><mrow><mspace width="0.1667em"></mspace><mi>sin</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi></mrow></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
x - x_0 &= r \cos \theta\\
y - y_0 &= r \sin \theta
\end{align*}</annotation></semantics></math><p>Substituting these into the boundary equation gave:</p>
<math display="block" class="tml-display" id="equation-7" aria-label="r \sin \theta + y_0 = \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}"><semantics><mrow><mi>r</mi><mrow><mspace width="0.1667em"></mspace><mi>sin</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi><mo>+</mo><msub><mi>y</mi><mn>0</mn></msub><mo>=</mo><mfrac><mrow><msub><mi>x</mi><mn>1</mn></msub><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub></mrow><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>−</mo><msub><mi>y</mi><mn>1</mn></msub></mrow></mfrac><mrow><mo fence="true" form="prefix">(</mo><mi>r</mi><mrow><mspace width="0.1667em"></mspace><mi>cos</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi><mo>+</mo><mfrac><mrow><msub><mi>x</mi><mn>0</mn></msub><mo>−</mo><msub><mi>x</mi><mn>1</mn></msub></mrow><mn>2</mn></mfrac><mo fence="true" form="postfix">)</mo></mrow><mo>+</mo><mfrac><mrow><msub><mi>y</mi><mn>0</mn></msub><mo>+</mo><msub><mi>y</mi><mn>1</mn></msub></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">r \sin \theta + y_0 = \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}</annotation></semantics></math><p>After some rearranging, the solution for <math><mi>r</mi></math> looks like:</p>
<math display="block" class="tml-display" id="equation-8" aria-label="r = \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}"><semantics><mrow><mi>r</mi><mo>=</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mo>⋅</mo><mfrac><mrow><mo form="prefix" stretchy="false" lspace="0em" rspace="0em">(</mo><msub><mi>y</mi><mn>1</mn></msub><mo>−</mo><msub><mi>y</mi><mn>0</mn></msub><msup><mo form="postfix" stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><mo form="prefix" stretchy="false">(</mo><msub><mi>x</mi><mn>1</mn></msub><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub><msup><mo form="postfix" stretchy="false">)</mo><mn>2</mn></msup></mrow><mrow><mo form="prefix" stretchy="false" lspace="0em" rspace="0em">(</mo><msub><mi>y</mi><mn>1</mn></msub><mo>−</mo><msub><mi>y</mi><mn>0</mn></msub><mo form="postfix" stretchy="false">)</mo><mrow><mspace width="0.1667em"></mspace><mi>sin</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi><mo>+</mo><mo form="prefix" stretchy="false">(</mo><msub><mi>x</mi><mn>1</mn></msub><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub><mo form="postfix" stretchy="false">)</mo><mrow><mspace width="0.1667em"></mspace><mi>cos</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">r = \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}</annotation></semantics></math><p>Since the equations for the bounding box edges are more simple:</p>
<math display="block" class="tml-display" id="equation-9" aria-label="\begin{align*}
x &= 0\\
x &= x_\text{max}\\
y &= 0\\
y &= y_\text{max}
\end{align*}"><semantics><mtable displaystyle="true" columnalign="right left" class="tml-jot"><mtr><mtd class="tml-right"><mi>x</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mn>0</mn></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>x</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><msub><mi>x</mi><mtext>max</mtext></msub></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>y</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mn>0</mn></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>y</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><msub><mi>y</mi><mtext>max</mtext></msub></mrow></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
x &= 0\\
x &= x_\text{max}\\
y &= 0\\
y &= y_\text{max}
\end{align*}</annotation></semantics></math><p>The solutions for r with these bounding box equations are a little simpler too:</p>
<math display="block" class="tml-display" id="equation-10" aria-label="\begin{align*}
r &= \frac{-x_0}{\cos\theta}\\
r &= \frac{x_\text{max} - x_0}{\cos\theta}\\
r &= \frac{-y_0}{\sin\theta}\\
r &= \frac{y_\text{max} - y_0}{\sin\theta}
\end{align*}"><semantics><mtable displaystyle="true" columnalign="right left" class="tml-jot"><mtr><mtd class="tml-right"><mi>r</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mfrac><mrow><mo lspace="0em" rspace="0em">−</mo><msub><mi>x</mi><mn>0</mn></msub></mrow><mrow><mrow><mi>cos</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi></mrow></mfrac></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>r</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mfrac><mrow><msub><mi>x</mi><mtext>max</mtext></msub><mo>−</mo><msub><mi>x</mi><mn>0</mn></msub></mrow><mrow><mrow><mi>cos</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi></mrow></mfrac></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>r</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mfrac><mrow><mo lspace="0em" rspace="0em">−</mo><msub><mi>y</mi><mn>0</mn></msub></mrow><mrow><mrow><mi>sin</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi></mrow></mfrac></mrow></mtd></mtr><mtr><mtd class="tml-right"><mi>r</mi></mtd><mtd class="tml-left"><mrow><mo>=</mo><mfrac><mrow><msub><mi>y</mi><mtext>max</mtext></msub><mo>−</mo><msub><mi>y</mi><mn>0</mn></msub></mrow><mrow><mrow><mi>sin</mi><mo></mo><mspace width="0.1667em"></mspace></mrow><mi>θ</mi></mrow></mfrac></mrow></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
r &= \frac{-x_0}{\cos\theta}\\
r &= \frac{x_\text{max} - x_0}{\cos\theta}\\
r &= \frac{-y_0}{\sin\theta}\\
r &= \frac{y_\text{max} - y_0}{\sin\theta}
\end{align*}</annotation></semantics></math><p>Given all these equations, for each spoke radiating from each point we need to
calculate <math><mi>r</mi></math> for boundaries with every other point and also the bounding box.
Spokes radiate <em>away</em> from a point, so all negative values of <math><mi>r</mi></math> can be
discarded. Of the remaining possible values of <math><mi>r</mi></math>, the shortest wins!</p>
<p>I settled on using 10 randomly placed points, each with 48 evenly spaced spokes.
I'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.</p>
<p>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 <a href="ffconf-2019">Game of Life</a> demo in
a previous post.</p>
https://qubyte.codes/blog/generative-art-piece-bubblesGenerative art piece: bubbles2020-06-17T22:00:00Z2020-06-17T22:00:00Zqubytehttps://qubyte.codes
<p>An experiment into generating SVG circles which don't overlap. It was timeboxed to an hour,
so it's a little rough around the edges (but I think that adds to its charm).</p>
https://qubyte.codes/blog/superimposed-triangular-gridssuperimposed triangular grids2020-08-21T16:30:00Z2020-08-21T16:30:00Zqubytehttps://qubyte.codes
<p>This experiment was inspired by field of <a href="https://en.wikipedia.org/wiki/Twistronics"><em>twistronics</em></a>, the study
of the intersesting properties of misaligned graphene sheets. The misalignment
produces a <a href="https://en.wikipedia.org/wiki/Moir%C3%A9_pattern">moiré pattern</a> which echoes the underlying structure. I'm too
lazy to do hexagonal grids in an afternoon of tinkering so I used triangular
grids instead.</p>
https://qubyte.codes/blog/superimposed-hexagonal-gridssuperimposed hexagonal grids2020-08-22T13:30:00Z2020-08-22T13:30:00Zqubytehttps://qubyte.codes
<p>Based on the <a href="/blog/superimposed-triangular-grids">last experiment</a>, this one uses hexagonal grids rather than
triangular ones. The way the SVG is constructed isn't pretty (a mish-mash of
paths) but it gets the job done. I've given the two grids red and blue lines
and a black background to make the moiré pattern stand out.</p>
https://qubyte.codes/blog/custom-markdown-blocks-with-markedCustom markdown blocks with marked2020-10-09T21:45:00Z2020-10-09T21:45:00Zqubytehttps://qubyte.codes
<p>I use <a href="https://marked.js.org"><code>marked</code></a> 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!</p>
<p>I'll be using mathematics (<a href="https://tug.org/">TeX</a>) blocks for my example. The <code>marked</code> setup
code looks like this:</p>
<pre><code class="language-javascript">marked.<span class="hljs-title function_">use</span>({
<span class="hljs-title function_">walkTokens</span>(<span class="hljs-params">token</span>) {
<span class="hljs-keyword">const</span> { type, raw } = token;
<span class="hljs-comment">// Modify paragraph blocks beginning and ending with $$.</span>
<span class="hljs-keyword">if</span> (type === <span class="hljs-string">'paragraph'</span> && raw.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'$$\n'</span>) && raw.<span class="hljs-title function_">endsWith</span>(<span class="hljs-string">'\n$$'</span>)) {
token.<span class="hljs-property">type</span> = <span class="hljs-string">'code'</span>;
token.<span class="hljs-property">lang</span> = <span class="hljs-string">'mathematics'</span>;
token.<span class="hljs-property">text</span> = token.<span class="hljs-property">raw</span>.<span class="hljs-title function_">slice</span>(<span class="hljs-number">3</span>, -<span class="hljs-number">3</span>); <span class="hljs-comment">// Remove the $$ boundaries.</span>
token.<span class="hljs-property">tokens</span>.<span class="hljs-property">length</span> = <span class="hljs-number">0</span>; <span class="hljs-comment">// Remove child tokens.</span>
}
},
<span class="hljs-attr">renderer</span>: {
<span class="hljs-title function_">code</span>(<span class="hljs-params">code, language</span>) {
<span class="hljs-comment">// Use custom mathematics renderer.</span>
<span class="hljs-keyword">if</span> (language === <span class="hljs-string">'mathematics'</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-title function_">renderMathematics</span>(code);
}
<span class="hljs-comment">// Use default code renderer.</span>
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
}
});
</code></pre><p>I'm passing two things to <code>marked</code> to configure it here. The first is a
<a href="https://marked.js.org/using_pro#walk-tokens">token walker function</a>, 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's sort of depth-first).</p>
<p>The idea is for blocks of text with a <code>$$</code> 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:</p>
<pre><code class="language-markdown">Some example text with some mathematics to render below:
$$
a^2 = b^2 + c^2
$$
Some example text below.
</code></pre><p>It's a common extension to place LaTeX code inside <code>$$</code> delimited blocks.
Even if you're not familiar, the dollar symbols above and below are a little
like a fenced code block. To <code>marked</code> the block looks like a paragraph. This
means that the token walker will receive some paragraph tokens which need to be
modified.</p>
<p>To know the difference, paragraph tokens are checked by the token walker to see
if they begin with <code>$$\n</code> and end with <code>\n$$</code>. When they do, the block is
modified to look like a code block with a special <code>'markdown'</code> language. Child
tokens are removed because the content shouldn't be treated as markdown, and the
<code>text</code> property is set by snipping the leading and trailing dollar symbols and
newlines off.</p>
<p>The second part of this trick is in the renderer option. The renderer for code
blocks is modified with special handling for the <code>'mathematics'</code> language. The
code parameter received by it is the text we set on the token, so it's ready to
be rendered to mathematics. The rendering itself is beyond the scope of this
post, but I use <a href="https://www.mathjax.org/">MathJax</a>. When code is of any other language the
custom code renderer returns <code>false</code> to instruct <code>marked</code> to use the default
code rendering behaviour.</p>
https://qubyte.codes/blog/dispatching-webmentions-with-a-netlify-build-pluginDispatching Webmentions with a Netlify build plugin2021-02-27T15:45:00Z2023-02-20T23:23:00Zqubytehttps://qubyte.codes
<p>This site uses a static site generator to build plain HTML pages. Since there's
no database to add, update, or delete pages from, determining when to dispatch
mentions can be challenging! Here's how I use a Netlify
<a href="https://docs.netlify.com/configure-builds/build-plugins/create-plugins/">build plugin</a> and an <a href="https://tools.ietf.org/html/rfc5023">atom feed</a> to manage it.</p>
<p>The <a href="https://www.w3.org/TR/webmention/">Webmention spec</a> 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's up to them to determine what changed.</p>
<p>Netlify provides hooks for various stages of the build, and a plugin may use as
many as it needs. I use the <code>onPostBuild</code> and <code>onSuccess</code> hooks. The former is
called when the build is complete, but <em>before</em> 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 <code>onSuccess</code> hook. This hook fires when the site is
deployed. It'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.</p>
<p>If you prefer to read code, <a href="https://github.com/qubyte/qubyte-codes/tree/main/plugins/dispatch-webmentions/index.js">check it out here</a>.</p>
<p>This is just the beginning! I plan to port other capabilities over to Netlify
build plugins in time.</p>
https://qubyte.codes/blog/tip-connecting-to-localstack-s3-using-the-javascript-aws-sdk-v3Tip: Connecting to localstack S3 using the JavaScript AWS SDK v32021-03-26T18:30:51Z2021-03-26T18:30:51Zqubytehttps://qubyte.codes
<p>I had some issues getting the <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/index.html">v3 AWS SDK for JavaScript</a> to communicate
with localstack S3, but I found a solution! With the <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/">V2 JS SDK</a>, the
configuration object for the S3 client looks like:</p>
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"region"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"eu-west-1"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"endpoint"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"http://localhost:4566"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"s3ForcePathStyle"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
<span class="hljs-punctuation">}</span>
</code></pre><p>The last field tells the sdk not to use <code><bucket>.hostname</code> 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 <code><bucket>.localhost</code>. This'll
work when managing buckets, but not when dealing with their contents (unless you
add an entry to your <code>/etc/hosts</code> file, which often isn't practical).</p>
<p>Unfortunately this field doesn't exist for v3, and after searching I didn't find
all that much except for a couple of mentions of the v3 JS SDK no longer
supporting it.</p>
<p>Fortunately it's not actually the case. It was just renamed to <code>forcePathStyle</code>!</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> { S3Client } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@aws-sdk/client-s3'</span>);
<span class="hljs-keyword">const</span> s3 = <span class="hljs-keyword">new</span> <span class="hljs-title function_">S3Client</span>({
<span class="hljs-attr">region</span>: <span class="hljs-string">'eu-west-1'</span>, <span class="hljs-comment">// The value here doesn't matter.</span>
<span class="hljs-attr">endpoint</span>: <span class="hljs-string">'http://localhost:4566'</span>, <span class="hljs-comment">// This is the localstack EDGE_PORT</span>
<span class="hljs-attr">forcePathStyle</span>: <span class="hljs-literal">true</span>
});
</code></pre><p>For reference, I boot localstack with this <a href="https://docs.docker.com/compose/">docker-compose</a>
configuration:</p>
<pre><code class="language-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.9"</span>
<span class="hljs-attr">services:</span>
<span class="hljs-attr">mock-s3:</span>
<span class="hljs-attr">image:</span> <span class="hljs-string">localstack/localstack:latest</span>
<span class="hljs-attr">ports:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">"4566-4583:4566-4583"</span>
<span class="hljs-attr">environment:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">AWS_DEFAULT_REGION=eu-west-1</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">EDGE_PORT=4566</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">SERVICES=s3</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">DEBUG=1</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">DATA_DIR=/tmp/localstack/data</span>
<span class="hljs-attr">volumes:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">"./.localstack:/tmp/localstack"</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">"/var/run/docker.sock:/var/run/docker.sock"</span>
</code></pre>
https://qubyte.codes/blog/pastel-migraine-aurasPastel migraine auras2021-10-14T08:10:00Z2021-10-14T08:10:00Zqubytehttps://qubyte.codes
<p>A generative patchwork of pastel colours. The colours begin with a randomly
picked colour in <a href="https://www.w3.org/TR/css-color-4/#lab-colors">LCH</a>. Other
colours are hue rotations in LCH space so that they'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
<a href="https://github.com/d3/d3-color">d3-color</a>). Refreshing generates a new
patchwork.</p>
https://qubyte.codes/blog/procedural-christmas-cardsProcedural Christmas cards2021-12-07T16:01:05Z2021-12-07T16:01:05Zqubytehttps://qubyte.codes
<p>Below is a procedural snowflake. I'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 <code>?seed=1234567890</code> in the URL bar. That will be the
random seed which generated your snowflake (and it'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!</p>
<p>The red colour is a convenience to make it show up in the software I use to
cut card.</p>
https://qubyte.codes/blog/marqdownMarqdown2022-05-15T09:55:00Z2023-06-19T23:20:00Zqubytehttps://qubyte.codes
<p>Markdown is the standard for writing in techie circles these days, but
it's pretty minimal. For a readme it'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 <em>deliberately</em> limited, so it's no fault of the language or its
creator!</p>
<p>Nevertheless, over time I've added my own tweaks and extensions upon Markdown,
so I've decided to document them, and name the dialect Marqdown. Naming may seem
a little arrogant, but it's mostly to disambiguate what I'm writing with more
common Markdown variants.</p>
<p>My variant is based on the default configuration provided by <a href="https://marked.js.org/">marked</a>, 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'll document them
in this post.</p>
<h2 id="footnotes">Footnotes</h2>
<p>I use footnotes<sup class="footnote-ref"><a id="footnote-ref-1" href="#footnote-1">[1]</a></sup> from time to time. The way I'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.</p>
<p>The footnote in the previous paragraph is encoded like this:</p>
<pre><code class="language-plaintext">I use footnotes[^][sparingly] from time to time.
</code></pre><p>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's at least one it appends a section with an ordered list of
footnotes. Another complication is index pages. For the blog posts
<a href="/blog">index page</a> only the first paragraph of each post is used, and footnote
superscripts have to be removed from those.</p>
<h2 id="languages">Languages</h2>
<p>HTML supports language attributes. Most of the time a (well-built) page will
have a single language attribute on the opening <code><html></code> tag itself, setting
the language for the entire document.</p>
<p>I write notes in mixed English and Japanese as I learn the latter. When working
with CJK text it's particularly important to give elements containing such text
the appropriate language tag so that Chinese characters are
<a href="https://heistak.github.io/your-code-displays-japanese-wrong/">properly rendered</a> (there are divergences which are important).</p>
<p>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:</p>
<pre><code class="language-plaintext">The text between here {ja:今日は} and here is in Japanese.
</code></pre><p>This snippet renders to:</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">p</span>></span>
The text between here <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ja"</span>></span>今日は<span class="hljs-tag"></<span class="hljs-name">span</span>></span> and here is in Japanese.
<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
</code></pre><p>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:</p>
<pre><code class="language-plaintext">- English
- {ja:日本語}
- English
</code></pre><p>migrates the language attribute to the parent <code><li></code> to eliminate a span:</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">ul</span>></span>
<span class="hljs-tag"><<span class="hljs-name">li</span>></span>English<span class="hljs-tag"></<span class="hljs-name">li</span>></span>
<span class="hljs-tag"><<span class="hljs-name">li</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ja"</span>></span>日本語<span class="hljs-tag"></<span class="hljs-name">li</span>></span>
<span class="hljs-tag"><<span class="hljs-name">li</span>></span>English<span class="hljs-tag"></<span class="hljs-name">li</span>></span>
<span class="hljs-tag"></<span class="hljs-name">ul</span>></span>
</code></pre><p>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:</p>
<pre><code class="language-plaintext">{ja:_すごい_}
</code></pre><p>migrates the language attribute to the spans only child, an <code><em></code>:</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">em</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ja"</span>></span>すごい<span class="hljs-tag"></<span class="hljs-name">em</span>></span>
</code></pre><p>This becomes particularly important in the case of my notes, where it's common
to nest ruby elements inside these language wrappers. There's a ruby annotation
in the next section, and you'll see the language attributes appear directly
on the ruby element if you inspect it.</p>
<p>As with footnotes, the language attribute migration and span elimination is
handled using JSDOM <em>after</em> 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.</p>
<h2 id="ruby-annotations">Ruby annotations</h2>
<p>I'm studying Japanese. It'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's also common to see it for less
common words, or where the reading of a word may be ambiguous.</p>
<p>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're called
<a href="https://en.wikipedia.org/wiki/Furigana"><ruby lang="ja">振<rp>(</rp><rt>ふ</rt><rp>)</rp>り<rt></rt>仮名<rp>(</rp><rt>がな</rt><rp>)</rp></ruby> (furigana)</a>, and you can see them right here
in the Japanese text in this sentence! The code for it looks like this:</p>
<pre><code class="language-plaintext">^振,ふ,り,,仮名,がな^
</code></pre><p>The delimiters are the carets, odd elements are regular text, and even elements
are their annotations. So, <span lang="ja">ふ</span> goes above <span lang="ja">振</span>, nothing goes above <span lang="ja">り</span>
(it's already a kana character), and <span lang="ja">がな</span> goes above <span lang="ja">仮名</span>.</p>
<p>There are actually <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby">specific elements</a> for handling ruby annotations, so
what you see rendered is only from HTML and CSS! They'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.</p>
<h2 id="highlighted-text">Highlighted text</h2>
<p>The syntax of this extension is borrowed from elsewhere (I didn't invent it).
This addition allows me to wrap stuff in <code><mark></code> elements. By default, this
is a bit like using a highlighter pen on text. The syntax looks like:</p>
<pre><code class="language-plaintext">Boring ==important== boring again.
</code></pre><p>which renders to:</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">p</span>></span>
Boring <span class="hljs-tag"><<span class="hljs-name">mark</span>></span>important<span class="hljs-tag"></<span class="hljs-name">mark</span>></span> boring again.
<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
</code></pre><p>which looks like:</p>
<blockquote>
<p>Boring <mark>important</mark> boring again.</p>
</blockquote>
<p>This is another extension I use heavily in my language notes to emphasize the
important parts of grammar notes.</p>
<h2 id="fancy-maths">Fancy maths</h2>
<p>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'd have to include to do that).</p>
<p>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 <a href="https://temml.org">temml</a> to render LaTeX directly to
presentational MathML. Inline mode uses single <code>$</code> symbols as delimiters, and
blocks use double <code>$$</code> on their own lines above and below the content.</p>
<p>In the past I used <a href="https://www.mathjax.org/">MathJax</a>. Visually the <code>SVG</code> MathJax produces is superior to
MathML, but the latter is now acceptable, and <code><math></code> elements are more
appropriate than <code><svg></code>, and much less bulky.</p>
<p>This:</p>
<pre><code class="language-plaintext">$$
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
$$
</code></pre><p>Results in:</p>
<math display="block" class="tml-display" id="equation-1" aria-label="x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}"><semantics><mrow><mi>x</mi><mo>=</mo><mfrac><mrow><mo lspace="0em" rspace="0em">−</mo><mi>b</mi><mo>±</mo><msqrt><mrow><msup><mi>b</mi><mn>2</mn></msup><mo>−</mo><mn>4</mn><mi>a</mi><mi>c</mi></mrow></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}</annotation></semantics></math>
https://qubyte.codes/blog/its-time-to-build-a-study-habitIt's time to build a study habit2022-05-28T12:00:00Z2022-07-09T15:05:00Zqubytehttps://qubyte.codes
<p>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.</p>
<p>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.</p>
<p>I'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.</p>
<p>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.</p>
<p>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.</p>
<p>Other times I think I should read some manga. Two I've been meaning to start
reading which I think may work are <span lang="ja">よつばと!</span> and <span lang="ja">しろくまカフェ</span>. They'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.</p>
<p>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.</p>
<h2 id="update-2022-07-09">Update (2022-07-09)</h2>
<p>To prove that I'm not just talking crap, it's time for an update!</p>
<p>Since I wrote the above post, I've subscribed to <a href="https://www.wanikani.com">WaniKani</a>, and bought three
volumes of each of the manga I mentioned above. I'm reading <span lang="ja">よつばと!</span> first
because it's a little more accessible at my level (and I'm really enjoying it,
even if I am looking forward to <span lang="ja">しろくまカフェ</span>). I spend 30-60 minutes
each week on a Saturday or Sunday reading, and I intend to build that up to
more.</p>
<p>I've found WaniKani to be especially effective. Over the years (decades?) I've
tried a lot of flash card apps and approaches, but avoided WaniKani since my
kanji wasn't too bad and I didn'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's helping me as I read manga.</p>
<p>To hold myself to account I'm logging my <a href="/study-sessions/">study sessions</a>. I'm only logging
sessions over about 10 minutes. Lots of WaniKani review sessions come in under
that bar.</p>
https://qubyte.codes/blog/controlling-ruby-annotation-positioning-and-appearance-with-pure-css-and-a-select-boxControlling ruby annotation positioning and appearance with pure CSS and a select box2022-11-07T12:05:00Z2022-11-07T12:05:00Zqubytehttps://qubyte.codes
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has"><code>:has()</code></a> 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)!</p>
<p>Demonstration time. Below the date stamp in the header above you'll see
"<span lang="ja">ふりがな</span>". 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.</p>
<p lang="ja">この<ruby>文<rp>(</rp><rt>ぶん</rt><rp>)</rp></ruby>にはふりがながあります。</p>
<p>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 <a href="/blog/marqdown">my own extensions to Markdown</a>.</p>
<p>The replacement CSS looks like this (there's a little more to handle the quirks
of <code><mark></code> elements which I omit here):</p>
<pre><code class="language-css"><span class="hljs-comment">/* Select <body> when an element with class furigana-position has an <option>
* with value "over" and that option is in the checked state.
*/</span>
<span class="hljs-selector-tag">body</span><span class="hljs-selector-pseudo">:has</span>(<span class="hljs-selector-class">.furigana-position</span> <span class="hljs-selector-tag">option</span><span class="hljs-selector-attr">[value=<span class="hljs-string">"over"</span>]</span><span class="hljs-selector-pseudo">:checked</span>) {
<span class="hljs-attribute">ruby-position</span>: over;
-webkit-<span class="hljs-attribute">ruby-position</span>: before;
}
<span class="hljs-comment">/* Select <body> when an element with class furigana-position has an <option>
* with value "under" and that option is in the checked state.
*/</span>
<span class="hljs-selector-tag">body</span><span class="hljs-selector-pseudo">:has</span>(<span class="hljs-selector-class">.furigana-position</span> <span class="hljs-selector-tag">option</span><span class="hljs-selector-attr">[value=<span class="hljs-string">"under"</span>]</span><span class="hljs-selector-pseudo">:checked</span>) {
<span class="hljs-attribute">ruby-position</span>: under;
-webkit-<span class="hljs-attribute">ruby-position</span>: after;
}
<span class="hljs-comment">/* Select <rt> and <rp> elements in the <body> when an element with class
* furigana-position has an <option> with value "off" and that option is in the
* checked state.
*/</span>
<span class="hljs-selector-tag">body</span><span class="hljs-selector-pseudo">:has</span>(<span class="hljs-selector-class">.furigana-position</span> <span class="hljs-selector-tag">option</span><span class="hljs-selector-attr">[value=<span class="hljs-string">"off"</span>]</span><span class="hljs-selector-pseudo">:checked</span>) <span class="hljs-selector-pseudo">:is</span>(rt, rp) {
<span class="hljs-attribute">display</span>: none;
}
</code></pre><p>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's a progressive enhancement
of sorts. It's not required for the positioning feature to work, just for the
state to be saved.</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> select = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'.furigana-position'</span>);
select.<span class="hljs-property">value</span> = <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">getItem</span>(<span class="hljs-string">'ruby-position'</span>) || <span class="hljs-string">'over'</span>;
select.<span class="hljs-property">onchange</span> = <span class="hljs-function"><span class="hljs-params">e</span> =></span> <span class="hljs-variable language_">localStorage</span>.<span class="hljs-title function_">setItem</span>(<span class="hljs-string">'ruby-position'</span>, e.<span class="hljs-property">target</span>.<span class="hljs-property">value</span>);
</code></pre><p>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're reading and why. It can
also be helpful, especially on a tablet, to position furigana <em>below</em> the text
it annotates, so it can be hidden with a sheet of paper as you read.</p>
https://qubyte.codes/blog/progressively-enhanced-caching-of-javascript-modules-without-bundling-using-import-mapsProgressively enhanced caching of JavaScript modules without bundling using import maps2022-11-23T09:00:00Z2023-09-24T11:50:00Zqubytehttps://qubyte.codes
<p>I went to <a href="https://2022.ffconf.org">ffconf 2022</a> a couple of weeks ago, and two of the talks in
particular resonated with me... (more actually, but these felt actionable):</p>
<ul>
<li><a href="https://youtu.be/vGYm9VdfJ8s">"This Talk is Under Construction: a love letter to the personal website"</a> <a href="https://localghost.dev/">Sophie Koonin</a></li>
<li><a href="https://youtu.be/CS-3bFo1XHA">"Working towards a greener world from behind the keyboard"</a> Natalia Waniczek</li>
</ul>
<p>I really like having my own place on the web, and I've already put a fairly
substantial amount of effort into making it as gentle on the environment as
possible:</p>
<ul>
<li>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).</li>
<li>Few images, served in the <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">most efficient formats</a> supported by the
browser making the request.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes">Lazy loaded images</a>. This is important for <a href="/notes">my notes stream</a>.</li>
<li>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).</li>
<li>The site uses a static site generator, so requests for pages resolve to files
which are easily cached. There's no database serving queries behind the
scenes.</li>
<li>As little JS as possible. Most pages have only enough JS to
<a href="/blog/putting-back-the-service-worker">load a service worker</a> as a progressive enhancement to allow offline access.
These pages work with JS disabled (you're reading one now).</li>
</ul>
<p>I was inspired to put some time into reducing my footprint even further!</p>
<h3 id="caching-javascript">Caching JavaScript</h3>
<p>The small elephant in the room are my JavaScript experiments (for example,
<a href="/tags/GenerativeArt">generative art stuff</a>). I like to build with small, reusable
modules, and until now I haven'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 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable">immutable</a><sup class="footnote-ref"><a id="footnote-ref-1" href="#footnote-1">[1]</a></sup> cache headers with a long <code>max-age</code>, like the CSS
mentioned above.</p>
<p>Why though? Resources which are immutably cached need no more network requests
once cached. This means fewer requests (especially on subsequent page views).
<strong>Less energy used</strong> to transmit files. Less opportunity for network latency and
failure to delay or break features using JS. It's also kinder to mobile users
and their plans.</p>
<p>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.</p>
<h3 id="bundling-versus-small-modules">Bundling versus small modules</h3>
<p>I'd still rather not bundle though. The pool of small modules I'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.</p>
<p>It would be far better to cache these modules independently. Consider this tree
of dependencies (imagine that <code>/a.js</code> is the entry point):</p>
<svg class="flow-diagram" role="img" focusable="false" aria-labelledby="dependency-tree-no-hashes" height="322" viewBox="0 0 207.9453125 322">
<title id="dependency-tree-no-hashes">Dependency tree with no hashes</title>
<defs>
<marker id="arrowhead" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath"></path></marker>
<rect id="small-box" rx="0" ry="0" x="-27.5078125" y="-19.5" width="55.015625" height="39" class="label-container"></rect>
<rect id="large-box" rx="0" ry="0" x="-50.765625" y="-19.5" width="101.53125" height="39" class="label-container"></rect>
</defs>
<g transform="translate(0, 0)">
<g class="edgePaths">
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
</g>
<g class="nodes">
<g class="node" transform="translate(88.28125,27.5)">
<use href="#small-box"></use>
<text>/a.js</text>
</g>
<g class="node" transform="translate(35.765625,116.5)">
<use href="#small-box"></use>
<text>/b.js</text>
</g>
<g class="node" transform="translate(140.796875,116.5)">
<use href="#small-box"></use>
<text>/c.js</text>
</g>
<g class="node" transform="translate(172.1796875,205.5)">
<use href="#small-box"></use>
<text>/d.js</text>
</g>
<g class="node" transform="translate(140.796875,294.5)">
<use href="#small-box"></use>
<text>/e.js</text>
</g>
</g>
</g>
</svg>
<p>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'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 <em>could</em> do some preloading, but whatever we do it's still a
request per module, even when the module has been downloaded before and
there are no changes.</p>
<p>We could try to hash the content of each file, and put it in the filenames (the
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#cache_busting">cache-busting pattern</a>):</p>
<svg class="flow-diagram" role="img" focusable="false" aria-labelledby="dependency-tree-with-hashes" height="322" viewBox="0 0 247.9453125 322">
<title id="dependency-tree-with-hashes">Dependency tree with hashed file names</title>
<g transform="translate(20, 0)">
<g class="edgePaths">
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
</g>
<g class="nodes">
<g class="node" transform="translate(88.28125,27.5)">
<use href="#large-box"></use>
<text>/a-63efa7.js</text>
</g>
<g class="node" transform="translate(35.765625,116.5)">
<use href="#large-box"></use>
<text>/b-913d04.js</text>
</g>
<g class="node" transform="translate(140.796875,116.5)">
<use href="#large-box"></use>
<text>/c-f61966.js</text>
</g>
<g class="node" transform="translate(172.1796875,205.5)">
<use href="#large-box"></use>
<text>/d-732056.js</text>
</g>
<g class="node" transform="translate(140.796875,294.5)">
<use href="#large-box"></use>
<text>/e-d2df5d.js</text>
</g>
</g>
</g>
</svg>
<p>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.</p>
<p>But there's a problem with this... when a file changes, its name will change.
When it's name changes, any files which depend on it will change since the
imports need to be updated! For example, if <code>/e.js</code> changes, the imports in
<code>/c.js</code> and <code>/d.js</code> must be updated, which leads to a change in <code>/a.js</code> too. The
changes go all the way from the updated module to the top!</p>
<svg class="flow-diagram" role="img" focusable="false" aria-labelledby="dependency-tree-with-invalidated-hashes" height="322" viewBox="0 0 247.9453125 322">
<title id="dependency-tree-with-invalidated-hashes">Dependency tree with invalidated hashed file names</title>
<g transform="translate(20, 0)">
<g class="edgePaths">
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
<path class="arrow-path" d="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" marker-end="url(#arrowhead)"></path>
</g>
<g class="nodes">
<g class="node" transform="translate(88.28125,27.5)">
<use href="#large-box"></use>
<text class="error">/a-5b5be8.js</text>
</g>
<g class="node" transform="translate(35.765625,116.5)">
<use href="#large-box"></use>
<text>/b-913d04.js</text>
</g>
<g class="node" transform="translate(140.796875,116.5)">
<use href="#large-box"></use>
<text class="error">/c-7534a8.js</text>
</g>
<g class="node" transform="translate(172.1796875,205.5)">
<use href="#large-box"></use>
<text class="error">/d-25255e.js</text>
</g>
<g class="node" transform="translate(140.796875,294.5)">
<use href="#large-box"></use>
<text class="error">/e-bb438d.js</text>
</g>
</g>
</g>
</svg>
<p>This is a problem. I don't want to invalidate caching for modules which have no
meaningful code changes.</p>
<p><a href="https://github.com/WICG/import-maps">Import maps</a> 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.</p>
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"imports"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"/a.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/a-5b5be8.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/b.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/b-913d04.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/c.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/c-7534a8.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/d.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/d-25255e.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/e.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/e-bb438d.js"</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre><p>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.</p>
<h3 id="implementation">Implementation</h3>
<p>For a static site generator the setup is:</p>
<ol>
<li>Copy the files with their original names into the target directory.</li>
<li>Copy each file a second time to the same target, but include a prefix and the
hash in the file name.</li>
<li>Add an import map to each page which uses JS.</li>
<li>Update the script entry point to use the hashed file.</li>
</ol>
<p>The first point allows browsers which don'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're served (the second point).</p>
<p>The last point was unexpected, but to spec (it's not just a Chrome quirk).
Browsers which don't understand import maps actually benefit from this change,
because they can at least cache the entry point.</p>
<p>The third point hides <em>a lot</em> of pain. As I add more experiments, I don't want
the import maps of other pages to grow, so I don't want just one import map for
all pages, but rather an import map for each page.</p>
<p>When the files are copied and hashed, a big import map is generated. The code
I've written also parses (but does not modify) each module to determine its
dependencies.</p>
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"/a.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"hashedFileName"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/hashed-a-5b5be8.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"dependencies"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"/b.js"</span><span class="hljs-punctuation">,</span> <span class="hljs-string">"/c.js"</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/b.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"hashedFileName"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/hashed-b-913d04.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"dependencies"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/c.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"hashedFileName"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/hashed-c-7534a8.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"dependencies"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"/d.js"</span><span class="hljs-punctuation">,</span> <span class="hljs-string">"e.js"</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/d.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"hashedFileName"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/hashed-d-25255e.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"dependencies"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"e.js"</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"/e.js"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"hashedFileName"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/hashed-e-bb438d.js"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"dependencies"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre><p>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.</p>
<h3 id="content-security-policy">Content Security Policy</h3>
<p>While the import map specification allows for them to be external to the HTML of
a page, no browsers currently implement this<sup class="footnote-ref"><a id="footnote-ref-2" href="#footnote-2">[2]</a></sup>. The HTML pages of my static
site have quite strict <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Content Security Policy (CSP)</a> headers. These
headers prevent the execution of inline scripts and inline CSS, and I prefer not
to relax them.</p>
<p>The way to resolve the conflict is to <a href="https://content-security-policy.com/hash/">add the hash of the import map</a>
to the CSP header of a page. This is like the server telling the browser <em>you
can't run scripts inlined in the page, except for those with this hash</em>.</p>
<p>I once used Netlify edge functions to accomplish this<sup class="footnote-ref"><a id="footnote-ref-3" href="#footnote-3">[3]</a></sup>, but now I
template a <a href="https://docs.netlify.com/routing/headers/">headers file</a> with an entry for each HTML page which needs a custom
CSP header for an import map hash.</p>
<h3 id="preloading">Preloading</h3>
<p>One problem with lots of small modules I'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.</p>
<p>Since the import maps are created specifically for each page, their values are
a list of URLs for the hashed files which <em>will</em> be needed. It's possible to
preload the modules by adding <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload">module preload</a> links in the header. Like the
entry point, the browser won't use the import map to resolve JS modules. For
stuff in HTML you have to do that.</p>
<pre><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"modulepreload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/hashed-every-90ec60.js"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"modulepreload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/hashed-javascript-dc77dc.js"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"modulepreload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/hashed-module-8d541a.js"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"modulepreload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/hashed-needed-54bb3e.js"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"modulepreload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/hashed-later-05fbf2.js"</span>></span>
</code></pre><p>The solution isn'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.</p>
<p>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.</p>
<p>One small saving I make is to exclude preload links for scripts which will
appear in script tags. This includes the <code>index.js</code> 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't have an import map or any preload
links.</p>
<h3 id="interactions-with-the-service-worker">Interactions with the service worker</h3>
<p>When a file is served with the immutable cache-control header, it'll either be
in the browser cache or it won't be (and will trigger a request to populate the
cache). The service worker isn't needed for such files. It's only there to make
decisions about more weakly cached files when browsing offline.</p>
<p>The service worker intercepts all requests, but we don'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't there at all.</p>
<p>Since I'm prefixing immutable resources, with <code>hashed-</code>, I can check the URL
of the request in the fetch event, and use the prefix to know not to continue.</p>
<pre><code class="language-javascript"><span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">fetchEvent</span> =></span> {
<span class="hljs-string">'use strict'</span>;
<span class="hljs-keyword">const</span> isHashed = fetchEvent.<span class="hljs-property">request</span>.<span class="hljs-property">url</span>
.<span class="hljs-title function_">split</span>(<span class="hljs-string">'/'</span>)
.<span class="hljs-title function_">pop</span>()
.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'hashed-'</span>);
<span class="hljs-keyword">if</span> (isHashed) {
<span class="hljs-keyword">return</span>; <span class="hljs-comment">// Delegate to the browser.</span>
}
<span class="hljs-comment">// ...</span>
});
</code></pre><p>I like this. My site only uses a service worker to handle caching, and I'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.</p>
<h3 id="conclusion-and-future-work">Conclusion and future work</h3>
<p>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!</p>
<p>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'll update
this post as I improve coverage.</p>
<p>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's less to cache (eliminates bundle overlap) and less to fetch when there
are changes (only fetch changed modules, not a whole bundle).</p>
https://qubyte.codes/blog/procedural-christmas-cards-2022Procedural Christmas cards 20222022-12-17T15:03:00Z2022-12-17T15:03:00Zqubytehttps://qubyte.codes
<p>Below is a procedural snowman. I'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 <code>?seed=1234567890</code> in the URL bar.
That will be the random seed which generated your snowflake (and it'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!</p>
<p>The red colour is a convenience to make it show up in the software I use to
cut card.</p>
<p>Full disclosure; I managed the time badly this year. If you got a card after
Christmas then it's on me (not the mail strikes).</p>
https://qubyte.codes/blog/my-2022-japanese-language-study-habitsMy 2022 Japanese language study habits2023-01-03T09:10:00Z2023-01-03T09:10:00Zqubytehttps://qubyte.codes
<p>This time last year I put together some custom scripts to send a record of each
study session to my personal site using the <a href="https://indieweb.org/Micropub">micropub</a> endpoint. You can see
them <a href="/study-sessions">here</a>. Each entry says what I did, how long I did it for,
and when I started doing it. On their own these aren'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!</p>
<p>I also took lessons during 2022, but I'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 <a href="https://www.wanikani.com">WaniKani</a> and <a href="https://www.wanikani.com">Bunpro</a>, both of which I categorize as flashcards.</p>
<ul>
<li>The <em>most</em> time I spent studying on a single day was 88 minutes.</li>
<li>The <em>least</em> time I spent studying on a single day was (unsurprisingly) 0 minutes.</li>
<li>I averaged about 17 minutes a day.</li>
<li>The <em>most</em> time I spent studying in a calendar week was 5 hours and 47 minutes.</li>
<li>The <em>least</em> time I spent studying in a calendar week was (regrettably, but we'll get to that) 0 hours.</li>
<li>I studied the least on Saturdays (averaging about 11 minutes), and the most on Sundays (averaging about 24 minutes).</li>
</ul>
<p>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.</p>
<div class="plot">
<svg viewBox="-20 -5 580 369" role="img" aria-labelledby="weeks-plot">
<title id="weeks-plot">A plot of total minutes studied for each calendar week</title>
<line x1="0" x2="535" y1="347" y2="347" />
<line x1="0" x2="0" y1="0" y2="347" />
<text x="265" y="362">week</text>
<text transform="rotate(270) translate(-173.5, -5)">minutes</text>
<g transform="translate(10, 347)">
<title>Week 2, 150 minutes</title>
<rect x="1" y="-150" width="8" height="150"></rect>
</g>
<g transform="translate(20, 347)">
<title>Week 3, 103 minutes</title>
<rect x="1" y="-103" width="8" height="103"></rect>
</g>
<g transform="translate(30, 347)">
<title>Week 4, 76 minutes</title>
<rect x="1" y="-76" width="8" height="76"></rect>
</g>
<g transform="translate(40, 347)">
<title>Week 5, 142 minutes</title>
<rect x="1" y="-142" width="8" height="142"></rect>
</g>
<g transform="translate(50, 347)">
<title>Week 6, 103 minutes</title>
<rect x="1" y="-103" width="8" height="103"></rect>
</g>
<g transform="translate(60, 347)">
<title>Week 7, 58 minutes</title>
<rect x="1" y="-58" width="8" height="58"></rect>
</g>
<g transform="translate(70, 347)">
<title>Week 8, 50 minutes</title>
<rect x="1" y="-50" width="8" height="50"></rect>
</g>
<g transform="translate(80, 347)">
<title>Week 9, 55 minutes</title>
<rect x="1" y="-55" width="8" height="55"></rect>
</g>
<g transform="translate(90, 347)">
<title>Week 10, 21 minutes</title>
<rect x="1" y="-21" width="8" height="21"></rect>
</g>
<g transform="translate(100, 347)">
<title>Week 11, 20 minutes</title>
<rect x="1" y="-20" width="8" height="20"></rect>
</g>
<g transform="translate(200, 347)">
<title>Week 21, 15 minutes</title>
<rect x="1" y="-15" width="8" height="15"></rect>
</g>
<g transform="translate(210, 347)">
<title>Week 22, 25 minutes</title>
<rect x="1" y="-25" width="8" height="25"></rect>
</g>
<g transform="translate(220, 347)">
<title>Week 23, 145 minutes</title>
<rect x="1" y="-145" width="8" height="145"></rect>
</g>
<g transform="translate(230, 347)">
<title>Week 24, 130 minutes</title>
<rect x="1" y="-130" width="8" height="130"></rect>
</g>
<g transform="translate(240, 347)">
<title>Week 25, 135 minutes</title>
<rect x="1" y="-135" width="8" height="135"></rect>
</g>
<g transform="translate(250, 347)">
<title>Week 26, 45 minutes</title>
<rect x="1" y="-45" width="8" height="45"></rect>
</g>
<g transform="translate(260, 347)">
<title>Week 27, 163 minutes</title>
<rect x="1" y="-163" width="8" height="163"></rect>
</g>
<g transform="translate(270, 347)">
<title>Week 28, 100 minutes</title>
<rect x="1" y="-100" width="8" height="100"></rect>
</g>
<g transform="translate(280, 347)">
<title>Week 29, 140 minutes</title>
<rect x="1" y="-140" width="8" height="140"></rect>
</g>
<g transform="translate(290, 347)">
<title>Week 30, 192 minutes</title>
<rect x="1" y="-192" width="8" height="192"></rect>
</g>
<g transform="translate(300, 347)">
<title>Week 31, 181 minutes</title>
<rect x="1" y="-181" width="8" height="181"></rect>
</g>
<g transform="translate(310, 347)">
<title>Week 32, 196 minutes</title>
<rect x="1" y="-196" width="8" height="196"></rect>
</g>
<g transform="translate(320, 347)">
<title>Week 33, 165 minutes</title>
<rect x="1" y="-165" width="8" height="165"></rect>
</g>
<g transform="translate(330, 347)">
<title>Week 34, 230 minutes</title>
<rect x="1" y="-230" width="8" height="230"></rect>
</g>
<g transform="translate(340, 347)">
<title>Week 35, 172 minutes</title>
<rect x="1" y="-172" width="8" height="172"></rect>
</g>
<g transform="translate(350, 347)">
<title>Week 36, 303 minutes</title>
<rect x="1" y="-303" width="8" height="303"></rect>
</g>
<g transform="translate(360, 347)">
<title>Week 37, 297 minutes</title>
<rect x="1" y="-297" width="8" height="297"></rect>
</g>
<g transform="translate(370, 347)">
<title>Week 38, 268 minutes</title>
<rect x="1" y="-268" width="8" height="268"></rect>
</g>
<g transform="translate(380, 347)">
<title>Week 39, 293 minutes</title>
<rect x="1" y="-293" width="8" height="293"></rect>
</g>
<g transform="translate(390, 347)">
<title>Week 40, 347 minutes</title>
<rect x="1" y="-347" width="8" height="347"></rect>
</g>
<g transform="translate(400, 347)">
<title>Week 41, 242 minutes</title>
<rect x="1" y="-242" width="8" height="242"></rect>
</g>
<g transform="translate(410, 347)">
<title>Week 42, 239 minutes</title>
<rect x="1" y="-239" width="8" height="239"></rect>
</g>
<g transform="translate(420, 347)">
<title>Week 43, 180 minutes</title>
<rect x="1" y="-180" width="8" height="180"></rect>
</g>
<g transform="translate(430, 347)">
<title>Week 44, 156 minutes</title>
<rect x="1" y="-156" width="8" height="156"></rect>
</g>
<g transform="translate(440, 347)">
<title>Week 45, 170 minutes</title>
<rect x="1" y="-170" width="8" height="170"></rect>
</g>
<g transform="translate(450, 347)">
<title>Week 46, 141 minutes</title>
<rect x="1" y="-141" width="8" height="141"></rect>
</g>
<g transform="translate(460, 347)">
<title>Week 47, 99 minutes</title>
<rect x="1" y="-99" width="8" height="99"></rect>
</g>
<g transform="translate(470, 347)">
<title>Week 48, 115 minutes</title>
<rect x="1" y="-115" width="8" height="115"></rect>
</g>
<g transform="translate(480, 347)">
<title>Week 49, 184 minutes</title>
<rect x="1" y="-184" width="8" height="184"></rect>
</g>
<g transform="translate(490, 347)">
<title>Week 50, 132 minutes</title>
<rect x="1" y="-132" width="8" height="132"></rect>
</g>
<g transform="translate(500, 347)">
<title>Week 51, 92 minutes</title>
<rect x="1" y="-92" width="8" height="92"></rect>
</g>
<g transform="translate(510, 347)">
<title>Week 52, 146 minutes</title>
<rect x="1" y="-146" width="8" height="146"></rect>
</g>
<g transform="translate(520, 347)">
<title>Week 53, 127 minutes</title>
<rect x="1" y="-127" width="8" height="127"></rect>
</g>
</svg>
</div>
<p>Week 1 aside (January 1st 2022 was a Saturday) you can see that I started out
with <em>good intentions</em><sup class="footnote-ref"><a id="footnote-ref-1" href="#footnote-1">[1]</a></sup>. At the time
I didn'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 <a href="https://www.memrise.com">Memrise</a> and <a href="https://www.busuu.com">Busuu</a>
which are general purpose. Japanese (and probably many or most other languages)
is a unique beast, and these general purpose flash card apps didn't do a great
job in my case.</p>
<p>At the end of May I resolved to <a href="/blog/its-time-to-build-a-study-habit">build a study habit</a>. That'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 <a href="https://www.wanikani.com">WaniKani</a> and <a href="https://www.wanikani.com">Bunpro</a> above) helped a lot. These applications are
designed specifically for Japanese, and I'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.</p>
<p>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!</p>
<p>My resolution for this year is to regain all the ground I've lost since I passed
the <a href="https://www.jlpt.jp">JLPT</a> N4 exam a decade ago, and sit the N3 exam in December. I'm going to
need to increase my study minutes (I'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.</p>
https://qubyte.codes/blog/tip-type-narrowing-arrays-for-sorbet-in-rubyTip: Type narrowing arrays for sorbet in ruby2023-01-28T15:25:00Z2023-01-28T15:25:00Zqubytehttps://qubyte.codes
<p>When working with type systems like <a href="https://www.typescriptlang.org">TypeScript</a> or <a href="https://sorbet.org">Sorbet</a>, type narrowing
patterns are a way to handle different types a variable may contain in different
branches. For example:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">n:</span> T.nilable(<span class="hljs-title class_">Integer</span>)
).returns(
T.nilable(<span class="hljs-title class_">Integer</span>)
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">double</span>(<span class="hljs-params">n</span>)
<span class="hljs-keyword">if</span> n
<span class="hljs-comment"># n can't be nil in this branch, so sorbet</span>
<span class="hljs-comment"># knows the type is narrowed to Integer.</span>
n * <span class="hljs-number">2</span>
<span class="hljs-keyword">else</span>
<span class="hljs-comment"># This branch could be implied, but is</span>
<span class="hljs-comment"># here for clarity.</span>
<span class="hljs-literal">nil</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre><p>Type narrowing applies to more than just <a href="https://sorbet.org/docs/nilable-types"><code>T.nilable(X)</code></a> to <code>X</code>. You
can use it to refine a type to a subset of some types allowed by a <a href="https://sorbet.org/docs/union-types">union</a>, or
from a value of some class to a value of a child class.</p>
<p>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 <code>select</code> and <code>select!</code> (the latter modifies in place).</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">array:</span> T::<span class="hljs-title class_">Array</span>[T.nilable(<span class="hljs-title class_">Integer</span>)]
).returns(
T::<span class="hljs-title class_">Array</span>[<span class="hljs-title class_">Integer</span>]
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">filter_and_double_array</span>(<span class="hljs-params">array</span>)
<span class="hljs-comment"># Sorbet thinks that integers is</span>
<span class="hljs-comment"># T::Array[T.nilable(Integer)] :(</span>
integers = array.select { |<span class="hljs-params">n</span>| n }
<span class="hljs-comment"># So it thinks the next line is broken,</span>
<span class="hljs-comment"># although we know it's safe.</span>
integers.map { |<span class="hljs-params">n</span>| n * <span class="hljs-number">2</span> }
<span class="hljs-keyword">end</span>
</code></pre><p>The brute force option here is to <a href="https://sorbet.org/docs/type-assertions#tcast"><code>T.cast(x, Integer)</code></a> in the <code>map</code>, but
escape hatches like <code>T.cast</code> and <a href="https://sorbet.org/docs/type-assertions#tmust"><code>T.must</code></a> are a last resort. When the
type system is telling us the type we're safer. There's also a runtime overhead
to using <code>T.cast</code> or <code>T.must</code>.</p>
<p>The solution is to use <code>filter_map</code>. It can be used to filter out the type(s)
you don't want (by returning <code>false</code> or <code>nil</code>), and <em>return</em> those you do:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">array:</span> T::<span class="hljs-title class_">Array</span>[T.nilable(<span class="hljs-title class_">Integer</span>)]
).returns(
T::<span class="hljs-title class_">Array</span>[<span class="hljs-title class_">Integer</span>]
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">filter_and_double_array</span>(<span class="hljs-params">array</span>)
integers = array.filter_map { |<span class="hljs-params">n</span>| n }
integers.map { |<span class="hljs-params">n</span>| n * <span class="hljs-number">2</span> }
<span class="hljs-keyword">end</span>
</code></pre><p>Or in this very simple case, the two array operations can be put together:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">array:</span> T::<span class="hljs-title class_">Array</span>[T.nilable(<span class="hljs-title class_">Integer</span>)]
).returns(
T::<span class="hljs-title class_">Array</span>[<span class="hljs-title class_">Integer</span>]
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">filter_and_double</span>(<span class="hljs-params">array</span>)
array.filter_map { |<span class="hljs-params">n</span>| n * <span class="hljs-number">2</span> <span class="hljs-keyword">if</span> n }
<span class="hljs-keyword">end</span>
</code></pre><p>Just like in the single value case, this works on more than just <code>T.nilable(X)</code>
to <code>X</code>. Filter an array of <code>Numeric</code> to an array of <code>Integer</code> (a child type of
<code>Numeric</code>):</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">array:</span> T::<span class="hljs-title class_">Array</span>[<span class="hljs-title class_">Numeric</span>]
).returns(
T::<span class="hljs-title class_">Array</span>[<span class="hljs-title class_">Integer</span>]
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">filter_non_integers</span>(<span class="hljs-params">array</span>)
array.filter_map { |<span class="hljs-params">n</span>| n <span class="hljs-keyword">if</span> n.is_a?(<span class="hljs-title class_">Integer</span>) }
<span class="hljs-keyword">end</span>
</code></pre><p>Filter an array of some union or types to a subset of the union, in this case
<code>T.any(String, Integer, Boolean)</code> to <code>T.any(String, Integer)</code>:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">array:</span> T::<span class="hljs-title class_">Array</span>[T.any(<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Integer</span>, T::<span class="hljs-title class_">Boolean</span>)]
).returns(
T::<span class="hljs-title class_">Array</span>[T.any(<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Integer</span>)]
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">filter_non_integers</span>(<span class="hljs-params">array</span>)
array.filter_map <span class="hljs-keyword">do</span> |<span class="hljs-params">x</span>|
x <span class="hljs-keyword">if</span> x != <span class="hljs-literal">true</span> && x != <span class="hljs-literal">false</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
https://qubyte.codes/blog/tip-find-and-type-narrow-an-element-from-an-array-in-ruby-and-sorbetTip: Find and type narrow an element from an array in ruby and sorbet2023-01-30T09:05:00Z2023-07-08T14:30:00Zqubytehttps://qubyte.codes
<p>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 <code>nil</code>)
element in an array, but sorbet isn't smart enough to type narrow it when the
match is related to the type of the element.</p>
<p>For illustrative purposes, here's a function which takes an array of strings
and integers, and returns the first string element lowercased, or <code>nil</code> when no
strings are in the array. My first try looked like this:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">maybe_strings:</span> T::<span class="hljs-title class_">Array</span>[T.any(<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Integer</span>)]
).returns(
T.nilable(<span class="hljs-title class_">String</span>)
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">find_first_and_lower</span>(<span class="hljs-params">maybe_strings</span>)
first = maybe_strings.find { |<span class="hljs-params">x</span>| x.is_a?(<span class="hljs-title class_">String</span>) }
<span class="hljs-comment"># Sorbet thinks that first is </span>
<span class="hljs-comment"># `T.nilable(T.any(String, Integer))`,</span>
<span class="hljs-comment"># so this doesn't work because Integer</span>
<span class="hljs-comment"># has no `downcase` method.</span>
first&.downcase
<span class="hljs-keyword">end</span>
</code></pre><p>This unfortunately doesn't work. The code is fine, but sorbet doesn't (at the
time of writing) understand that the type of <code>first</code> should be
<code>T.nilable(String)</code>, so it thinks the last line of the function is incorrect.</p>
<p>My next attempt was to manually iterate through the array:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">maybe_strings:</span> T::<span class="hljs-title class_">Array</span>[T.any(<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Integer</span>)]
).returns(
T.nilable(<span class="hljs-title class_">String</span>)
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">find_first_and_lower</span>(<span class="hljs-params">maybe_strings</span>)
maybe_strings.each <span class="hljs-keyword">do</span> |<span class="hljs-params">s</span>|
<span class="hljs-keyword">return</span> s.downcase <span class="hljs-keyword">if</span> s.is_a?(<span class="hljs-title class_">String</span>)
<span class="hljs-keyword">end</span>
<span class="hljs-literal">nil</span>
<span class="hljs-keyword">end</span>
</code></pre><p>This works! It'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 <code>find</code>). To
folk new to ruby (me) the <code>return</code> applying to the function as a whole and not
just the block was jarring too...</p>
<p>In the end I <a href="/blog/tip-type-narrowing-arrays-for-sorbet-in-ruby">used <code>filter_map</code> again</a>, which encodes most of the
behaviour I want:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">maybe_strings:</span> T::<span class="hljs-title class_">Array</span>[T.any(<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Integer</span>)]
).returns(
T.nilable(<span class="hljs-title class_">String</span>)
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">find_first_and_lower</span>(<span class="hljs-params">maybe_strings</span>)
first = maybe_strings
.filter_map { |<span class="hljs-params">x</span>| x <span class="hljs-keyword">if</span> x.is_a?(<span class="hljs-title class_">String</span>) }
.first
first&.downcase
<span class="hljs-keyword">end</span>
</code></pre><p>This works too! It's not <em>quite</em> ideal though, because the <code>filter_map</code> will
build us a whole new array when we only want the first (if any) element. That's
why I've kept the <code>downcase</code> outside the <code>filter_map</code>. The solution was to
make the <code>filter_map</code> lazy:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">maybe_strings:</span> T::<span class="hljs-title class_">Array</span>[T.any(<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Integer</span>)]
).returns(
T.nilable(<span class="hljs-title class_">String</span>)
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">find_first_and_lower</span>(<span class="hljs-params">maybe_strings</span>)
first = maybe_strings
.lazy
.filter_map { |<span class="hljs-params">x</span>| x.downcase <span class="hljs-keyword">if</span> x.is_a?(<span class="hljs-title class_">String</span>) }
.first
<span class="hljs-keyword">end</span>
</code></pre><p>I've moved the <code>downcase</code> call into the <code>filter_map</code> now because it looks a
little cleaner, and it will only apply to the first found element.</p>
<p>Bonus: How about getting the <em>last</em> matching element? While there's a <code>.last</code>
method I could use, I don't want to iterate over the whole array to get to it.
It turns out that <code>reverse_each</code> returns an enumerator when it's called without
a block:</p>
<pre><code class="language-ruby">sig <span class="hljs-keyword">do</span>
params(
<span class="hljs-symbol">maybe_strings:</span> T::<span class="hljs-title class_">Array</span>[T.any(<span class="hljs-title class_">String</span>, <span class="hljs-title class_">Integer</span>)]
).returns(
T.nilable(<span class="hljs-title class_">String</span>)
)
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">find_first_and_lower</span>(<span class="hljs-params">maybe_strings</span>)
first = maybe_strings
.reverse_each
.lazy
.filter_map { |<span class="hljs-params">x</span>| x.downcase <span class="hljs-keyword">if</span> x.is_a?(<span class="hljs-title class_">String</span>) }
.first
<span class="hljs-keyword">end</span>
</code></pre><p>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.</p>
<p>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 <code>filter_map</code> performs
better on average for your use case, even if it does process an entire array.</p>
https://qubyte.codes/blog/my-2023-japanese-language-study-habitsMy 2023 Japanese language study habits2024-01-07T11:20:00Z2024-01-07T11:20:00Zqubytehttps://qubyte.codes
<p>I've been tracking my Japanese language study session since 2022. You can see
the <a href="/blog/my-2022-japanese-language-study-habits">first year report</a> for
comparison.</p>
<p>This year my regular Japanese tutor has been taking maternity leave, so I'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'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'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 <a href="https://www.wanikani.com">WaniKani</a> and <a href="https://www.wanikani.com">Bunpro</a>, both of which I
categorize as flashcards.</p>
<ul>
<li>The <em>most</em> time I spent studying on a single day was 80 minutes.</li>
<li>The <em>least</em> time I spent studying on a single day was (unsurprisingly) 0
minutes.</li>
<li>I averaged about 16 minutes a day.</li>
<li>The <em>most</em> time I spent studying in a calendar week was 2 hours and 56
minutes.</li>
<li>The <em>least</em> time I spent studying in a calendar week was (regrettably) 0
hours.</li>
<li>I studied the least on Fridays (averaging about 10 minutes), and the most on
Sundays (averaging about 25 minutes).</li>
</ul>
<p>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.</p>
<div class="plot">
<svg viewBox="-20 -5 550 198" role="img" aria-labelledby="weeks-plot">
<title id="weeks-plot">A plot of total minutes studied for each calendar week</title>
<line x1="0" x2="485" y1="176" y2="176" />
<line x1="0" x2="0" y1="0" y2="176" />
<text x="265" y="191">week</text>
<text transform="rotate(270) translate(-88, -5)">minutes</text>
<g transform="translate(0, 176)">
<title>Week 1, 22 minutes</title>
<rect x="1" y="-22" width="8" height="22"></rect>
</g>
<g transform="translate(10, 176)">
<title>Week 2, 172 minutes</title>
<rect x="1" y="-172" width="8" height="172"></rect>
</g>
<g transform="translate(20, 176)">
<title>Week 3, 165 minutes</title>
<rect x="1" y="-165" width="8" height="165"></rect>
</g>
<g transform="translate(30, 176)">
<title>Week 4, 169 minutes</title>
<rect x="1" y="-169" width="8" height="169"></rect>
</g>
<g transform="translate(40, 176)">
<title>Week 5, 140 minutes</title>
<rect x="1" y="-140" width="8" height="140"></rect>
</g>
<g transform="translate(50, 176)">
<title>Week 6, 162 minutes</title>
<rect x="1" y="-162" width="8" height="162"></rect>
</g>
<g transform="translate(60, 176)">
<title>Week 7, 152 minutes</title>
<rect x="1" y="-152" width="8" height="152"></rect>
</g>
<g transform="translate(70, 176)">
<title>Week 8, 143 minutes</title>
<rect x="1" y="-143" width="8" height="143"></rect>
</g>
<g transform="translate(80, 176)">
<title>Week 9, 159 minutes</title>
<rect x="1" y="-159" width="8" height="159"></rect>
</g>
<g transform="translate(90, 176)">
<title>Week 10, 120 minutes</title>
<rect x="1" y="-120" width="8" height="120"></rect>
</g>
<g transform="translate(100, 176)">
<title>Week 11, 77 minutes</title>
<rect x="1" y="-77" width="8" height="77"></rect>
</g>
<g transform="translate(110, 176)">
<title>Week 12, 86 minutes</title>
<rect x="1" y="-86" width="8" height="86"></rect>
</g>
<g transform="translate(120, 176)">
<title>Week 13, 76 minutes</title>
<rect x="1" y="-76" width="8" height="76"></rect>
</g>
<g transform="translate(130, 176)">
<title>Week 14, 152 minutes</title>
<rect x="1" y="-152" width="8" height="152"></rect>
</g>
<g transform="translate(140, 176)">
<title>Week 15, 110 minutes</title>
<rect x="1" y="-110" width="8" height="110"></rect>
</g>
<g transform="translate(150, 176)">
<title>Week 16, 20 minutes</title>
<rect x="1" y="-20" width="8" height="20"></rect>
</g>
<g transform="translate(160, 176)">
<title>Week 17, 0 minutes</title>
<rect x="1" y="0" width="8" height="0"></rect>
</g>
<g transform="translate(170, 176)">
<title>Week 18, 0 minutes</title>
<rect x="1" y="0" width="8" height="0"></rect>
</g>
<g transform="translate(180, 176)">
<title>Week 19, 37 minutes</title>
<rect x="1" y="-37" width="8" height="37"></rect>
</g>
<g transform="translate(190, 176)">
<title>Week 20, 176 minutes</title>
<rect x="1" y="-176" width="8" height="176"></rect>
</g>
<g transform="translate(200, 176)">
<title>Week 21, 171 minutes</title>
<rect x="1" y="-171" width="8" height="171"></rect>
</g>
<g transform="translate(210, 176)">
<title>Week 22, 167 minutes</title>
<rect x="1" y="-167" width="8" height="167"></rect>
</g>
<g transform="translate(220, 176)">
<title>Week 23, 163 minutes</title>
<rect x="1" y="-163" width="8" height="163"></rect>
</g>
<g transform="translate(230, 176)">
<title>Week 24, 176 minutes</title>
<rect x="1" y="-176" width="8" height="176"></rect>
</g>
<g transform="translate(240, 176)">
<title>Week 25, 112 minutes</title>
<rect x="1" y="-112" width="8" height="112"></rect>
</g>
<g transform="translate(250, 176)">
<title>Week 26, 117 minutes</title>
<rect x="1" y="-117" width="8" height="117"></rect>
</g>
<g transform="translate(260, 176)">
<title>Week 27, 144 minutes</title>
<rect x="1" y="-144" width="8" height="144"></rect>
</g>
<g transform="translate(270, 176)">
<title>Week 28, 138 minutes</title>
<rect x="1" y="-138" width="8" height="138"></rect>
</g>
<g transform="translate(280, 176)">
<title>Week 29, 144 minutes</title>
<rect x="1" y="-144" width="8" height="144"></rect>
</g>
<g transform="translate(290, 176)">
<title>Week 30, 100 minutes</title>
<rect x="1" y="-100" width="8" height="100"></rect>
</g>
<g transform="translate(300, 176)">
<title>Week 31, 140 minutes</title>
<rect x="1" y="-140" width="8" height="140"></rect>
</g>
<g transform="translate(310, 176)">
<title>Week 32, 165 minutes</title>
<rect x="1" y="-165" width="8" height="165"></rect>
</g>
<g transform="translate(320, 176)">
<title>Week 33, 91 minutes</title>
<rect x="1" y="-91" width="8" height="91"></rect>
</g>
<g transform="translate(330, 176)">
<title>Week 34, 94 minutes</title>
<rect x="1" y="-94" width="8" height="94"></rect>
</g>
<g transform="translate(340, 176)">
<title>Week 35, 103 minutes</title>
<rect x="1" y="-103" width="8" height="103"></rect>
</g>
<g transform="translate(350, 176)">
<title>Week 36, 85 minutes</title>
<rect x="1" y="-85" width="8" height="85"></rect>
</g>
<g transform="translate(360, 176)">
<title>Week 37, 121 minutes</title>
<rect x="1" y="-121" width="8" height="121"></rect>
</g>
<g transform="translate(370, 176)">
<title>Week 38, 144 minutes</title>
<rect x="1" y="-144" width="8" height="144"></rect>
</g>
<g transform="translate(380, 176)">
<title>Week 39, 95 minutes</title>
<rect x="1" y="-95" width="8" height="95"></rect>
</g>
<g transform="translate(390, 176)">
<title>Week 40, 86 minutes</title>
<rect x="1" y="-86" width="8" height="86"></rect>
</g>
<g transform="translate(400, 176)">
<title>Week 41, 66 minutes</title>
<rect x="1" y="-66" width="8" height="66"></rect>
</g>
<g transform="translate(410, 176)">
<title>Week 42, 139 minutes</title>
<rect x="1" y="-139" width="8" height="139"></rect>
</g>
<g transform="translate(420, 176)">
<title>Week 43, 105 minutes</title>
<rect x="1" y="-105" width="8" height="105"></rect>
</g>
<g transform="translate(430, 176)">
<title>Week 44, 100 minutes</title>
<rect x="1" y="-100" width="8" height="100"></rect>
</g>
<g transform="translate(440, 176)">
<title>Week 45, 139 minutes</title>
<rect x="1" y="-139" width="8" height="139"></rect>
</g>
<g transform="translate(450, 176)">
<title>Week 46, 126 minutes</title>
<rect x="1" y="-126" width="8" height="126"></rect>
</g>
<g transform="translate(460, 176)">
<title>Week 47, 100 minutes</title>
<rect x="1" y="-100" width="8" height="100"></rect>
</g>
<g transform="translate(470, 176)">
<title>Week 48, 107 minutes</title>
<rect x="1" y="-107" width="8" height="107"></rect>
</g>
<g transform="translate(480, 176)">
<title>Week 49, 75 minutes</title>
<rect x="1" y="-75" width="8" height="75"></rect>
</g>
<g transform="translate(490, 176)">
<title>Week 50, 61 minutes</title>
<rect x="1" y="-61" width="8" height="61"></rect>
</g>
<g transform="translate(500, 176)">
<title>Week 51, 33 minutes</title>
<rect x="1" y="-33" width="8" height="33"></rect>
</g>
<g transform="translate(510, 176)">
<title>Week 52, 64 minutes</title>
<rect x="1" y="-64" width="8" height="64"></rect>
</g>
<g transform="translate(520, 176)">
<title>Week 53, 100 minutes</title>
<rect x="1" y="-100" width="8" height="100"></rect>
</g>
</svg>
</div>
<p>The gap you see was when I was in Japan! I think I can give myself a pass for
that.</p>
<p>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've built up a collection of cards I find difficult to
memorize, and that's both hurting my motivation and constricting the flow of new
cards. I'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.</p>
<p>Another thing I've noticed is that with the grammar focussed app (<a href="https://www.wanikani.com">Bunpro</a>) is
that it's way more than just a flashcard app, and I'm underutilizing it. This is
not just to get the most value out of it; I've found that simple repetition
isn't enough to learn many grammar points (what I think of as natural
acquisition, like a child might learn). I'm going to have to sit down and start
writing proper notes out. Of course, I have the <a href="/japanese-notes">notes section</a>
of this site, but nothing beats doing it by hand with glittery gel pens.</p>
<p>My resolutions <a href="/blog/my-2022-japanese-language-study-habits">from last year</a>:</p>
<blockquote>
<p>...regain all the ground I've lost since I passed
the <a href="https://www.jlpt.jp">JLPT</a> N4 exam a decade ago, and sit the N3 exam in December. I'm going to
need to increase my study minutes (I'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.</p>
</blockquote>
<p>I'm <em>almost</em> back to where I was when I passed the N4, and in terms of listening
and speaking I'm more advanced thanks to signing up for more tuition. As for
sitting the N3... not even close. That's a good goal to have, but in hindsight
it was a high target given the free time I have around work and family.</p>
<p>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't know
if I'll do that this year (I'd say probably not given my trajectory). Goals
should be measurable though, so here's what I'm promising myself:</p>
<ul>
<li>Properly write out 5 grammar points a week, guided by Bunpro.</li>
<li>Revise one grammar point per day.</li>
<li>Read one article on <a href="https://www3.nhk.or.jp/news/easy/">NHK News Web Easy</a> a
day.</li>
<li>Continue with flashcards. At least fifteen minutes per day.</li>
</ul>
<p>Those don't represent a lot of time, but they <em>do</em> need organization and
discipline, which I'm not so great at. Wish me luck!</p>
https://qubyte.codes/blog/why-did-i-create-a-keyboardWhy did I create a keyboard?2024-02-29T21:00:00Z2024-02-29T21:00:00Zqubytehttps://qubyte.codes
<p>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?</p>
<h2 id="the-wish-list">The wish list</h2>
<p>Constraints breed creativity, so I decided to be restrictive:</p>
<ul>
<li>It should be a split keyboard, like a <a href="https://github.com/foostan/crkbd">Corne</a> or a <a href="https://github.com/pierrechevalier83/ferris">Ferris</a>. 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).</li>
<li>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.</li>
<li>It should be a 36 key build, with <a href="https://github.com/manna-harbour/miryoku">Miryoku</a> bindings. The idea here is to
minimize how far each digit needs to move, again to reduce fatigue.</li>
<li>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'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.</li>
</ul>
<h2 id="pcb-printed-circuit-board-design">PCB (printed circuit board) design</h2>
<p>While it'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 <a href="https://github.com/ruiqimao/keyboard-pcb-guide">this one</a>). Since I was going with a controller, I
didn'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'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.</p>
<p>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're given guides to show which parts still need to be
connected. Here's what my schematic looked like:</p>
<img src="/images/fg-11-rev1-schematic.png" width="800" height="508" lazy>
<p>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't
change, just the footprints used on the PCB.</p>
<p>TODO: PCB design and layout.</p>
<p>The result is the FG-11 Rev1:</p>
<picture>
<source type="image/avif" srcset="/images/1665735132683.avif, /images/1665735132683-2x.avif 2x">
<source type="image/webp" srcset="/images/1665735132683.webp, /images/1665735132683-2x.webp 2x">
<img class="u-photo" src="/images/1665735132683.jpeg" alt="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." width="800" height="440" loading="lazy">
</picture>
<h2 id="retrospective">Retrospective</h2>
<p>After a few months using the device I've had time to understand what worked well
and what did not.</p>
<p>Firstly, <a href="https://github.com/manna-harbour/miryoku">Miryoku</a> wasn't for me. I like the idea, but it'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).</p>
<p>Next, the column stagger is good, but the wrong columns are staggered. If you
think of the little notches or nubbins on the <code>f</code> and <code>j</code> 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's like learning to type again because everything is shifted
one column over.</p>
<p>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.</p>
<p>The ribbon to connect the two sides is very flexible. I had no trouble
positioning each half wherever I wanted with it.</p>
<p>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.</p>
<p>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've found that
split keyboards often feel too light.</p>
<h2 id="the-future">The future</h2>
<p>The keyboard is good, but flawed. The next revision will have these changes:</p>
<ul>
<li>Column stagger moved outwards one column on each side.</li>
<li>Per-key backlighting. From the schematics of similar split keyboards I'm
pretty sure the controller can support enough LEDs to cover 36 keys.</li>
<li>Brass plates. I like the steel plates, but I think brass plates will work well
with the Work Louder choc key caps.</li>
<li>I'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'd be nice
to source something which isn't a bland grey.</li>
<li>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'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's a bit of a
faff.</li>
</ul>
https://qubyte.codes/blog/indiewebcamp-brighton-2024IndieWebCamp Brighton 20242024-03-16T23:00:00Z2024-03-18T03:05:00Zqubytehttps://qubyte.codes
<p>Last weekend we held <a href="https://indieweb.org/2024/Brighton">IndieWebCamp Brighton 2024</a>, the first in Brighton since
2019. I thought I'd collect my thoughts both as a host, and as an attendee.</p>
<p>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
<a href="https://indieweb.org/Homebrew_Website_Club">Homebrew Website Club</a> while people were in town for <a href="https://2023.ffconf.org">ffconf 2023</a>. 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 <a href="https://paulrobertlloyd.com">Paul</a>
came, and I think we both realized that there may be enough interest. I didn't
know it at the time, but Paul had attended <a href="https://indieweb.org/2023/Nuremberg">IndieWebCamp Nuremberg 2023</a> just
a few weeks before (which is where the seeds for Brighton 2024 were <em>actually</em>
planted).</p>
<p>So we (mostly Paul) got on with organizing things. I'm a member of <a href="https://theskiff.org">The Skiff</a>,
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<sup class="footnote-ref"><a id="footnote-ref-1" href="#footnote-1">[1]</a></sup>, 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<sup class="footnote-ref"><a id="footnote-ref-2" href="#footnote-2">[2]</a></sup>.</p>
<h2 id="saturday-introductions-planning-and-discussions">Saturday: Introductions, planning, and discussions</h2>
<p>After refreshments and some introductions, we started the planning session.
IndieWebCamps follow the <a href="https://en.wikipedia.org/wiki/BarCamp#Structure_and_participatory_process">BarCamp</a> model of collectively planning session.
<a href="https://adactio.com/journal/20968">Jeremy</a> officiated the process, and once we'd found a good <a href="https://indieweb.org/2024/Brighton/Schedule">schedule</a> of
discussions, spaces, and times.</p>
<picture>
<source type="image/avif" srcset="/images/1710294021680.avif, /images/1710294021680-2x.avif 2x">
<source type="image/webp" srcset="/images/1710294021680.webp, /images/1710294021680-2x.webp 2x">
<img class="u-photo" src="/images/1710294021680.jpeg" alt="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." width="800" height="600" loading="lazy">
</picture>
<p>There was a session I wanted to participate in for each time slot.</p>
<h3 id="energy-efficiency"><a href="https://indieweb.org/2024/Brighton/energy-efficiency">Energy Efficiency</a></h3>
<p>I wanted to be a part of this one because I'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.</p>
<p>We explored static sites versus dynamically served sites. Static sites are a
good way to serve efficiently, but harder to integrate IndieWeb standards like
<a href="https://www.w3.org/TR/webmention/">Webmentions</a> into.</p>
<p>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.</p>
<h3 id="pictures"><a href="https://indieweb.org/2024/Brighton/pictures">Pictures</a></h3>
<p>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've been
wondering about storing images separately. Fortunately for me, others share this
pain point, and that's what we talked about in this session.</p>
<p>Various bucket-like solutions were discussed, and <a href="https://www.lazaruscorporation.co.uk/artists/paul-watson">Paul Watson</a> 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).</p>
<h3 id="hosting"><a href="https://indieweb.org/2024/Brighton/hosting">Hosting</a></h3>
<p>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.</p>
<p>We discussed the pros and cons of them all, including avoiding hosting traps
which make it hard to move your site. There'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's lots more in the linked discussion.</p>
<h3 id="nfc"><a href="https://indieweb.org/2024/Brighton/nfc">NFC</a></h3>
<p>This was a fun session. <a href="https://shkspr.mobi/blog/">Terence</a> 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 <a href="https://en.wikipedia.org/wiki/The_Thing_%28listening_device%29">The Thing</a>, which is an
ancestor technology.</p>
<h3 id="personal-website-pain-points"><a href="https://indieweb.org/2024/Brighton/pains">Personal website pain points</a></h3>
<p>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.</p>
<p>A theme which emerged over Saturday was <em>URL regret</em>. 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 <code>/blog/</code>, but I don't
really think of many posts I've written on this site as blog entries. In fact,
internally they've always been managed as <em>posts</em>. URLs link the web together,
so we don't want to break them! The options are:</p>
<ul>
<li>Keep pages where they are.</li>
<li>Move pages and add permanent redirects to them so old links continue to work.</li>
<li>Move the pages, and accept that links will break. Perhaps give just the most
important pages redirects.</li>
</ul>
<p><a href="https://maggieappleton.com/garden-history">Digital gardening</a> was another theme of the day. A traditional blog lends
itself to the idea of a <em>feed</em>. 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<sup class="footnote-ref"><a id="footnote-ref-3" href="#footnote-3">[3]</a></sup>. The move from linear
feeds to gardening was the root of some URL regret in this session.</p>
<picture>
<source type="image/avif" srcset="/images/1710730970838.avif, /images/1710730970838-2x.avif 2x">
<source type="image/webp" srcset="/images/1710730970838.webp, /images/1710730970838-2x.webp 2x">
<img class="u-photo" src="/images/1710730970838.jpeg" alt="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." width="800" height="450" loading="lazy">
</picture>
<h2 id="sunday-hacking-writing-and-demos"><a href="https://indieweb.org/2024/Brighton/Demos">Sunday: Hacking, writing, and demos</a></h2>
<p>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.</p>
<p>In the morning, <a href="https://scoutaloud.net">Scout</a> and I paired to buy a domain name, and wire DNS up to
<a href="https://pages.github.com">GitHub Pages</a>. Scout is an organizer at the <a href="https://codebar.io/brighton">CodeBar Brighton</a> chapter, so
she'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'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!</p>
<p>I used the afternoon to scratch some itches. I recently wrote a <a href="/colophon">colophon</a>, 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 <a href="/">my about page</a>.</p>
<p>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'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.</p>
<p>Finally, I tweaked a little of the code I use for <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby">ruby annotations</a>. 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.
<a href="/blog/controlling-ruby-annotation-positioning-and-appearance-with-pure-css-and-a-select-box">Thanks to the <code>:has()</code> CSS pseudo-class</a> no JavaScript is needed to
do this. <em>Remembering</em> the preference, however, does need JS. So these pages
have a tiny JS script which stores the setting in <code>localStorage</code>. I tweaked the
script so that when the user sets it to "above" (the default), the script
deletes the setting, rather than storing "above".</p>
<p>To wrap up the day <a href="https://indieweb.org/2024/Brighton/Demos">we demoed what we'd written and made</a>. Click through
to explore those (there's too much good stuff to do it justice here).</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>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.</p>
<picture>
<source type="image/avif" srcset="/images/1710606604896.avif, /images/1710606604896-2x.avif 2x">
<source type="image/webp" srcset="/images/1710606604896.webp, /images/1710606604896-2x.webp 2x">
<img class="u-photo" src="/images/1710606604896.jpeg" alt="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." width="800" height="600" loading="lazy">
</picture>
https://qubyte.codes/blog/favourite-musicFavourite music2024-08-15T02:45:00Z2024-12-28T14:25:00Zqubytehttps://qubyte.codes
<p>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've linked to music services where I can find each album available,
but with minimal effort. There are other services, but they're either behind
paywalls or incomprehensible to me.</p>
<h2 id="devin-townsend--terria">Devin Townsend – Terria</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/terria.webp, /images/fair-use/terria-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/terria.avif, /images/fair-use/terria-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/terria.jpeg" alt="The album cover for Terria" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://devintownsendofficial.bandcamp.com/album/terria">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/6w2kw48F3xyHLgY4GDwFRV">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/terria/1045112739">Apple</a></li>
</ul>
<p>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.</p>
<p>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.</p>
<h2 id="strapping-young-lad--city">Strapping Young Lad – City</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/city.webp, /images/fair-use/city-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/city.avif, /images/fair-use/city-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/city.jpeg" alt="The album cover for City" width="150" height="149" loading="lazy">
</picture></p>
<ul>
<li><a href="https://centurymedia.bandcamp.com/album/city-remastered-demo-versions">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/78Y2OaDAdvEqs3TRdCRdZc">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/city/1045623722">Apple</a></li>
</ul>
<p>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).</p>
<h2 id="portishead--dummy">Portishead – Dummy</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/dummy.webp, /images/fair-use/dummy-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/dummy.avif, /images/fair-use/dummy-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/dummy.jpeg" alt="The album cover for Dummy" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://open.spotify.com/album/3539EbNgIdEDGBKkUf4wno">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/dummy/1440653096">Apple</a></li>
</ul>
<p>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.</p>
<h2 id="isis-the-band--panopticon">ISIS (the band) – Panopticon</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/panopticon.webp, /images/fair-use/panopticon-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/panopticon.avif, /images/fair-use/panopticon-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/panopticon.jpeg" alt="The album cover for Panopticon" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://isistheband.bandcamp.com/album/panopticon-remastered">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/4YVSY6TwnXWH7Jz4olWO1e">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/panopticon-remastered/1001664659">Apple</a></li>
</ul>
<p>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' takes an age
to build, but when it finally gets there it's the deepest, heaviest, most
crushing sound. Amazing pay-off.</p>
<h2 id="opeth--blackwater-park">Opeth – Blackwater Park</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/blackwater-park.webp, /images/fair-use/blackwater-park-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/blackwater-park.avif, /images/fair-use/blackwater-park-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/blackwater-park.jpeg" alt="The album cover for Blackwater Park" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://opeth.omerch.com/products/opeth-blackwater-park-cd">Omerch (buy)</a></li>
<li><a href="https://open.spotify.com/album/3CCkWrqhWcKU7qXK3ooEEo">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/blackwater-park/363715800">Apple</a></li>
</ul>
<p>While the first Opeth album I got into was Still Life (and Deliverance &
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.</p>
<h2 id="múm--finally-we-are-no-one">Múm – Finally We Are No One</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/finally-we-are-no-one.webp, /images/fair-use/finally-we-are-no-one-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/finally-we-are-no-one.avif, /images/fair-use/finally-we-are-no-one-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/finally-we-are-no-one.jpeg" alt="The album cover for Finally We Are No One" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://fatcatrecords.bandcamp.com/album/finally-we-are-no-one">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/2XCcnYJJQXYoWm5oc20x9k">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/finally-we-are-no-one/285318218">Apple</a></li>
</ul>
<p>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.</p>
<h2 id="the-avalanches--since-i-left-you">The Avalanches – Since I Left You</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/since-i-left-you.webp, /images/fair-use/since-i-left-you-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/since-i-left-you.avif, /images/fair-use/since-i-left-you-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/since-i-left-you.jpeg" alt="The album cover for Since I Left You" width="150" height="130" loading="lazy">
</picture></p>
<ul>
<li><a href="https://open.spotify.com/album/0CvU96jYCiNP4c9u8dWHoI">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/since-i-left-you/1450114829">Apple</a></li>
</ul>
<p>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.</p>
<p>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.</p>
<h2 id="portico-quartet--memory-streams">Portico Quartet – Memory Streams</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/memory-streams.webp, /images/fair-use/memory-streams-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/memory-streams.avif, /images/fair-use/memory-streams-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/memory-streams.jpeg" alt="The album cover for Memory Streams" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://porticoquartet.bandcamp.com/album/memory-streams">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/7rUuKsh6pJLcb44d1NBV81">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/memory-streams/1583551298">Apple</a></li>
</ul>
<p>This is a fairly recent edition, and I find it difficult to categorize. The band
makes instrumental music with heavy use of <a href="https://en.wikipedia.org/wiki/Hang_(instrument)">the Hang</a> (a form of steel drum),
saxophones, and synths. I guess it's jazz, but I don'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.</p>
<h2 id="boards-of-canada--tomorrows-harvest">Boards of Canada – Tomorrow's Harvest</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/tomorrows-harvest.webp, /images/fair-use/tomorrows-harvest-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/tomorrows-harvest.avif, /images/fair-use/tomorrows-harvest-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/tomorrows-harvest.jpeg" alt="The album cover for Tomorrow's Harvest" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://boardsofcanada.bandcamp.com/album/tomorrows-harvest">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/159ORixBSSemxiualv1Woj">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/tomorrows-harvest/641229267">Apple</a></li>
</ul>
<p>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's earlier work, but
Tomorrow's Harvest blew my mind. The vintage sound and sparseness gives it a
brooding and foreboding feeling. This album is excellent to work to.</p>
<p>I bought this album shortly before moving back to the UK from Japan, so it used
to remind me of that time. I'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.</p>
<h2 id="the-devin-townsend-project---addicted">The Devin Townsend Project - Addicted</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/addicted.webp, /images/fair-use/addicted-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/addicted.avif, /images/fair-use/addicted-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/addicted.jpeg" alt="The album cover for Addicted" width="150" height="150" loading="lazy">
</picture></p>
<ul>
<li><a href="https://devintownsendofficial.bandcamp.com/album/addicted">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/4vaF198cLJTlSZ49v1N1mk">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/addicted/1045214857">Apple</a></li>
</ul>
<p>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 <em>Bend it like Bender!</em> was a favourite of one of my Italian friends. That
friendship ultimately led to me meeting my partner.</p>
<p>Life is full of these incredibly unlikely little sequences of events with
important outcomes. What if that album came out a day later? I'd have been too
busy to buy it. What if I had chosen a different share house? What if the
aforementioned friend hadn'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 "encouraged" to attend to recoup some losses? What if my
partner and I hadn't been snacking on arancini next to each other and struck up
a conversation? What if...</p>
<h2 id="marconi-union---signals">Marconi Union - Signals</h2>
<p><picture>
<source type="image/webp" srcset="/images/fair-use/signals.webp, /images/fair-use/signals-2x.webp 2x">
<source type="image/avif" srcset="/images/fair-use/signals.avif, /images/fair-use/signals-2x.avif 2x">
<img class="u-photo" src="/images/fair-use/signals.jpeg" alt="The album cover for Signals" width="150" height="150" loading="lazy">
</picture></p>
<p>Another relatively recent addition. I'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's an exceptional album. Tracks are repetitive, build slowly, and at
times feel meditative. There's a thrumming, almost dangerous quality to it.
Stand out tracks for me are Strata, and A Citizen's Dream. The latter I find
very affecting. It feels like longing, sadness, and hope, sometimes to the point
of tears when I'm actively listening to it. I have no idea why that is though!
Sometimes my reaction to music can't be explained. It's like it can be a key to
a random emotional door in my mind.</p>
<p>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.</p>
<ul>
<li><a href="https://marconiunion.bandcamp.com/album/signals">Bandcamp</a></li>
<li><a href="https://open.spotify.com/album/1z6YgGKHAEJ9FIV93LP4SI?si=8b1a-X2oRJKqsCAjFSfkfQ">Spotify</a></li>
<li><a href="https://music.apple.com/gb/album/signals/1580742497">Apple</a></li>
</ul>
https://qubyte.codes/blog/how-i-syndicate-links-and-notes-to-bluesky-with-github-actionsHow I syndicate links and notes to Bluesky with GitHub Actions2024-11-25T02:15:00Z2024-11-25T02:15:00Zqubytehttps://qubyte.codes
<p>Inspired by <a href="https://adactio.com/journal/21570">Jeremy's post on how he syndicates to Bluesky</a>, I
thought I'd follow suit (many examples are useful when it comes to API
integration work). A disclaimer though... I'm dubious of the long term prospects
of Bluesky for reasons I won't go into here. That being said, it's currently
a vibrant place, and <a href="https://indieweb.org/POSSE">syndicating from my site to other places</a> keeps my
content<sup class="footnote-ref"><a id="footnote-ref-1" href="#footnote-1">[1]</a></sup> in my hands.</p>
<p>My setup is a bit of a Rube Goldberg machine:</p>
<ul>
<li>I publish notes (optionally with a photo) and bookmarks using <a href="https://www.w3.org/TR/micropub/">Micropub</a>
endpoints.</li>
<li>The endpoints add the note or bookmark as a <a href="https://jf2.spec.indieweb.org">JF2</a> JSON file to the <a href="https://github.com/qubyte/qubyte-codes">git
repository of my personal site on GitHub</a>.</li>
<li>A <a href="https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/syndicate-to-bluesky.yml">GitHub Actions workflow</a> is triggered by the addition of a note
or bookmark.</li>
<li>The workflow calls a Node.js script. It calls the script using my Bluesky
handle, which is <code>qubyte.codes</code> for me, and an <a href="https://bsky.app/settings/app-passwords">app password</a>. Do <em>not</em> use
your actual password! Bluesky makes it pretty easy to create an
<a href="https://bsky.app/settings/app-passwords">app password</a>, and when you have one you can add it as a secret for Actions
to use at the <code>./settings/secrets/actions</code> path of your repo site. I've called
my secret <code>BLUESKY_APP_PASSWORD</code>.</li>
</ul>
<p>The rest of this post is about <a href="https://github.com/qubyte/qubyte-codes/blob/main/scripts/syndicate-to-bluesky.js">the script itself</a>. 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.</p>
<p>I publish notes (optionally with an image) and bookmarks using <a href="https://www.w3.org/TR/micropub/">Micropub</a>
endpoints. The note or bookmark is added as a <a href="https://jf2.spec.indieweb.org">JF2</a> JSON file to the <a href="https://github.com/qubyte/qubyte-codes">git
repository of my personal site on GitHub</a>. When such a file is created
there, a <a href="https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/syndicate-to-bluesky.yml">GitHub Actions workflow</a> is triggered, and this workflow
in turn calls a Node.js script.</p>
<p>The script is a vanilla Node.js script. While Bluesky does provide API client
libraries, I didn'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
<code>fetch</code>.</p>
<p>There are a couple of local libraries which I've extracted into their own
modules. The first is <code>blueskyAuth</code>, which looks like this:</p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> createSessionUrl = <span class="hljs-string">'https://bsky.social/xrpc/com.atproto.server.createSession'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">blueskyAuth</span>(<span class="hljs-params">handle, appPassword</span>) {
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(createSessionUrl, {
<span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'application/json'</span> },
<span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
<span class="hljs-attr">body</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({ <span class="hljs-attr">identifier</span>: handle, <span class="hljs-attr">password</span>: appPassword })
});
<span class="hljs-keyword">if</span> (!res.<span class="hljs-property">ok</span>) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(
<span class="hljs-string">`Bluesky responded with an unexpected status: <span class="hljs-subst">${res.status}</span> <span class="hljs-subst">${<span class="hljs-keyword">await</span> res.text()}</span>`</span>
);
}
<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">json</span>(); <span class="hljs-comment">// { accessJwt, refreshJwt, did }</span>
}
</code></pre><p>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:</p>
<ul>
<li><code>accessJwt</code>: A short-lived token used for API requests.</li>
<li><code>refreshJwt</code>: A token which can be exchanged for a new <code>accessJwt</code> and
<code>refreshJwt</code> when the <code>accessJwt</code> expires.</li>
<li><code>did</code>: An identifier. The initial "d"<sup class="footnote-ref"><a id="footnote-ref-2" href="#footnote-2">[2]</a></sup> stands for "distributed", but I recently discovered that the DID <code>plc</code>
method used by Bluesky is not actually distributed at all, thanks to Christine
Lemmer-Webber's <a href="https://dustycloud.org/blog/how-decentralized-is-bluesky/">excellent article on Bluesky and decentralization</a>. I
<em>highly</em> recommend reading it to dispel some myths around how decentralized
Bluesky is, by design.</li>
</ul>
<p>My script uses the <code>accessJwt</code> as a token and the <code>did</code> as an identifier in
API requests. Since the session is only needed to post one document (and
possibly an image), there's no need to think about refreshing the token, so I
don't use the <code>refreshJwt</code>.</p>
<p>The rest of the script is about one or two API requests. When an image is to be
uploaded, that happens first. There's not much to say here beyond the size
restrictions being quite low, and being constrained to JPEGs and PNGs. I don't
think they're doing much processing of images, so I recommend removing metadata
from image uploads. The response of the image upload contains a <code>blob</code> field,
which is used as a reference to the image in the second API request in the form
of an <em><a href="https://docs.bsky.app/docs/advanced-guides/posts#images-embeds">embedding</a></em>.</p>
<p>The final request creates the "record" (the note or bookmark) on Bluesky.
There's a lot to unpack here. The <a href="https://docs.bsky.app/docs/tutorials/creating-a-post"><em>creating a post</em></a> is full of
useful samples to help figure out the anatomy of a record creation body.</p>
<p>One fascinating aspect of Bluesky is how text is composed. Rather than some sort
of markup language, it uses a more limited concept called <em><a href="https://docs.bsky.app/docs/advanced-guides/post-richtext">rich text facets</a></em>.
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-'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 <code>Buffer</code> class, which
can be used to represent strings as arrays of UTF-8 bytes. A <code>Buffer.byteLength</code>
is all it takes to get the UTF-8 size of a string.</p>
<p>Anyway, I've put lots of annotations in <a href="https://github.com/qubyte/qubyte-codes/blob/main/scripts/syndicate-to-bluesky.js">the syndication script</a>, so
hopefully it serves as a useful example!</p>
https://qubyte.codes/blog/what-happened-to-2025What happened to 2025?2026-02-06T11:45:00Z2026-02-06T11:45:00Zqubytehttps://qubyte.codes
<p>I've not posted in over a year (the whole of 2025). I don't have a readership
(there are good odds that nobody besides me will read this), so nobody has
missed my writing and I've felt no pressure to force a post.</p>
<p>It <em>is</em> 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?</p>
<p>In short, I was very busy:</p>
<ul>
<li>I started coming out as trans in late November 2024. Three people close to me
by the end of the year.</li>
<li>Over 2025 I came out to everyone else, and by the middle of April I was Aura
(hello!) full time in all contexts.</li>
<li>In late January 2025 I started on hormone replacement therapy (HRT) which
means...</li>
<li>I just passed the first full year on HRT (the anniversary which led me to
write this post)!</li>
<li>Lots, lots more related stuff (laser hair removal, electrolysis, clothes (I
haven't boymoded<sup class="footnote-ref"><a id="footnote-ref-1" href="#footnote-1">[1]</a></sup> since April) legal stuff and ID, etc.).</li>
</ul>
<p>The year was <em>a lot</em><sup class="footnote-ref"><a id="footnote-ref-2" href="#footnote-2">[2]</a></sup>. 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'm viewing it
like a birthday. In the years between realizing I'm trans and making the
decision to come out, I'd gone from the persistent feeling of wrongness and
disconnection I'd always had, to being in a very bad place. The decision to
transition was a necessary one, but the rewards astonishing. I'm healthier and
happier than I've ever been before. I'm still early in my journey, but I've come
<em>so far</em>.</p>
<p>So that's what happened in 2025, and why there's a gap in the blog posts. I
might expand on individual parts of my experience in future posts. I'm not
certain yet. While it's an extremely personal subject, every trans person's
path is different, and perhaps someone reading this will see something of
themselves which helps them. I wouldn'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'd have read something like this and connected the dots
earlier.</p>
<p>If you're reading this, and your gender is on your mind,
<a href="https://genderdysphoria.fyi/en/am-i-trans">read this</a><sup class="footnote-ref"><a id="footnote-ref-3" href="#footnote-3">[3]</a></sup>. If
we're acquainted, then you can talk to me, in confidence.</p>