Qubyte Codes 2026-02-28T10:50:53Z https://qubyte.codes/ qubyte https://qubyte.codes/blog/private-data-for-js-classes-with-weakmap Private data for JS classes with WeakMap 2015-12-30T14:35:00Z 2015-12-30T14:35:00Z qubyte https://qubyte.codes <p>Private data has always been awkward in JavaScript. It&#39;s particularly difficult when it comes to constructors, and with ES2015 recently published, classes too. Let&#39;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&#39;t touch or look at that field. Why should this be private? Private stuff is subject to change without your users needing to know about it. This field could be renamed or go away completely if you refactor, without affecting the public API. So what&#39;s the problem?</p> <p><em>You can&#39;t trust your users!</em></p> <p>This isn&#39;t meant as an insult. Your users are cunning, and if they can solve a problem without filing an issue or raising a pull request, they probably will. They have deadlines after all... If your class gets very popular, it becomes inevitable that someone is going to use your private-by-convention field to hack together a solution to a problem they&#39;re having, and you&#39;ll break their code when you change it. Changes to your public API should be clearly indicated by changes to the version number and updated documentation. The inner workings of your code on the other hand, including private data, are subject to dramatic change at any time.</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&#39;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&#39;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&#39;re using a maintained version of Node, you&#39;re good to go!</p> <h3 id="addendum">Addendum</h3> <p>It&#39;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-blog About this blog 2016-01-03T12:22:00Z 2016-01-03T12:22:00Z qubyte https://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&#39;s about a hundred lines of code, mainly stitching together other small modules. I used Node to make the generator, with the following modules:</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&#39;ll have that working on a GitHub hook soon.</p> <p>I&#39;ve no intention of posting the generator, since that would mean supporting it. It&#39;s strictly for my use. I&#39;ve linked the modules above since they&#39;ve been very useful to me. If you&#39;re thinking about setting up a blog and you&#39;re a programmer, I recommend this approach!</p> https://qubyte.codes/blog/about-this-blog-2 About this blog 2 2016-01-11T20:15:00Z 2016-01-11T20:15:00Z qubyte https://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&#39;t explain the motivation behind a lot of the choices I made when building it. I&#39;d like to do that in this post. The design and architecture of this blog is the product of what things I like in other blogs, and also those things that I find frustrating. Where a choice was not obvious, I opted for the simplest option. The point of the exercise was to get it online. Below are a few points in no particular order.</p> <h3 id="https">HTTPS</h3> <p>SSL certificates are now free thanks to <a href="https://letsencrypt.org">Let&#39;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&#39;s a little clunky if you&#39;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&#39;t currently use tracking or cookies, mainly because I don&#39;t need them. Tracking for a blog is largely narcissism. There are no sessions for a blog, so no cookies are necessary. I might track some basic data like user agent later, but that&#39;s mainly so I know what browser features I can get away with using. That sort of thing can be done server side. I don&#39;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&#39;s no JavaScript code rendering DOM. This has some nice repercussions. The browser does very little, so this is kind to batteries. Since the pages are not dynamic, the pages can be statically generated by me, and hosted as files. This avoids building HTML on the server and a database to store pre-rendered data. Just as this is kind to the browser, it&#39;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&#39;s rare, but I do come across how-to style blog posts which lack dates. This is frustrating, since it&#39;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&#39;re also making the browser download that image, which probably takes more data than the rest of the page combined. This blog doesn&#39;t do it. I want a reader to be able to start reading with no interaction at all. This blog has been tested with devices as small as an iPhone 4 to that end.</p> <h3 id="mobile-friendly">Mobile friendly</h3> <p>I&#39;ve already touched on this above. A big part of mobile friendly is handling crappy networks. Crappy give each request a good chance of failing. Since the CSS is very simple, I minify and inline it in the head of each post. This means that the typical post is just a single file, so there is as good a chance as possible that the reader will receive the whole post.</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&#39;m not sure if comments are worth the trouble. If something is really worth saying, you can always tweet to me or tweet a gist to me. If comments become desirable in the future, I can revisit them.</p> https://qubyte.codes/blog/how-i-schedule-posts-using-atd How I schedule posts using atd 2016-01-14T19:30:00Z 2016-01-14T19:30:00Z qubyte https://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">&quot;git -C /absolute/path/to/repo pull origin master&quot;</span> | at 07:00 tomorrow </code></pre><p><code>at</code> accepts input through stdin, which is why I&#39;ve echoed the command and piped it. One big gotcha is that you have to be aware of the timezone your machine is configured for. I have mine set to UTC, and schedule according to that.</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&#39;t try to better it. The purpose of this post is to show you that it exists and how simple (especially when compared with <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-classes Private methods for JS classes 2016-01-31T02:00:00Z 2016-01-31T02:00:00Z qubyte https://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">&#x27;p&#x27;</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">&#x27;&#x27;</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&#39;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&#39;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&#39;t make <code>replace</code> available, and the module doesn&#39;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">&#x27;hello&#x27;</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">&#x27;goodbye&#x27;</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">&#x27;hello&#x27;</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">&#x27;goodbye&#x27;</span>); </code></pre><p>So there you have it. There&#39;s not a lot to the pattern really. Earlier module types and patterns allow the same thing to be done. I recommend reading the <a href="https://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript">section on module patterns</a> in Addy Osmani&#39;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-javascript Interfaces for JavaScript 2016-03-13T10:45:00Z 2016-03-13T10:45:00Z qubyte https://qubyte.codes <p>I use <code>instanceof</code> a lot in JavaScript. It&#39;s very handy when writing unit tests. It&#39;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&#39;m excited about ES2015s <code>Symbol.hasInstance</code>. It allows you to tune the behaviour of <code>instanceof</code> for a class. Here&#39;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">&#x27;PositiveInteger is an interface class.&#x27;</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">&#x27;number&#x27;</span>) { <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; } <span class="hljs-keyword">if</span> (value &lt; <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">&#x27;hi&#x27;</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">&#x27;View is an interface class.&#x27;</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">&#x27;function&#x27;</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">&#x27;function&#x27;</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&#39;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&#39;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-toisu A presentation on async-await and Toisu! 2016-04-06T22:00:00Z 2016-04-06T22:00:00Z qubyte https://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&#39;ll introduce Toisu! in a blog post soon, but until then here&#39;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-set Adding missing features to Set 2016-05-22T16:30:00Z 2016-05-22T16:30:00Z qubyte https://qubyte.codes <p>ES2015 bought a <code>Set</code> constructor to JavaScript. It&#39;s pretty barebones, consisting of a constructor which creates objects with a few methods for adding, removing, checking if something is a member, and iterating over the set. Instances have the essential quality of a set; an item is a member of the set or not a member. Unlike an array, an item cannot be an element more than once. In other words you can avoid using arrays and doing a lot of <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&#39;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&#39;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>) =&gt;</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> &lt; 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&#39;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>) =&gt;</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> =&gt;</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>) =&gt;</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> =&gt;</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-version Tip: customizing npm version 2016-09-05T20:00:00Z 2016-09-05T20:00:00Z qubyte https://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&#39;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">&quot;name&quot;</span>: <span class="hljs-string">&quot;my lovely app&quot;</span>, <span class="hljs-string">&quot;version&quot;</span>: <span class="hljs-number">1.0</span><span class="hljs-number">.0</span>, <span class="hljs-string">&quot;scripts&quot;</span>: { <span class="hljs-string">&quot;version&quot;</span>: <span class="hljs-string">&quot;node update-bower-version.js &amp;&amp; git add bower.json&quot;</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">&#x27;fs&#x27;</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">&#x27;./bower&#x27;</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&#39;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-1 Progressive enhancement #1 2016-10-15T18:00:00Z 2016-10-15T18:00:00Z qubyte https://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&#39;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&#39;m a novice when it comes to CSS, so let me know if I&#39;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">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;top-header&quot;</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>...<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>...<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</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">&#x27;.top-header&#x27;</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> &gt; <span class="hljs-number">0</span>) { $header.<span class="hljs-property">classList</span>.<span class="hljs-title function_">add</span>(<span class="hljs-string">&#x27;smaller&#x27;</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">&#x27;smaller&#x27;</span>); } } <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;scroll&#x27;</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&#39;re interested in such things, watch this space!</p> https://qubyte.codes/blog/progressive-enhancement-2 Progressive enhancement #2 2016-11-12T03:00:00Z 2019-01-17T16:00:00Z qubyte https://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&#39;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-mix Promises and Node.js event emitters don't mix 2016-12-24T17:00:00Z 2016-12-24T17:00:00Z qubyte https://qubyte.codes <p>To many experienced Node developers, the title of this post will seem intuitively obvious. Nevertheless, it&#39;s useful to see what unexpected behaviour can occur when the two are used together. Here&#39;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">&#x27;events&#x27;</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">&#x27;Oh noes!&#x27;</span>)) .<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> emitter.<span class="hljs-title function_">emit</span>(<span class="hljs-string">&#x27;error&#x27;</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&#39;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&#39;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">&#x27;events&#x27;</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">&#x27;error&#x27;</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">&#x27;Oh noes!&#x27;</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">&#x27;caught&#x27;</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">&#x27;events&#x27;</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">&#x27;error&#x27;</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">&#x27;Oh noes!&#x27;</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">&#x27;Oh noes!&#x27;</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">&#x27;Oh noes!&#x27;</span>)) .<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</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&#39;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-code Making arcade controls: Arduino Leonardo code 2017-05-01T00:00:00Z 2017-05-01T00:00:00Z qubyte https://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&#39;ll look when finished isn&#39;t important at the moment. It&#39;s enough now to say that there&#39;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 &quot;the only thing I need to do&quot;, what follows is three or four times as much effort as predicted. I was surprised this time that the effort required was so low, particularly given that I&#39;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">&lt;Keyboard.h&gt;</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">&#x27; &#x27;</span> }, <span class="hljs-comment">// Fire 3</span> { <span class="hljs-number">9</span>, <span class="hljs-string">&#x27;a&#x27;</span> }, <span class="hljs-comment">// Fire 4</span> { <span class="hljs-number">10</span>, <span class="hljs-string">&#x27;s&#x27;</span> }, <span class="hljs-comment">// Fire 5</span> { <span class="hljs-number">11</span>, <span class="hljs-string">&#x27;q&#x27;</span> }, <span class="hljs-comment">// Fire 6</span> { A0, <span class="hljs-string">&#x27;1&#x27;</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> &amp;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> &amp;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-mixins Test friendly mixins 2017-07-20T13:30:00Z 2017-07-20T13:30:00Z qubyte https://qubyte.codes <p>I&#39;ve recently been attempting to code a clone of the classic game asteroids using canvas in the browser. Since this is me, I&#39;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&#39;d write a mixin function to copy a move method onto host objects.</p> <p>I&#39;m not a purist though. There&#39;s still value to a base class to encode all essential properties of all objects in the game. Extending the base class for other objects in the game makes sense for those behaviours and properties unique to the child class. For example, the ship can be controlled by the user, so such control would be defined as part of the <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&#39;m a professional JavaScript programmer, which means that each time I write something there&#39;s a little voice in my head asking me how hard it&#39;ll be to write tests for it. Applying the mixin to a simple object makes writing tests for the mixin in isolation possible. In mocha:</p> <pre><code class="language-javascript"><span class="hljs-title function_">describe</span>(<span class="hljs-string">&#x27;movable&#x27;</span>, <span class="hljs-function">() =&gt;</span> { <span class="hljs-title function_">it</span>(<span class="hljs-string">&#x27;appends a single method &quot;move&quot;&#x27;</span>, <span class="hljs-function">() =&gt;</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">&#x27;function&#x27;</span>); }); <span class="hljs-title function_">it</span>(<span class="hljs-string">&#x27;updates the position given a dt&#x27;</span>, <span class="hljs-function">() =&gt;</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&#39;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&#39;s repetitive though. It would be nicer to have some mechanism to ask a mixin if an object has had the mixin applied to it. Then test for a class can use a single test for each mixin to check that the mixin has been applied, and all the repetition can be avoided.</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&#39;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&#39;t work because the mixin is applied to the prototype and no reference to an instance will be stored by the mixin. We can fix this by climbing up the prototype chain and checking each prototype object:</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">&#x27;is movable&#x27;</span>, <span class="hljs-function">() =&gt;</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&#39;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&#39;t work because <code>Function.prototype[Symbol.hasInstance]</code> is not writable. When appending a property to something, the field you&#39;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">&#x27;is movable&#x27;</span>, <span class="hljs-function">() =&gt;</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-element My first custom element 2017-11-16T01:45:00Z 2017-11-16T01:45:00Z qubyte https://qubyte.codes <p>After some years of browser vendors working out what web components should look like, they&#39;re almost ready for the prime time. The part which I find most intriguing (custom elements) has finally stabilised. With custom elements, you can make new HTML elements which have custom behaviour which you define using JavaScript. In this post I&#39;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&#39;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">&#x27;count&#x27;</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">&#x27;none&#x27;</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">&#x27;one&#x27;</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">&#x27;a couple&#x27;</span>; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count &lt; <span class="hljs-number">5</span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">&#x27;a few&#x27;</span>; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count &lt; <span class="hljs-number">10</span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">&#x27;several&#x27;</span>; } <span class="hljs-keyword">else</span> { <span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">&#x27;lots&#x27;</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&#39;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&#39;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">&#x27;fuzzy-count&#x27;</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">&lt;<span class="hljs-name">fuzzy-count</span> <span class="hljs-attr">count</span>=<span class="hljs-string">&quot;3&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">fuzzy-count</span>&gt;</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">&#x27;fuzzy-count&#x27;</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&#39;d have an element with no <code>count</code> attribute, and so we&#39;ve missed our chance since all the logic is in the constructor. It turns out that the constructor is the wrong place for this stuff.</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">&#x27;count&#x27;</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">&#x27;&#x27;</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">&#x27;none&#x27;</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">&#x27;one&#x27;</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">&#x27;a couple&#x27;</span>; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count &lt; <span class="hljs-number">5</span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">&#x27;a few&#x27;</span>; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (count &lt; <span class="hljs-number">10</span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">&#x27;several&#x27;</span>; } <span class="hljs-keyword">else</span> { <span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">&#x27;lots&#x27;</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 &quot;connected&quot; to avoid the error when constructing the element in JS. Connected is called when the element is inserted into the document, or the element is upgraded by custom element registration.</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">&#x27;fuzzy-count&#x27;</span>); fuzzy.<span class="hljs-title function_">setAttribute</span>(<span class="hljs-string">&#x27;count&#x27;</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&#x27;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">&#x27;count&#x27;</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">&#x27;fuzzy-count&#x27;</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">&#x27;count&#x27;</span>, <span class="hljs-number">10</span>); </code></pre><p>Overall I&#39;m impressed. I&#39;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-arrayfrom Tip: Array.from 2017-12-01T20:00:00Z 2017-12-01T20:00:00Z qubyte https://qubyte.codes <p>SPOILER ALERT: If you&#39;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&#39;t worry, I won&#39;t give solutions away). The first challenge involves summing numbers in a sequence. The sequence itself is too long to express as an integer, and that wouldn&#39;t be particularly useful for the task anyway. The most convenient form is probably an array of integers. The sequence is given as raw text, and the quickest way to get it into JS is as a string.</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&#39;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 &lt; 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&#39;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> =&gt;</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> =&gt;</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&#39;s a little easier for humans to parse). It would be nice to avoid the explicit mapping though, right? The solution I settled upon was to use the little-known second parameter to <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> =&gt;</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&#39;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-2 Advent of Code 2017 day 20 task 2 2018-01-02T20:50:00Z 2018-01-02T20:50:00Z qubyte https://qubyte.codes <p>SPOILER ALERT: If you&#39;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&#39;re asked to determine how many particles remain after all collisions have occurred. This one was fun for me because the solution I came up with involved a little mathematics to avoid brute forcing the answer.</p> <p>At this point in the challenges, writing a parser for an input file is something I&#39;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">&quot;p&quot;</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">&quot;v&quot;</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">&quot;a&quot;</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">&quot;p&quot;</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">&quot;v&quot;</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">&quot;a&quot;</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">&quot;p&quot;</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">&quot;v&quot;</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">&quot;a&quot;</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 &lt; 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 &lt; 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&#39;ve used the index of each particle in the input array as its ID for convenience. The snippet grabs all pairs and uses the function <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&#39;ll come back to <code>collisionTime</code> later, because that&#39;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>) =&gt;</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) &amp;&amp; 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&#39;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&#39;ve modelled all collisions as pairs, so a collision between three or more particles will look like multiple collisions of pairs at the same time. In order to properly count these, particle IDs to be removed are held in the <code>toDelete</code> set and removed after all collisions at a given time step have been checked.</p> <p>Let&#39;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&#39;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&#x27;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 &lt; d || nextV &lt; 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&#39;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 &amp;= \Delta a \\ b &amp;= 2\Delta v_0 + \Delta a \\ c &amp;= 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 &amp;= \Delta a \\ b &amp;= 2\Delta v_0 + \Delta a \\ c &amp;= 2\Delta p_0 \end{align*}</annotation></semantics></math><p>I&#39;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 &gt;&gt;&gt; <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 &lt; <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&#x27;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&#39;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-beginners Essential tools for JavaScript beginners 2018-02-11T17:00:00Z 2018-02-11T17:00:00Z qubyte https://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&#39;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&#39;s useful out of the box. Many editors can integrate with tools, but none quite as nicely. I recommend installing it and using its extensions system to install the ESLint extension and the Editorconfig extension (more on this in the sections below). The installation of ESLint into your project itself is covered in a later section.</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 &quot;Integrated Terminal&quot;.</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&#39;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&#39;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&#39;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&#39;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&#39;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">&quot;extends&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;qubyte&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;env&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;browser&quot;</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&#39;re &quot;extending&quot; 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&#39;re using newer JavaScript features, try setting <code>extends</code> to <code>&quot;qubyte/ES2017&quot;</code>.</p> <p>VSCode has an &quot;extension&quot; for ESLint. On the left of a VS code window there are several icons. Click on the one for extensions (hover to see) and search for &quot;ESLint&quot; to find it, and install. From the view menu, click on &quot;Problems&quot; 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&#x27;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&#39;s always worth having this file in your projects.</p> <p>VS Code has an extension for Editorconfig files. To install it, open &quot;Extensions&quot; via the icon on the left or via the view menu, and search for &quot;Editorconfig&quot;. The most downloaded (probably the top hit) is the one you want. Once this is installed, there&#39;s nothing left to do. VSCode will honour Editorconfig files whenever it finds them. It does this by applying Editorconfig rules to a file when it is saved.</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-3 About this blog 3 2018-02-17T02:40:00Z 2018-02-17T02:40:00Z qubyte https://qubyte.codes <p>It&#39;s been a while since <a href="/blog/about-this-blog-2">the last entry</a> about how I&#39;ve built this blog. Since it is constantly evolving, now seems as good a time as ever to write about some of the changes I&#39;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&#39;ve said before that I&#39;m not a fan of comments. They&#39;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&#39;m monitoring the logs to make sure it doesn&#39;t do anything unintentional. It&#39;ll probably go active this weekend once I&#39;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=&quot;webmention&quot;</code>, which is found in the head:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/webmention&quot;</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">&quot;webmention&quot;</span>&gt;</span> </code></pre><p>Since I don&#39;t expect a large volume of mentions, I&#39;ll add each to the front matter of the blog post being mentioned. When a post has mentions, a section listing them is added at the end of the post.</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&#39;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&#39;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&#39;s <code>rel=&quot;payment&quot;</code>. It doesn&#39;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&#39;ve added a link tag with this attribute to the blog as well. I don&#39;t expect it to get used, but one can always hope! Perhaps I&#39;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&#39;t know which anchors will link out to external domains, so it compiles then to <code>&lt;a&gt;</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=&quot;noopener&quot;</code> attribute. To do this, I&#39;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">&#x27;marked&#x27;</span>); <span class="hljs-keyword">const</span> urlResolve = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;url&#x27;</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">&#x27;https://qubyte.codes&#x27;</span>; renderer.<span class="hljs-property">link</span> = <span class="hljs-function">(<span class="hljs-params">href, title, text</span>) =&gt;</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">&#x27;&lt;a &#x27;</span>, <span class="hljs-string">&#x27;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; &#x27;</span>); }; marked.<span class="hljs-title function_">setOptions</span>({ <span class="hljs-comment">// Some unrelated stuff skipped.</span> renderer }); </code></pre><p>It&#39;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&#39;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">&#x27;mathjax-node&#x27;</span>); <span class="hljs-keyword">const</span> xml2js = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;xml2js&#x27;</span>); <span class="hljs-keyword">const</span> marked = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;marked&#x27;</span>); <span class="hljs-keyword">const</span> highlight = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;highlight.js&#x27;</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&#x27;t call MathJax</span> <span class="hljs-comment">// in here (it&#x27;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">&#x27;mathematics&#x27;</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">&#x27;mathematics&#x27;</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">&#x27;TeX&#x27;</span>, <span class="hljs-attr">svg</span>: <span class="hljs-literal">true</span> }, <span class="hljs-function">(<span class="hljs-params">{ errors, svg }</span>) =&gt;</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 &quot;mathematics&quot; 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>) =&gt;</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">&#x27;mathematics&#x27;</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&#39;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 &quot;install&quot; it on android phones. The caching strategy I was using was a bit wonky though. Any time hashes changed (common when I updated CSS, since each update to the CSS gets a new filename for caching reasons), a visit to any page would lead to the entire blog being downloaded. In the end I came to the conclusion that being a progressive web app was not good for readers bandwidth in this case and the only reason for me to have it was vanity.</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-worker Putting back the service worker 2018-03-08T01:20:00Z 2018-03-08T01:20:00Z qubyte https://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&#39;s good to give CSS a long cache time. In order to do this, but still deliver fresh CSS without readers having to wait for it, I give the CSS file a URI including a hash of its content. If the CSS is updated, its URI changes, as does the <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&#39;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&#39;t mesh well with the caching strategy of the service worker I added. The worker would download the entire blog, HTML and CSS, the first time a user navigated to it. The service worker was generated by <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&#39;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&#39;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&#39;re stuck <em>outside</em> of a tunnel and I&#39;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&#39;ll ever be necessary).</p> <p>I&#39;ll still need to do <em>some</em> work though. As mentioned in that post, cleanup isn&#39;t addressed. I&#39;m happy for HTML to be cached indefinitely, but for the CSS one way to clean it up might be to remove it once it is older than every HTML entry in the cache. For particularly large resources such as images, a relatively short cache time and good alt-text might be a good approach...</p> <p>In the meantime, I&#39;ve borrowed the service worker code from that post mostly unchanged (most changes are just to align it with the style enforced by my ESLint config). The one small addition is to allow server sent events (<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">&#x27;GET&#x27;</span>) { <span class="hljs-keyword">return</span>; } </code></pre><p>I&#39;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">&#x27;Accept&#x27;</span>); <span class="hljs-keyword">if</span> (request.<span class="hljs-property">method</span> !== <span class="hljs-string">&#x27;GET&#x27;</span> || acceptHeader.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;text/event-stream&#x27;</span>)) { <span class="hljs-keyword">return</span>; } </code></pre><p>You can see the current <a href="/sw.js">service worker code I&#39;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-beige A brighter shade of beige 2018-04-02T22:00:00Z 2018-04-02T22:00:00Z qubyte https://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&#39;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&#39;t appear to be a large departure from what was there before. I&#39;ve brightened the background colour, added distinct colour to the navigation bar and made it span the width of the page, removed text justification, and lightened text to a blue complimentary to the background. The look as been <a href="https://twitter.com/cassiecodes/status/980818410010562560">described as <em>Battenberg chic</em></a>, which I rather like! If you&#39;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&#39;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&#39;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&#39;t ratified yet anyway. I&#39;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-webmentions Update on webmentions 2018-04-04T19:10:00Z 2018-04-04T19:10:00Z qubyte https://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&#39;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&#39;t work out and the issue is now closed. I&#39;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-workers Content-Security-Policy and service workers 2018-04-23T23:00:00Z 2018-04-23T23:00:00Z qubyte https://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&#39;t pertinent):</p> <pre><code class="language-properties"><span class="hljs-attr">Content-Security-Policy</span>: <span class="hljs-string">default-src &#x27;self&#x27;; 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&#39;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 &#x27;&lt;URL&gt;&#x27; because it violates the following Content Security Policy directive: &quot;default-src &#x27;self&#x27;&quot;. Note that &#x27;connect-src&#x27; was not explicitly set, so &#x27;default-src&#x27; 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&#39;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">&quot;/sw.js&quot;</span> <span class="hljs-section">[headers.values]</span> <span class="hljs-attr">Content-Security-Policy</span> = <span class="hljs-string">&quot;connect-src *;&quot;</span> </code></pre><p>But what&#39;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">&#x27;fetch&#x27;</span>, <span class="hljs-function"><span class="hljs-params">fetchEvent</span> =&gt;</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&#39;m effectively proxying the request. I&#39;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&#39;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&#39;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&#39;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-svg A Battenberg in SVG 2018-06-10T18:14:20Z 2018-06-10T18:14:20Z qubyte https://qubyte.codes <p>In celebration of the Battenberg theme, here is an animated Battenberg! It&#39;s made of two SVG paths composed of lines and arcs. These are calculated using three angles and a bucket load of trigonometry (I&#39;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-entries Automatic announcement of new blog entries 2018-08-01T18:40:00Z 2018-08-01T18:40:00Z qubyte https://qubyte.codes <p>It occurred to me a couple of days ago that it&#39;d be neat to build a glitch to announce new blog posts. Since I deploy this blog by pushing to a master branch on GitHub, creation of a blog post is somewhat less obvious than when publishing on a platform like wordpress or medium, so I needed to figure out another approach.</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&#39;s not difficult to filter the sitemap down to only blog entires. By comparing a sitemap before and after deployment, it&#39;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&#39;s pretty cool to automate things using webhooks (glitch in particular shines for this use-case), the thing which really stood out was how the sitemap made it all fairly straight forward. Originally I added the sitemap almost as an afterthought, since it was cheap to do with a template and is good for search engines. I&#39;m glad I made it a newline separated list rather than an XML monstrosity because it&#39;s so easy to parse this way.</p> https://qubyte.codes/blog/routes-of-restful-services-with-nested-resources Routes of RESTful services with nested resources 2018-10-19T19:00:30Z 2018-10-19T19:00:30Z qubyte https://qubyte.codes <p>Nested resources are common in real life, and when you&#39;re a programmer working on RESTful APIs you may have noticed that there are often trade-offs when it comes to formatting the routes of nested resources.</p> <p>Let&#39;s say we&#39;re building an API for carpenters. Their guild wants a way to help the carpenters manage their clients, and the items which they&#39;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&#39;re rarely going to want to see clients which aren&#39;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&#39;ll want to filter it by the client. We can use query parameters to supply additional context, but this seems a bit clumsy. Let&#39;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&#39;s pretty clear now that these are nested resources. It&#39;s no longer possible to select a collection without refining it by its parent resources.</p> <p>The problem now is that it&#39;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&#39;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&#39;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-rust Parsing input from stdin to structures in Rust 2019-01-01T23:30:00Z 2019-04-22T13:40:00Z qubyte https://qubyte.codes <p>I&#39;ve been working on this post for a while, but about a month ago my firstborn arrived, and he&#39;s been getting the lion&#39;s share of my attention. I start work again tomorrow, so I decided to just publish this post and be done with it. Apologies if it&#39;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&#39;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&#39;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 &lt; input.txt </code></pre><p>On day 10 the data looked like</p> <pre><code class="language-plaintext">position=&lt;-10427, -42253&gt; velocity=&lt; 1, 4&gt; position=&lt; 21343, 42515&gt; velocity=&lt;-2, -4&gt; position=&lt;-10417, -52846&gt; velocity=&lt; 1, 5&gt; </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>&lt;Particle&gt; = <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&#39;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&#39;ve skipped a big chunk of what&#39;s going on in <code>#2</code>. How do I parse lines into a structure which I&#39;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&lt;Particle&gt;</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&#39;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&#39;s not a lot to see here. It&#39;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&#39;t the correct integer type to use).</p> <p>It&#39;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&#39;m just going to put it below here since there&#39;s a lot I don&#39;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>(&amp;<span class="hljs-keyword">self</span>, f: &amp;<span class="hljs-keyword">mut</span> Formatter) <span class="hljs-punctuation">-&gt;</span> fmt::<span class="hljs-type">Result</span> { <span class="hljs-built_in">write!</span>(f, <span class="hljs-string">&quot;Unable to parse to a Particle.&quot;</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>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> &amp;<span class="hljs-type">str</span> { <span class="hljs-string">&quot;Unable to parse to a Particle.&quot;</span> } <span class="hljs-keyword">fn</span> <span class="hljs-title function_">cause</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Option</span>&lt;&amp;Error&gt; { <span class="hljs-literal">None</span> } } <span class="hljs-keyword">impl</span> <span class="hljs-title class_">From</span>&lt;ParseIntError&gt; <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">-&gt;</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: &amp;<span class="hljs-type">str</span> = <span class="hljs-string">r&quot;&lt;\s*(-?\d+),\s*(-?\d+)&gt;.*&lt;\s*(-?\d+),\s*(-?\d)&gt;&quot;</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: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-keyword">Self</span>, <span class="hljs-keyword">Self</span>::<span class="hljs-literal">Err</span>&gt; { 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&#39;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&#39;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">&quot;&lt;\s*(?P&lt;position_x&gt;-?\d+),\s*(?P&lt;position_y&gt;-?\d+)&gt;.*&lt;\s*(?P&lt;velocity_x&gt;-?\d+),\s*(?P&lt;velocity_y&gt;-?\d)&gt;&quot;</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&#39;s it! Errors produced (or lack thereof) might be quite different, I&#39;ve not checked it out in detail yet. For the purposes of the task though, it works well. Named capture groups isolate elements by name, and the corresponding type of the field in the particle <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-resolution My new year resolution 2019-01-16T23:27:12Z 2019-01-16T23:27:12Z qubyte https://qubyte.codes <p>My resolution this year was to work on my Japanese speaking ability.</p> <p>I&#39;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 &quot;progress&quot; and &quot;done&quot; mean for it. A vague promise to get better at something does neither.</p> <p>So, how do I rescue it?</p> <p>It&#39;s never too late! I&#39;m only 16 days into the year. Lots of time to achieve my goal, as long as I can define it! I also have to be realistic about my spare time. My son was born at the end of last year, and while he&#39;s the biggest reason for me to improve my Japanese (we want him to be bilingual), he&#39;s also a time sink (in a good way!)</p> <p>First I must ask myself what&#39;s been holding me back. I&#39;ve been learning Japanese on and off since around 2001, and I still can&#39;t hold a conversation. My listening ability by far outstrips my ability to speak, and that imbalance is revealing. I think the problem stems from three things; perfectionism, laziness, and role-play.</p> <p>Perfectionism is easy to explain. I&#39;m a lapsed physicist and a programmer. Both fields emphasise precision. I dislike making mistakes, even though I know that making them is the faster path to learning.</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&#39;t write some code to learn it for me. Not yet anyway.</p> <p>Role-play is more difficult to explain. A language isn&#39;t just words. It has a personality, and you have to act it as much as you speak it. I&#39;m bad at role-play. It terrifies me.</p> <p>These three issues are holding me back, and I don&#39;t think there&#39;s a solution to that. I&#39;m going to have to push through. I think that defining what progress looks like for learning to speak more Japanese will help with these issues, and also rescue my resolution.</p> <p>So, how should I define progress? What does measurable look like for the ability to speak a language? I&#39;d love to know your thoughts!</p> https://qubyte.codes/blog/a-recent-contribution-i-made-for-nodejs A recent contribution I made for Node.js 2019-02-27T23:05:00Z 2019-02-27T23:05:00Z qubyte https://qubyte.codes <p>I&#39;ve made a couple of small contributions to Node.js in the past. These were quite esoteric, and unlikely to be discovered or noticed by most developers. Recently I made <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">&#x27;Hello, world!&#x27;</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">&#x27;Hello, world!&#x27;</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">&#x27;Hello, world!&#x27;</span>); } </code></pre><p>This makes the vanilla Node API for server responses just a little more friendly.</p> https://qubyte.codes/blog/weeknotes-1 Weeknotes #1 2019-03-03T14:00:00Z 2019-03-03T14:00:00Z qubyte https://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&#39;s first meeting since November (it&#39;s a small group and two of us had babies arrive a day apart), and the first held in the evening. We were shown some advanced code for handling game assets, and some hardware which can run embedded Rust. I&#39;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&#39;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&#39;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&#39;ve been thinking about books and records. I have lots of books. I&#39;m thinking of donating the fiction ones, but some of the text books I&#39;d like to keep around and have handy when the whim to pick one up takes me. There&#39;s a perfect little space in my home office to place a small book case, but I don&#39;t want the usual sort of bookcase, I want one with angled shelves so you can actually see what&#39;s on them while still being a short piece of furniture.</p> <p>There&#39;s a mid-century modern design that caught my eye, but they&#39;re rare in the UK (they appear to have been more popular in the US). Since I can&#39;t find one, I figured I&#39;d come up with a flat-pack version which slots together made of birch plywood, and outsource the machining. If I decide to do it I&#39;ll post pictures here!</p> https://qubyte.codes/blog/weeknotes-2 Weeknotes #2 2019-03-17T23:45:11Z 2019-03-17T23:45:11Z qubyte https://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-3 Weeknotes #3 2019-03-24T23:10:00Z 2019-03-24T23:10:00Z qubyte https://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&#39;s possible to select a theme before a paint, avoiding an awkward flash of the wrong theme. This may not news to anyone but me. I&#39;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&#39;ve baked a red velvet cake in a tray, but it came out as a regular chocolate cake so I&#39;m going to use more red colouring for the next try. I&#39;ll post a picture if I ever actually make it.</p> <p>I&#39;ve noticed folks I know are starting up, or returning to, their personal sites. Just like this blog, the majority of them are being reborn as static sites deployed by <a href="https://www.netlify.com/">Netlify</a>. It&#39;s good to know that this solution appeals to more folk than just me. It&#39;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=&quot;tag&quot;</code></a> attribute. I updated the bot to download the new entry and select <code>rel=&quot;tag&quot;</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&#39;re painful, and can lead to a fever. We&#39;ve spent most of this weekend trying to keep him comfortable, the fever low, and the injection sites as pain free as possible. He&#39;s almost back to his usual cheerful self now. It should go without saying that these vaccinations are well worth a short period of discomfort.</p> https://qubyte.codes/blog/the-code-for-this-blog-is-now-public The code for this blog is now public 2019-03-28T19:26:08Z 2019-03-28T19:26:08Z qubyte https://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&#39;ve <a href="/tags/aboutthisblog">written at length</a> about how I&#39;ve built this, and having the code makes it easier to point to particular lines. I hope it&#39;ll inspire <em>you</em> to do the same!</p> https://qubyte.codes/blog/how-i-schedule-posts-using-github-actions How I schedule posts using GitHub Actions 2019-06-15T22:00:00Z 2019-06-15T22:00:00Z qubyte https://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&#39;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&#39;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&#39;s in the past, the script moves the post to the published posts directory (at which point Netlify kicks in). The script takes advantage of the token provided by the <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&#39;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&#39;s a good deal more involved and I don&#39;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-you A new service to handle webmention dispatch for you 2019-06-18T23:45:00Z 2019-06-18T23:45:00Z qubyte https://qubyte.codes <p>I really like webmentions. They provide a way to let folk know that you&#39;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&#39;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&#39;ve <a href="/blog/about-this-blog-3">written about before</a>. It&#39;s closely coupled to the URL structure of my blog and how it compiles webmentions into pages, so I&#39;ll stick with it but it&#39;s not so useful for others except as a reference.</p> <p>If you&#39;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&#39;d use it! You can read more about it <a href="https://remysharp.com/2019/06/18/send-outgoing-webmentions">in Remy&#39;s own words</a>.</p> https://qubyte.codes/blog/cleaner-scheduled-posts-publication Cleaner scheduled posts publication 2019-06-27T17:00:00Z 2019-06-27T17:00:00Z qubyte https://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&#39;s no way to move a file using the Contents API, so the create-then-delete is necessary. This leads to two commits rather than just one. It may be possible to do it in a single commit using the Trees API, but that&#39;s a good deal more involved and I don&#39;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&#39;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-dispatch Updating webmention dispatch 2019-07-04T18:00:00Z 2019-07-04T18:00:00Z qubyte https://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&#39;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&#39;s already sent mentions for. This means that each post is only scanned once after publication. That avoids the issue of filtering out certain URLs (the rendered webmentions of others for example) since a newly published post won&#39;t have any mentions yet. It&#39;s also a little more portable. If you like the idea of using a sitemap.txt to drive a record of which posts have been handled, then <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-mode Dark mode 2019-10-12T01:30:00Z 2019-10-12T01:30:00Z qubyte https://qubyte.codes <p>That&#39;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&#39;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&#39;s becoming more common to see OLED screens in the wild, and these use less energy for black pixels since OLED pixels are self illuminating. I settled on using black for the background, and borrowing my light mode background colour for text and borders.</p> <p>The additions to the CSS aren&#39;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">&quot;.svg&quot;</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> &gt; <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&#39;s appropriate in this case.</p> https://qubyte.codes/blog/indiewebcamp-brighton-2019 IndieWebCamp Brighton 2019 2019-10-21T16:20:00Z 2019-10-21T16:20:00Z qubyte https://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&#39;m not so interested in tracking where I go, my weight, and so forth, though that might change in the future. The discussion eventually focussed on how data should be summarized.</p> <p>The local first discussion was one I was note taking for. I&#39;m using my old MacBook Air since I just finished a job and haven&#39;t started my new one yet, so I&#39;m between work laptops. The MBA is nearly 10 years old and still going strong, but it has a Japanese keyboard layout, and after being on a British layout for the last few years my ability to touch type is a little hampered. I mostly managed to keep up but missed the discussion since my focus was on taking notes. Where local first seems to shine is in content creation. For example, I could write a post or a note on a train with bad mobile coverage, and my device would stash the content and POST it whenever it next connects. <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&#39;t singular in a page. The latter has a potentially confusing resolution algorithm though. The issue of actually parsing licenses was avoided. We were mostly concerned with how to use multiple licenses within a page.</p> <p>The final session on the day was about integrating micropub with Apple shortcuts. I found this extremely interesting. I&#39;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&#39;re used in. What I implemented is fairly primitive. The shortcuts only use the URL of the current page. There&#39;s no elegant way to get hold of the title of a page like Omnibear can, but I&#39;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&#39;m enjoying the fruits of my work on the second day already. Likes and replies are more social than the notes and bookmarks I had previously, so I hope this leads to more interaction with other IndieWeb folk.</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&#39;ll be able to post pictures and not just walls of text!</p> https://qubyte.codes/blog/ffconf-2019 ffconf 2019 2019-11-15T13:16:33Z 2019-11-15T13:16:33Z qubyte https://qubyte.codes <p>The day was packed with interesting and diverse talks, and there&#39;s just too much to talk about in a single post. It&#39;s definitely worth searching around for other blog posts to see other&#39;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&#39;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&#39;s a quick Conway&#39;s Game of Life demo (requires JavaScript to be enabled). It loops around (so the world looks square but it&#39;s really a torus). It&#39;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-spokes Generative art piece: domains of points with spokes 2019-11-30T21:15:00Z 2019-11-30T21:15:00Z qubyte https://qubyte.codes <p>The graphic below is generated randomly and rendered as an SVG. Occasionally it glitches, but that&#39;s all part of the fun! It was inspired by a graphic seen in <a href="https://charlottedann.com/">Charlotte Dann&#39;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-spokes The maths of Domains of points with spokes 2019-12-02T00:00:00Z 2019-12-02T00:00:00Z qubyte https://qubyte.codes <p>It&#39;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&#39;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&#39;ll call these spokes) radiated outwards until they reached the edge of the domain of a point. The &quot;domain&quot; as I call it is an area around a point bounded by the half way lines between it and neighbouring points, or the bounding box of the image.</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&#39;ve drawn spokes radiating from the point on the left up to the domain edge shared by both points. A way of looking at this is to draw the domain boundary.</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&#39;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&#39;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&#39;re looking for a length, it&#39;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&#39;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 &amp;= r \cos \theta\\ y - y_0 &amp;= 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 &amp;= r \cos \theta\\ y - y_0 &amp;= 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 &amp;= 0\\ x &amp;= x_\text{max}\\ y &amp;= 0\\ y &amp;= 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 &amp;= 0\\ x &amp;= x_\text{max}\\ y &amp;= 0\\ y &amp;= 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 &amp;= \frac{-x_0}{\cos\theta}\\ r &amp;= \frac{x_\text{max} - x_0}{\cos\theta}\\ r &amp;= \frac{-y_0}{\sin\theta}\\ r &amp;= \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 &amp;= \frac{-x_0}{\cos\theta}\\ r &amp;= \frac{x_\text{max} - x_0}{\cos\theta}\\ r &amp;= \frac{-y_0}{\sin\theta}\\ r &amp;= \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&#39;ve added a gap between spoke ends (both with their point and the boundary) to make the graphic look as I remember it. Points are not drawn.</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-bubbles Generative art piece: bubbles 2020-06-17T22:00:00Z 2020-06-17T22:00:00Z qubyte https://qubyte.codes <p>An experiment into generating SVG circles which don&#39;t overlap. It was timeboxed to an hour, so it&#39;s a little rough around the edges (but I think that adds to its charm).</p> https://qubyte.codes/blog/superimposed-triangular-grids superimposed triangular grids 2020-08-21T16:30:00Z 2020-08-21T16:30:00Z qubyte https://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&#39;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-grids superimposed hexagonal grids 2020-08-22T13:30:00Z 2020-08-22T13:30:00Z qubyte https://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&#39;t pretty (a mish-mash of paths) but it gets the job done. I&#39;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-marked Custom markdown blocks with marked 2020-10-09T21:45:00Z 2020-10-09T21:45:00Z qubyte https://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&#39;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">&#x27;paragraph&#x27;</span> &amp;&amp; raw.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">&#x27;$$\n&#x27;</span>) &amp;&amp; raw.<span class="hljs-title function_">endsWith</span>(<span class="hljs-string">&#x27;\n$$&#x27;</span>)) { token.<span class="hljs-property">type</span> = <span class="hljs-string">&#x27;code&#x27;</span>; token.<span class="hljs-property">lang</span> = <span class="hljs-string">&#x27;mathematics&#x27;</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">&#x27;mathematics&#x27;</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&#39;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&#39;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&#39;s a common extension to place LaTeX code inside <code>$$</code> delimited blocks. Even if you&#39;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>&#39;markdown&#39;</code> language. Child tokens are removed because the content shouldn&#39;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>&#39;mathematics&#39;</code> language. The code parameter received by it is the text we set on the token, so it&#39;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-plugin Dispatching Webmentions with a Netlify build plugin 2021-02-27T15:45:00Z 2023-02-20T23:23:00Z qubyte https://qubyte.codes <p>This site uses a static site generator to build plain HTML pages. Since there&#39;s no database to add, update, or delete pages from, determining when to dispatch mentions can be challenging! Here&#39;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&#39;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&#39;s important to wait for the deployment because the receiver of a mention may automatically check the content of the source URL to know what happened.</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-v3 Tip: Connecting to localstack S3 using the JavaScript AWS SDK v3 2021-03-26T18:30:51Z 2021-03-26T18:30:51Z qubyte https://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">&quot;region&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;eu-west-1&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;endpoint&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;http://localhost:4566&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;s3ForcePathStyle&quot;</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>&lt;bucket&gt;.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>&lt;bucket&gt;.localhost</code>. This&#39;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&#39;t practical).</p> <p>Unfortunately this field doesn&#39;t exist for v3, and after searching I didn&#39;t find all that much except for a couple of mentions of the v3 JS SDK no longer supporting it.</p> <p>Fortunately it&#39;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">&#x27;@aws-sdk/client-s3&#x27;</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">&#x27;eu-west-1&#x27;</span>, <span class="hljs-comment">// The value here doesn&#x27;t matter.</span> <span class="hljs-attr">endpoint</span>: <span class="hljs-string">&#x27;http://localhost:4566&#x27;</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">&quot;3.9&quot;</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">&quot;4566-4583:4566-4583&quot;</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">&quot;./.localstack:/tmp/localstack&quot;</span> <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;/var/run/docker.sock:/var/run/docker.sock&quot;</span> </code></pre> https://qubyte.codes/blog/pastel-migraine-auras Pastel migraine auras 2021-10-14T08:10:00Z 2021-10-14T08:10:00Z qubyte https://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&#39;re perceptually nice together. Feature detection is used to render using LCH colours when the browser supports them (Safari only at the time of writing), or to pick a close colour in RGB space when LCH is not supported (the code for this is based on code in <a href="https://github.com/d3/d3-color">d3-color</a>). Refreshing generates a new patchwork.</p> https://qubyte.codes/blog/procedural-christmas-cards Procedural Christmas cards 2021-12-07T16:01:05Z 2021-12-07T16:01:05Z qubyte https://qubyte.codes <p>Below is a procedural snowflake. I&#39;m using a little code to create Christmas cards this year, and I wanted each to be unique! If you received a card from me, you may see something like <code>?seed=1234567890</code> in the URL bar. That will be the random seed which generated your snowflake (and it&#39;s yours to keep). To see a random snowflake, remove everything after the question mark and hit enter. Refresh the page to see a fresh random snowflake!</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/marqdown Marqdown 2022-05-15T09:55:00Z 2023-06-19T23:20:00Z qubyte https://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>&lt;html&gt;</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">&lt;<span class="hljs-name">p</span>&gt;</span> The text between here <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ja"</span>&gt;</span>今日は<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span> and here is in Japanese. <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</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>&lt;li&gt;</code> to eliminate a span:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>English<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ja"</span>&gt;</span>日本語<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>English<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</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>&lt;em&gt;</code>:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">em</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ja"</span>&gt;</span>すごい<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</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>&lt;mark&gt;</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">&lt;<span class="hljs-name">p</span>&gt;</span> Boring <span class="hljs-tag">&lt;<span class="hljs-name">mark</span>&gt;</span>important<span class="hljs-tag">&lt;/<span class="hljs-name">mark</span>&gt;</span> boring again. <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</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>&lt;math&gt;</code> elements are more appropriate than <code>&lt;svg&gt;</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-habit It's time to build a study habit 2022-05-28T12:00:00Z 2022-07-09T15:05:00Z qubyte https://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&#39;ve said it so many times in the past, but I really need to build a study habit which is substantial enough to improve my proficiency, but light enough that I can sustain it. Not just flash cards too.</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&#39;ve been meaning to start reading which I think may work are <span lang="ja">よつばと!</span> and <span lang="ja">しろくまカフェ</span>. They&#39;re gentle and humorous stories which I think will work well as spring becomes summer here. I also have some graded readers on the shelf I can start on.</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&#39;m not just talking crap, it&#39;s time for an update!</p> <p>Since I wrote the above post, I&#39;ve subscribed to <a href="https://www.wanikani.com">WaniKani</a>, and bought three volumes of each of the manga I mentioned above. I&#39;m reading <span lang="ja">よつばと!</span> first because it&#39;s a little more accessible at my level (and I&#39;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&#39;ve found WaniKani to be especially effective. Over the years (decades?) I&#39;ve tried a lot of flash card apps and approaches, but avoided WaniKani since my kanji wasn&#39;t too bad and I didn&#39;t want to spend a disproportionate amount of effort on it at the cost of time I could have put into other study. In hindsight, this was a mistake. The approach WaniKani takes is designed specifically for Japanese, and also includes relevant vocabulary. I can already tell it&#39;s helping me as I read manga.</p> <p>To hold myself to account I&#39;m logging my <a href="/study-sessions/">study sessions</a>. I&#39;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-box Controlling ruby annotation positioning and appearance with pure CSS and a select box 2022-11-07T12:05:00Z 2022-11-07T12:05:00Z qubyte https://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>&lt;mark&gt;</code> elements which I omit here):</p> <pre><code class="language-css"><span class="hljs-comment">/* Select &lt;body&gt; when an element with class furigana-position has an &lt;option&gt; * 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 &lt;body&gt; when an element with class furigana-position has an &lt;option&gt; * 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 &lt;rt&gt; and &lt;rp&gt; elements in the &lt;body&gt; when an element with class * furigana-position has an &lt;option&gt; 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> =&gt;</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-maps Progressively enhanced caching of JavaScript modules without bundling using import maps 2022-11-23T09:00:00Z 2023-09-24T11:50:00Z qubyte https://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">&quot;This Talk is Under Construction: a love letter to the personal website&quot;</a> <a href="https://localghost.dev/">Sophie Koonin</a></li> <li><a href="https://youtu.be/CS-3bFo1XHA">&quot;Working towards a greener world from behind the keyboard&quot;</a> Natalia Waniczek</li> </ul> <p>I really like having my own place on the web, and I&#39;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&#39;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&#39;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&#39;t bothered bundling. The most common solution is to bundle the JS for each page, put a hash of the content in the file name, and serves it with <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&#39;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&#39;d still rather not bundle though. The pool of small modules I&#39;ve written for my experiments are intended for reuse. The bundle for each page would significantly overlap with the bundles of other pages, but would be cached independently. A single character change to any module will invalidate all bundles which include it.</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&#39;s about as far as you can get without transforming the JS in the modules. It means that the browser only knows about each dependency as it encounters it in import statements. We <em>could</em> do some preloading, but whatever we do it&#39;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&#39;s a problem with this... when a file changes, its name will change. When it&#39;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&#39;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">&quot;imports&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;/a.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/a-5b5be8.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;/b.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/b-913d04.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;/c.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/c-7534a8.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;/d.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/d-25255e.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;/e.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/e-bb438d.js&quot;</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&#39;t understand import maps yet to work as they did before. Temporary redirects would work too, but mean more requests. The prefixes of the files with content hashed in their names allows immutable cache headers to be added when they&#39;re served (the second point).</p> <p>The last point was unexpected, but to spec (it&#39;s not just a Chrome quirk). Browsers which don&#39;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&#39;t want the import maps of other pages to grow, so I don&#39;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&#39;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">&quot;/a.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;hashedFileName&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/hashed-a-5b5be8.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;dependencies&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;/b.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;/c.js&quot;</span><span class="hljs-punctuation">]</span> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;/b.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;hashedFileName&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/hashed-b-913d04.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;dependencies&quot;</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">&quot;/c.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;hashedFileName&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/hashed-c-7534a8.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;dependencies&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;/d.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;e.js&quot;</span><span class="hljs-punctuation">]</span> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;/d.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;hashedFileName&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/hashed-d-25255e.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;dependencies&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;e.js&quot;</span><span class="hljs-punctuation">]</span> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;/e.js&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;hashedFileName&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;/hashed-e-bb438d.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;dependencies&quot;</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&#39;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&#39;ve not addressed yet is that on first load, the browser only discovers what it needs to fetch as it reads the imports of each module.</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&#39;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&#39;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">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">&quot;modulepreload&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/hashed-every-90ec60.js&quot;</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">&quot;modulepreload&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/hashed-javascript-dc77dc.js&quot;</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">&quot;modulepreload&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/hashed-module-8d541a.js&quot;</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">&quot;modulepreload&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/hashed-needed-54bb3e.js&quot;</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">&quot;modulepreload&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/hashed-later-05fbf2.js&quot;</span>&gt;</span> </code></pre><p>The solution isn&#39;t perfect though. A hypothetical problem (there are no browsers like this at the time of writing) is when module preload is supported, but import maps are not. This would lead to modules being fetched twice (hashed and original file names). One way out of that would be to have the original filenames respond with a temporary redirect to the file names with the hashes in.</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&#39;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&#39;ll either be in the browser cache or it won&#39;t be (and will trigger a request to populate the cache). The service worker isn&#39;t needed for such files. It&#39;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&#39;t want it to handle ones cached immutably. By returning early from a fetch event handler the handler delegates the request back to the browser and the browser cache will be used as if the service worker isn&#39;t there at all.</p> <p>Since I&#39;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">&#x27;fetch&#x27;</span>, <span class="hljs-function"><span class="hljs-params">fetchEvent</span> =&gt;</span> { <span class="hljs-string">&#x27;use strict&#x27;</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">&#x27;/&#x27;</span>) .<span class="hljs-title function_">pop</span>() .<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">&#x27;hashed-&#x27;</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&#39;d rather not have one at all. The less it does the better! In an ideal world, all resources would be immutable, and only the HTML document would not be. In such a world a request for a page resulting in a 304 response (not changed) would also mean all resources are already cached, and no further requests are needed.</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&#39;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&#39;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-2022 Procedural Christmas cards 2022 2022-12-17T15:03:00Z 2022-12-17T15:03:00Z qubyte https://qubyte.codes <p>Below is a procedural snowman. I&#39;m using a little code to create Christmas cards again this year, and as before I wanted each to be unique! If you received a card from me, you may see something like <code>?seed=1234567890</code> in the URL bar. That will be the random seed which generated your snowflake (and it&#39;s yours to keep). To see a random snowflake, remove everything after the question mark and hit enter. Refresh the page to see a fresh random snowman!</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&#39;s on me (not the mail strikes).</p> https://qubyte.codes/blog/my-2022-japanese-language-study-habits My 2022 Japanese language study habits 2023-01-03T09:10:00Z 2023-01-03T09:10:00Z qubyte https://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&#39;t particularly interesting or useful. They mostly serve to hold me accountable. However, now that I have a full year of data, it seems like a good time to see if there are any trends!</p> <p>I also took lessons during 2022, but I&#39;m not including those here. Study sessions as I define them here are solo study time, but that does include homework. While I have categorized each session, the vast majority of time was spent on <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&#39;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&#39;t have much structure to my study, and the tools I used were not working well for me. I was using flash card applications like <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&#39;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&#39;s where you can see my study minutes really take off. A mixture of finding interesting reading material within my comprehension and a couple of learning applications (I mentioned <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&#39;m getting much better results from them. I also changed my morning routine to include waking up a little earlier, heading to a local coffee shop, and clearing my review backlog for both each weekday morning before work.</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&#39;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&#39;m going to need to increase my study minutes (I&#39;m still considering where to place this but a minimum of 30 minutes a day seems like a good goal) and sign up for more lessons.</p> https://qubyte.codes/blog/tip-type-narrowing-arrays-for-sorbet-in-ruby Tip: Type narrowing arrays for sorbet in ruby 2023-01-28T15:25:00Z 2023-01-28T15:25:00Z qubyte https://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&#x27;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&#x27;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&#39;re safer. There&#39;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&#39;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> &amp;&amp; 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-sorbet Tip: Find and type narrow an element from an array in ruby and sorbet 2023-01-30T09:05:00Z 2023-07-08T14:30:00Z qubyte https://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&#39;t smart enough to type narrow it when the match is related to the type of the element.</p> <p>For illustrative purposes, here&#39;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&#x27;t work because Integer</span> <span class="hljs-comment"># has no `downcase` method.</span> first&amp;.downcase <span class="hljs-keyword">end</span> </code></pre><p>This unfortunately doesn&#39;t work. The code is fine, but sorbet doesn&#39;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&#39;s pretty ugly though. The only good thing going for it is that it stops iterating through the array once the string is found (like <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&amp;.downcase <span class="hljs-keyword">end</span> </code></pre><p>This works too! It&#39;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&#39;s why I&#39;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&#39;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&#39;s a <code>.last</code> method I could use, I don&#39;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&#39;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-habits My 2023 Japanese language study habits 2024-01-07T11:20:00Z 2024-01-07T11:20:00Z qubyte https://qubyte.codes <p>I&#39;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&#39;ve been doing conversation lessons with another tutor. The format is a little different, and I think this change helped to spark some improvements in my development. Before a typical lesson I&#39;m given an article to read in Japanese. I then spend part of the lesson chatting with my tutor, and the rest re-reading the article, answering comprehension questions, and discussing it. These lessons aren&#39;t included in the data, but I do include the pre-lesson read of an article as reading time. As for last time year, while I have categorized each session, the vast majority of time was spent on <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&#39;ve built up a collection of cards I find difficult to memorize, and that&#39;s both hurting my motivation and constricting the flow of new cards. I&#39;ll make a more focussed effort to memorize these difficult cards, even if it means I have to write them out by hand a lot or something.</p> <p>Another thing I&#39;ve noticed is that with the grammar focussed app (<a href="https://www.wanikani.com">Bunpro</a>) is that it&#39;s way more than just a flashcard app, and I&#39;m underutilizing it. This is not just to get the most value out of it; I&#39;ve found that simple repetition isn&#39;t enough to learn many grammar points (what I think of as natural acquisition, like a child might learn). I&#39;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&#39;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&#39;m going to need to increase my study minutes (I&#39;m still considering where to place this but a minimum of 30 minutes a day seems like a good goal) and sign up for more lessons.</p> </blockquote> <p>I&#39;m <em>almost</em> back to where I was when I passed the N4, and in terms of listening and speaking I&#39;m more advanced thanks to signing up for more tuition. As for sitting the N3... not even close. That&#39;s a good goal to have, but in hindsight it was a high target given the free time I have around work and family.</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&#39;t know if I&#39;ll do that this year (I&#39;d say probably not given my trajectory). Goals should be measurable though, so here&#39;s what I&#39;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&#39;t represent a lot of time, but they <em>do</em> need organization and discipline, which I&#39;m not so great at. Wish me luck!</p> https://qubyte.codes/blog/why-did-i-create-a-keyboard Why did I create a keyboard? 2024-02-29T21:00:00Z 2024-02-29T21:00:00Z qubyte https://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&#39;ve noticed that most split keyboards tuck the thumb keys as a short row or arc below the finger columns. My thumbs want to splay out more than that.</li> </ul> <h2 id="pcb-printed-circuit-board-design">PCB (printed circuit board) design</h2> <p>While it&#39;s possible (and fairly common) to hand-wire keyboards, the constraints above led to a rough idea of what I wanted, which included some visible PCB. PCB design is new to me, so I had to figure out how to do that. Thankfully there are guides (I used <a href="https://github.com/ruiqimao/keyboard-pcb-guide">this one</a>). Since I was going with a controller, I didn&#39;t need to worry about much of this guide. The important bit for me was how to do a key matrix. The software I used is KiCad, which is free, and libraries can be installed using plain old git submodules (ordinarily I&#39;d balk at submodules, but this is a rare use case where they make sense). I ignored the guide and borrowed the libraries used by other custom keyboard kits since many of them are in GitHub.</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&#39;re given guides to show which parts still need to be connected. Here&#39;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&#39;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&#39;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&#39;t for me. I like the idea, but it&#39;s just too much of a jump from conventional typing for me. I ended up creating a custom layered layout, which covers all the keys I actually use (no mouse keys).</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&#39;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&#39;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&#39;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&#39;m considering a different connector standard to join the two halves. Maybe something silly like a pair of PS/2 connectors per side, or a D-Sub 15 connector per side. I may keep the ribbon for flexibility though. It&#39;d be nice to source something which isn&#39;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&#39;d definitely like to have escape and tab keys on the left, and enter and backspace on the right would be nice too. Except for backspace the FG-11 Rev1 uses chording to achieve these, and it&#39;s a bit of a faff.</li> </ul> https://qubyte.codes/blog/indiewebcamp-brighton-2024 IndieWebCamp Brighton 2024 2024-03-16T23:00:00Z 2024-03-18T03:05:00Z qubyte https://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&#39;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&#39;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&#39;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&#39;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&#39;ve put so much effort into getting the footprint of my own site as low as possible. We discussed efficiency server-side, client-side, in transmission, and the costs and benefits of single servers vs large data centres.</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&#39;ve been wondering about storing images separately. Fortunately for me, others share this pain point, and that&#39;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&#39;s a clear need for a decision tree to help folk starting out to make a good choice without overwhelming them with lots of information. There&#39;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&#39;t really think of many posts I&#39;ve written on this site as blog entries. In fact, internally they&#39;ve always been managed as <em>posts</em>. URLs link the web together, so we don&#39;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&#39;s no stranger to GitHub (making pages an ideal starting place)! Of course, being DNS, it took us a while to figure out its interaction with GitHub&#39;s funky way of doing things, but we got there. This was the highlight of the weekend for me. Another independent, personal site on the web!</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&#39;t use syntax highlighting or MathML styles, so I put those in their own files. Both are third party anyway, so it makes sense to keep them separate and versioned with their associated dependencies. This was mostly plumbing work to configure my static site generator.</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 &quot;above&quot; (the default), the script deletes the setting, rather than storing &quot;above&quot;.</p> <p>To wrap up the day <a href="https://indieweb.org/2024/Brighton/Demos">we demoed what we&#39;d written and made</a>. Click through to explore those (there&#39;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-music Favourite music 2024-08-15T02:45:00Z 2024-12-28T14:25:00Z qubyte https://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&#39;ve linked to music services where I can find each album available, but with minimal effort. There are other services, but they&#39;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&#39; takes an age to build, but when it finally gets there it&#39;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 &amp; Damnation come close), my favourite remains Blackwater Park. Most of the music I listen to avoids guitar solos and such. Opeth manage to do the reverse, and have moments when every instrument seems to be executing a solo at the same time and maintain coherence. I was originally hooked by The Drapery Falls, but the entire album is exquisite. Give it a try, even if the thought of death-growl vocals is off putting to you. It’s worth it.</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&#39;s jazz, but I don&#39;t know enough about jazz to really say. I bought the record as an impulse purchase shortly before the pandemic lockdowns started. My son was still a baby then, so I was looking for something to relax to. Due to the timing, it became the sound of the early pandemic for me. Back then, it felt like the world was just the three of us.</p> <h2 id="boards-of-canada--tomorrows-harvest">Boards of Canada – Tomorrow&#39;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&#x27;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&#39;s earlier work, but Tomorrow&#39;s Harvest blew my mind. The vintage sound and sparseness gives it a brooding and foreboding feeling. This album is excellent to work to.</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&#39;ve played it so often since that any nostalgia for that time has long since gone, but I do still associate it with a change in mindset that came with changing career from a physics academic to a software engineer.</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&#39;d have been too busy to buy it. What if I had chosen a different share house? What if the aforementioned friend hadn&#39;t started a business with a friend of my (now) partner and set up a Roman Holiday themed club night beneath a Fiat showroom which we were both &quot;encouraged&quot; to attend to recoup some losses? What if my partner and I hadn&#39;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&#39;d listened to some of their music before and found it good to work to, so when Signals was released I bought it straight away. It&#39;s an exceptional album. Tracks are repetitive, build slowly, and at times feel meditative. There&#39;s a thrumming, almost dangerous quality to it. Stand out tracks for me are Strata, and A Citizen&#39;s Dream. The latter I find very affecting. It feels like longing, sadness, and hope, sometimes to the point of tears when I&#39;m actively listening to it. I have no idea why that is though! Sometimes my reaction to music can&#39;t be explained. It&#39;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-actions How I syndicate links and notes to Bluesky with GitHub Actions 2024-11-25T02:15:00Z 2024-11-25T02:15:00Z qubyte https://qubyte.codes <p>Inspired by <a href="https://adactio.com/journal/21570">Jeremy&#39;s post on how he syndicates to Bluesky</a>, I thought I&#39;d follow suit (many examples are useful when it comes to API integration work). A disclaimer though... I&#39;m dubious of the long term prospects of Bluesky for reasons I won&#39;t go into here. That being said, it&#39;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&#39;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&#39;t find it necessary to use one. I managed to write this without any third party libraries. API work is all achieved with plain old <code>fetch</code>.</p> <p>There are a couple of local libraries which I&#39;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">&#x27;https://bsky.social/xrpc/com.atproto.server.createSession&#x27;</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">&#x27;content-type&#x27;</span>: <span class="hljs-string">&#x27;application/json&#x27;</span> }, <span class="hljs-attr">method</span>: <span class="hljs-string">&#x27;POST&#x27;</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 &quot;d&quot;<sup class="footnote-ref"><a id="footnote-ref-2" href="#footnote-2">[2]</a></sup> stands for &quot;distributed&quot;, 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&#39;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&#39;s no need to think about refreshing the token, so I don&#39;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&#39;s not much to say here beyond the size restrictions being quite low, and being constrained to JPEGs and PNGs. I don&#39;t think they&#39;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 &quot;record&quot; (the note or bookmark) on Bluesky. There&#39;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-&#39;90s), so the range of characters you get with naïve string work in JS will give you incorrect offsets. Thankfully Node.js has the venerable <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&#39;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-2025 What happened to 2025? 2026-02-06T11:45:00Z 2026-02-06T11:45:00Z qubyte https://qubyte.codes <p>I&#39;ve not posted in over a year (the whole of 2025). I don&#39;t have a readership (there are good odds that nobody besides me will read this), so nobody has missed my writing and I&#39;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&#39;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&#39;m viewing it like a birthday. In the years between realizing I&#39;m trans and making the decision to come out, I&#39;d gone from the persistent feeling of wrongness and disconnection I&#39;d always had, to being in a very bad place. The decision to transition was a necessary one, but the rewards astonishing. I&#39;m healthier and happier than I&#39;ve ever been before. I&#39;m still early in my journey, but I&#39;ve come <em>so far</em>.</p> <p>So that&#39;s what happened in 2025, and why there&#39;s a gap in the blog posts. I might expand on individual parts of my experience in future posts. I&#39;m not certain yet. While it&#39;s an extremely personal subject, every trans person&#39;s path is different, and perhaps someone reading this will see something of themselves which helps them. I wouldn&#39;t change my life so far (except skipping the years between figuring it out and coming out), but I often wonder how things might have been if I&#39;d have read something like this and connected the dots earlier.</p> <p>If you&#39;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&#39;re acquainted, then you can talk to me, in confidence.</p>