<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The GentleHacker]]></title><description><![CDATA[Musings on the nature of these infernal Difference Engines, by a Victorian Gentleman (Computer) Scientist.]]></description><link>https://blog.gentlehacker.io</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1661825703377/7CjPt7iD7.png</url><title>The GentleHacker</title><link>https://blog.gentlehacker.io</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 21 May 2026 11:36:57 GMT</lastBuildDate><atom:link href="https://blog.gentlehacker.io/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Even Internet Users can Read it!]]></title><description><![CDATA[A Tech Writing Tip, from your #DevRel Friend; It's not a good look to define people as surprisingly capable when describing how usable a system is:

"Even non-developers can use!"
"Even your Mum could do!"
"Can even be interpreted by end-users"

I ca...]]></description><link>https://blog.gentlehacker.io/even-internet-users-can-read-it</link><guid isPermaLink="true">https://blog.gentlehacker.io/even-internet-users-can-read-it</guid><category><![CDATA[DevRel]]></category><category><![CDATA[Technical writing ]]></category><category><![CDATA[Product Management]]></category><category><![CDATA[Inclusion]]></category><category><![CDATA[stereotypes]]></category><category><![CDATA[product marketing]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Fri, 26 Apr 2024 02:53:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1714099872703/cab0beae-8f10-4ccf-a583-6eb5075654ff.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A Tech Writing Tip, from your #DevRel Friend; It's not a good look to define people as surprisingly capable when describing how usable a system is:</p>
<blockquote>
<p>"Even non-developers can use!"</p>
<p>"Even your Mum could do!"</p>
<p>"Can even be interpreted by end-users"</p>
</blockquote>
<p>I call this <strong>Surprising Simplicity</strong> and it's dreadful.</p>
<p>Using "<em>non</em>" frames developers as being a special class of people, which makes your readers feel good... But it implies that people who aren't in that class aren't as worthwhile. You're typically writing as part of the in-group, the developers in question. Don't make fun of those who are not <em>du monde</em>; That's no more then simple bullying.</p>
<p>You also want to avoid the universal truth vibes given off by "<em>Even</em>". This form is reliant on the assumption that the group described would typically be incapaple of making use of your product. You're positioning these folks as incapable so that It <em>feels</em> like a win that these folks can make use of your product. If that's not true, that's kinda gross. If it <em>is</em> true, is that an inherent property to the <strong>problem</strong>, or to the <strong>solution</strong>? What's the difference? The latter tends to be an assumed reality; something that products make true by assuming it's true. (A much more visceral (<em>and harmful</em>) example of this is explained by the video <a target="_blank" href="https://www.youtube.com/watch?v=9HpLhxMFJR8">ASSUME THAT I CAN</a> released for World Down Syndrome Day 2024).</p>
<p>The "<em>Mum</em>" example also includes bonus of propagating some sexist and ageist stereotypes about women, older people, and parents. Best to skip this one entirely.</p>
<h2 id="heading-what-should-you-do-instead">What should you do instead?</h2>
<h3 id="heading-describe-things-in-terms-of-the-power-it-affords-people">Describe things in terms of the power it affords people:</h3>
<blockquote>
<p>"Gives end-users flexible reporting, and developers the fine-grained controls you need"</p>
</blockquote>
<p>This isn't just better for inclusivity, it's better, period. People read tech writing because they have a problem &amp; want to not. As an author, you're aiming to be convincing in your ability to make said problem go away. By presenting your solution as a tool they can use, it invests the reader with feelings of agency and capability. A focus on surprising simplicity doesn't do this; Instead, it shifts the focus to other, apparently less capable people.</p>
<h3 id="heading-use-explicit-archetypes-not-implicit-stereotypes">Use explicit archetypes, not implicit stereotypes</h3>
<blockquote>
<p>"Simpler then programming a VCR"</p>
<p>"Easy enough for your 'bad-with-computers' friend"</p>
</blockquote>
<p>(NB: <em>A VCR was a device for recording &amp; playing back TV shows; like a bad-quality CD for video.</em>)</p>
<p>(NB: <em>CDs were a flat plastic disc which encoded music by laser etching; Like a offline version of Limewire.</em>)</p>
<p>(NB: <em>Limewire was...</em>)</p>
<p>Archetypes help scaffold our thinking. This is why personas are useful for product decisions, user stories and marketing strategies. They're just as useful in tech writing, with two caveats.</p>
<p>Firstly, people tend to use stereotypes instead of archetypes. What's the difference? Let us compare</p>
<p>"<strong>Self-Service customers are not skilled at statistics, have short result timeframes and a tendency to escalate support tickets.</strong>"</p>
<p>with</p>
<p>"<strong>Karen wants to speak to the manager again because she doesn't understand regressions.</strong>"</p>
<p>Archetypes have their relevant traits, skills and behaviours explicitly defined. They are (<em>or at least should be</em>) formed from emotionally neutral observations. Stereotypes, on the other hand, are only <em>indexical</em> of their traits; That is, the presence of the stereotype <em>implies</em> the traits. Typically they do so by creating a caricature, over-emphasizing certain behaviours and ascribing them to a certain kind of person, often in ways that reflect existing prejudices and relies on emotional implications.</p>
<p>Secondly, when referring to archetypes by name, you risk over-reliance on internal information. I have often experienced folksrefers to a market segment or persona by their companies' internal name, to the confusion of external parties. A <em>mid-market</em> customer probably doesn't know they're mid-market; a vendor might not know that you use <em>architect</em> in a way they use <em>business analyst</em>. This one is easy to avoid; Make the traits and behaviours specific instead of relying on names. (We're back to <em>indexicality.</em>)</p>
<h3 id="heading-i-think-youre-being-too-picky-about-semantics">I think you're being too picky about semantics.</h3>
<p>Then I invite you to consider: These forms are <a target="_blank" href="https://tech.lgbt/tags/techwriting">#techwriting</a><a target="_blank" href="https://tech.lgbt/tags/cliches">#cliches</a>. Your copy will resonate more strongly and read more fluently without them, and your audience will walk away thinking about your product, not your writing.</p>
<p>Additionally, we are living in the <em>algocene</em>, an epoch where vaugely understood advertising junkie information systems shuttle human attention around like hummingbees doing a speedrun, yet our ability to find accurate information appears worse then ever before. It is entirely conceivable that a simple "<em>Ahoy Computer: How do I {x}</em>" will lead one of your non-developer, four-kids-and-a-dog customers to your writing, leaving them with the gross sense that they're lesser beings; and your product with one less user.</p>
<hr />
<p><em>Liked this? Hated it? Comment down below!</em></p>
<p><em>See the original vent over on</em><a target="_blank" href="https://tech.lgbt/@TheGentlehacker/112334885826894282"><em>Mastodon</em></a><em>and</em><a target="_blank" href="https://twitter.com/DylanLacey/status/1783666069485551625"><em>Twitter</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Found in Translation]]></title><description><![CDATA[Fellow GentleHackers, if you've ever found yourself engaged in a serious attempt to acquire another language you have no doubt discovered it to be a quite onerous practice. Indeed, several practices, as language learning requires one to tune one's ea...]]></description><link>https://blog.gentlehacker.io/found-in-translation</link><guid isPermaLink="true">https://blog.gentlehacker.io/found-in-translation</guid><category><![CDATA[elevenlabs]]></category><category><![CDATA[TextToSpeech]]></category><category><![CDATA[AI]]></category><category><![CDATA[Web Audio]]></category><category><![CDATA[languages]]></category><category><![CDATA[translation]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[Web Extensions]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Fri, 02 Feb 2024 10:21:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/6WoJXG_dJAE/upload/62559f9ae5342e7a8da5144fdcebe209.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Fellow GentleHackers, if you've ever found yourself engaged in a serious attempt to acquire another language you have no doubt discovered it to be a quite onerous practice. Indeed, several practices, as language learning requires one to tune one's ear, learn grammar &amp; vocabulary, parse texts and produce utterances, often altogether.</p>
<p>As such, it won't surprise that researchers have conducted significant research into the relative efficacy of learning practices, and reached some tentative conclusions. One such conclusion is that it can be quite a boon to have a source of both textual <em>and</em> audio examples of comprehensible input.</p>
<p><a target="_blank" href="https://tatoeba.org/en/">Tatoeba</a> is one such boon, being a large, community driven database of cross-language sentences, with their respective translations and, quite often, spoken examples. Sadly, only <em>quite</em> often; examples are added by kind and generous community members, so may be absent for any given phrase.</p>
<p>We shan't let that stop us! Let us make use of <a target="_blank" href="https://tatoeba.org/en/">ElevenLabs.io</a>'s <em>splendid</em> AI text to speech faculties to build a browser extension that generates missing audio on demand. Given ElevenLabs' excellent articulation, cadence, and emotional inflection, along with the ability to stream audio via HTTP, our challenge lies primarily in wiring our apparatus together.</p>
<hr />
<h1 id="heading-the-setup">The Setup</h1>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎩</div>
<div data-node-type="callout-text">As this tutorial is primarily intended to educate you, dear reader, upon the consumption &amp; subsequent broadcast of audio from the ElevenLabs API, I intend to be quite brief when covering all other topics.</div>
</div>

<h2 id="heading-elevenlabs-access">ElevenLabs Access</h2>
<h3 id="heading-get-an-api-key">Get an API Key</h3>
<p>You shall need an API key and a chosen voice. Easily obtained; Sign up for their free plan <a target="_blank" href="http://elevenlabs.io/?from=partnertaylor6266">thusly</a>. (This is an affiliate link, as the GentleHacker is not above securing additional funds, should his recommendations prove useful.)</p>
<p>Make note of the API key, which you may find in your profile; The button to the bottom left.</p>
<h3 id="heading-choose-a-voice">Choose a Voice</h3>
<p>ElevenLabs has a wealth of voices to choose from. Should you wish to use a community-created voice, you can <a target="_blank" href="https://elevenlabs.io/app/voice-library">peruse the voice library</a>. Once chosen, click "Add to VoiceLab".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706786893635/131cc91b-9553-4e64-9cc6-52dd2b57950d.png" alt class="image--center mx-auto" /></p>
<p>That done, open the <a target="_blank" href="https://elevenlabs.io/voice-lab">Voice Lab</a> and click the "ID" losenge to copy it to your clipboard.</p>
<p>Alternatively, if you'd prefer an <em>official</em> voice, that is... less simple. Those are found in the <a target="_blank" href="https://elevenlabs.io/speech-synthesis">Speech Synthesis</a> section. Once you've found a voice you enjoy, you can <em>either</em> use the <a target="_blank" href="https://elevenlabs.io/docs/api-reference/get-voices">/getVoices</a> method of the API to find the ID, <em>or</em> you can open the developer tools, generate a speech sample, and grab the <code>voice_id</code> from the path of the POST made to <code>/api.elevenlabs.io/v1/text-to-speech/voice_id_is_this_bit</code> .</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706787647177/9d7f9e83-5e5c-498f-a9b0-4fefa643fac9.png" alt class="image--center mx-auto" /></p>
<p>This is a little inelegant, but I was unable to find a superior method. We prevail.</p>
<p>(Oh, and by the way; I recommend choosing the <code>Eleven Multilingual v2</code> model; the documentation claims it is of slightly lower quality but the non-English output is more verisimilar to reality.)</p>
<hr />
<h2 id="heading-the-web-extension">The Web Extension</h2>
<p>Web Extensions are little more then a bundle of webpages, along with guidance to the browser about how they ought be used. There's an <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions">ersatz standard</a>. The browsers somewhat follow it.</p>
<p>As such we <em>could</em> hand-write some HTML, some Javascript, a smidgen of CSS and a skerrick of JSON and have ourselves a functional extension.</p>
<p>We could. But <strong>no</strong>.</p>
<p>It is 2024 as I pen this missive, and I enjoy the conveniences of Svelte, and Vite, and TypeScript far too stridently to hand-carve my extension. Some brief research revealed <a target="_blank" href="https://vite-plugin-web-extension.aklinker1.io/">vite-plugin-web-extension</a>, by Aaron Linker. A brief <code>npm create vite-plugin-web-extension</code> and it provided the shell of a functional extension, with the ability to use Svelte components and rely on Vite's hot module reloading. Delightful.</p>
<p>I also installed <code>tailwind-css</code>, along with <code>flowbyte-svelte</code>, (a component library unfamiliar to me; I deemed this a good opportunity to play), and the <code>webextension-polyfill</code>, to provide missing web extension methods in Chrome</p>
<pre><code class="lang-bash">npx svelte-add@latest tailwindcss
npm i -D flowbite-svelte flowbite webextension-polyfill
</code></pre>
<hr />
<h1 id="heading-fetch-and-playback-of-speech">Fetch and Playback of Speech</h1>
<h2 id="heading-but-first-a-brief-detour-for-configuration">But first, a brief detour for configuration</h2>
<p>As one might imagine, ElevenLabs requires that one identify oneself when making API requests. We require some means by which our user can provide said API key, and the <em>Options UI</em>, combined with the Web Extension storage API, seems the perfect way to do so.</p>
<p>In our <code>manifest.json</code>, we can specify the URL to a page to present as part of the extensions options. We also need to request access to the storage API for the domains we intend to make use of it upon.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/93009466-45fe-454f-9568-b28b9a644ee5">https://snappify.com/view/93009466-45fe-454f-9568-b28b9a644ee5</a></div>
<p> </p>
<h3 id="heading-storing-our-api-key">Storing our API Key</h3>
<p><code>Options.html</code> loads our <code>Options</code> component, which, despite appearance, is a fairly simple affair. Here's the meat of it:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/3d5bb2c2-056a-4061-b475-452441493ce4">https://snappify.com/view/3d5bb2c2-056a-4061-b475-452441493ce4</a></div>
<p> </p>
<p>When the user enters their key and clicks "<em>Save</em>", we take the following steps:</p>
<ol>
<li><p>Get the key from the DOM</p>
</li>
<li><p>Validate the key by requesting this user's details from the API, providing the key via the <code>xi-api-key</code> header</p>
</li>
<li><p>If successful, generate a truncated version of the key, and store both the full and truncated versions in local storage as <code>eleven_labs_key</code> and <code>truncated_eleven_labs_key</code> respectively.</p>
</li>
</ol>
<p>This approach provides the user with two conveniences. Firstly, it allows the user immediate knowledge of whether their key is operational (and thus, that any bugs are due to the extension). Secondly, displaying the truncated key allows users to check they've already saved the correct key.</p>
<h3 id="heading-retrieving-our-api-key">Retrieving our API Key</h3>
<p>When we wish to use our API Key, we can retrieve it from storage. However, we also need to keep our code abreast of changes to said key, in case our user should replace the key whilst our other scripts are already loaded. Let us make use of <code>storage.onChanged.addListener</code> to do just that.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/a63d2726-822a-476a-a5d9-d7f103f30bcb">https://snappify.com/view/a63d2726-822a-476a-a5d9-d7f103f30bcb</a></div>
<p> </p>
<h2 id="heading-now-we-may-fetch-our-speech">Now, we may fetch our speech</h2>
<h3 id="heading-selecting-a-generation-method">Selecting a Generation Method</h3>
<p>The ElevenLabs API provides three methods for generating speech.</p>
<p>Firstly, the <a target="_blank" href="https://elevenlabs.io/docs/api-reference/text-to-speech"><strong>Text-to-Speech</strong></a> API operates in a traditional HTTP fashion, generating all audio data then returning it once complete.</p>
<p>Secondly, the <a target="_blank" href="https://elevenlabs.io/docs/api-reference/streaming"><strong>Text-to-Speech Streaming</strong></a> API begins streaming audio data as soon as it's available, allowing for faster playback (especially for longer audio passages). This is what we shall make use of.</p>
<p>Thirdly, the <a target="_blank" href="https://elevenlabs.io/docs/api-reference/websockets"><strong>Websockets</strong></a> API provides real-time audio responses over a, well, WebSocket. It is intended for situations where the input is not entirely available up front, or where word-to-audio concordance is required.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎩</div>
<div data-node-type="callout-text">API Selection is predicated upon your desired experience. We choose to avoid WebSockets as they add complexity (and potentially latency, due to buffering) without providing features we need. Similarly, we avoid the non-streaming HTTP method, as we don't wish to wait for generation to finish before playback begins; our user is patiently waiting to hear our efforts. Standard HTTP would be suitable if we were generating audio entirely in advance of consumption, say during media production or a website build step.</div>
</div>

<h3 id="heading-calling-the-api">Calling the API</h3>
<p>Let us package the code for retrieving and playing audio data in <code>elevenAPI.ts</code>. We start with data retrievall; as <code>fetch</code> in modern browsers can provide a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream"><code>readableStream</code></a>, it is eminently suitable for our purposes.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/36e6edfa-60bd-4567-bc33-15eac59f1e83">https://snappify.com/view/36e6edfa-60bd-4567-bc33-15eac59f1e83</a></div>
<p> </p>
<p>There are three parameters of note. We provide <code>optimize_streaming_latency</code> on line 9, which (according to the documentation) provides us with 75% of the maximum latency optimization, at the cost of "<em>some</em>" quality. I consider this fair; Language is not always heard in ideal situations!</p>
<p>The <code>output_format</code> on line 10 indicates how our audio shall be encoded. We choose <a target="_blank" href="https://en.wikipedia.org/wiki/Pulse-code_modulation">PCM</a>, as this is the closest to what the Web Audio API consumes. More on this topic later. Should we care to, ElevenLabs could also furnish us with MP3 or μ-law, all at a range of sample rates.</p>
<p>Lastly, we choose the <code>eleven_multilingual_v2</code> model on line 20, as this model supports the widest range of languages; ElevenLabs will determines which language is in use automatically, which is rather handy!</p>
<p>Note also, the retrieval of our <code>apiKey</code> on line 5. Because we must <code>await</code> the storage API, we are unable to load the key when this module is imported, due to the harsh realities of Top Level Await. To avoid calling the storage API needlessly, we only do so if <code>apiKey</code> is not already defined, using the ternary operator.</p>
<h2 id="heading-playback">Playback</h2>
<h3 id="heading-the-vexation-of-audio-formats">The Vexation of Audio Formats</h3>
<p>The question of playing out audio back brings up a <em>slight</em> quibble. A <em>vexation</em>. One might call it <em>inconvenience</em>, perhaps even a <em>botherance</em>. Were we using the non-streaming API, we'd convert our data into a blob URL and provide it to a HTML5 <code>&lt;audio&gt;</code> tag. We, brave hackers, opted for streaming instead and, as such, will be receiving a series of <em>chunks</em> of audio in lieu of one complete file.</p>
<p>I apologize in advance, gentle reader, for the number of times you are about to read the word <em>chunk</em>.</p>
<p>We rely on the auspices of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> to make use of our chunks. It furnishes us with the ability to play individual buffers of audio, albeit with one futher peevishment. Recall when I mentioned that the PCM data returned by the API is the <em>closest</em> to that required by the Web Audio API? Well, <em>closest</em> here won't do. The Web Audio API requires PCM to be encoded as:</p>
<blockquote>
<p>(signed) 32-bit linear PCM with a nominal range between <code>-1</code> and <code>+1</code>, that is, a 32-bit floating point buffer, with each sample between -1.0 and 1.0</p>
</blockquote>
<p>Whereas the Elevenlabs API returns signed, 16-bit integer PCM, whose values are <em>not</em> between <code>-1</code> and <code>+1</code>.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎩</div>
<div data-node-type="callout-text">I shan't be discussing signed <em>vs </em>unsigned here; I recommend perusing <a target="_blank" href="https://en.wikipedia.org/wiki/Signedness">Wikipedia</a> for more.</div>
</div>

<p>Compounding our difficulty, the <code>ReadableStream</code> is <code>Uint8</code>, an 8-bit <em>unsigned</em> integer <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArray</a>, meaning we shall have to first convert it to <code>Int16</code>, <em>signed</em> 16-bit integer and thence to <code>Float32</code>, signed 32-bit <em>float</em>.</p>
<h3 id="heading-converting-the-response-to-float32">Converting the response to Float32</h3>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/94d9bac5-a425-4202-967e-4ee268905ae1">https://snappify.com/view/94d9bac5-a425-4202-967e-4ee268905ae1</a></div>
<p> </p>
<p>Firstly, our <strong>type signature and read loop</strong>. Our function will take the incoming stream, which is a <code>ReadableStreamDefaultReader</code>. Because we generate this within a <code>try/catch</code> block in <code>doFetch</code>, there's chance it will be <code>undefined</code>.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎩</div>
<div data-node-type="callout-text">It is good practice to explicitly deal with undefined values, however, we can avoid doing so for <code>streamReader</code>; if undefined the optional chaining operator <code>?.</code> will spare us from exceptions, and the false-y nature of undefined will terminate our loop.</div>
</div>

<p>We are also providing a <code>Function</code> called <code>fn</code>, which takes a <code>Float32Array</code> and can return anything. This function will be called once for each decoded chunk, allowing us flexibility in how we manage the decoded data.</p>
<p>Once the reader indicates that our stream is complete, we exit the loop.</p>
<p>Our data processing is managed in the section marked <code>// Omitted for brevity</code>, which is supplied below.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/b8602284-533a-437c-b171-bc4d8094d3e8">https://snappify.com/view/b8602284-533a-437c-b171-bc4d8094d3e8</a></div>
<p> </p>
<p>As we <strong>read each chunk</strong>, we encounter another vexation of streaming. Our 16-bit data is encoded as an array of 8-bit responses, meaning <em>each 2-byte</em> value is split into <em>two 1-byte values</em>. Furthermore, there is no guarantee that each chunk contains an even number of bytes.</p>
<p>This necessitates we keep track whether we have a leftover byte or not, every time we process a chunk. If so, the next chunk needs to have said byte appended at the beginning. Frustratingly, it's not possible to add elements to a <code>TypedArray</code>, so we must resort to using the spread operator to create a new array containing the old one.</p>
<p>Once we've dealt with vexatiously odd chunk lengths, we need to deal with our signing issue.</p>
<p>The <code>DataView</code> we've created on line 16 can see all of the data in the chunk's underlying buffer, and allows us to call <code>getInt16</code> to read any two bytes, in any position in said buffer, as though they were a signed, 16-bit integer.</p>
<p>Because we're consuming two bytes to generate each value, the resulting <code>Float32Array</code> only needs to hold <em>half</em> as many elements. We can then convert the <code>Int16</code> values to <code>Float32</code> values (Which we will discuss shortly) and that's that.</p>
<h3 id="heading-why-the-divide">Why the Divide?</h3>
<p>You may be wondering, why half as many, instead of one quarter?</p>
<p>Think of the conversion thusly. Say you download a 10x10 image from an API, which returns an array containing each pixel's red, blue, and green value, one after the other, until a full 300 element array is in your possession.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706860100600/57bf59d7-4aff-49fe-a1fe-b7c54107a29b.png" alt="An image showing an array of nine numbers on the first line. On the second line, they have been converted into three hexidecimal values. On the final line, the hexidecimal values have each had &quot;FF&quot; appended, to represent opacity." class="image--center mx-auto" /></p>
<p>Once you've fetched your array, you convert it into the corresponding hex representation; You now have <em>one hundred</em> elements where previously you had three. Say you now need to feed this image into one which supports hex with opacity. You don't <em>have</em> opacity values, so you convert your values and default to "full opacity" (<em>ff</em>). Each individual value now has <em>more data</em>, but you do not have <em>more values</em>.</p>
<p>When we converted our audio data, we were doing something similar. To go from<code>UInt8</code> to <code>Int16</code>, we required two unsigned bytes to combine and decode into one, two-byte long signed sample. We then have to convert each sample to <code>Float32</code> to please our Web Audio API master. Because signed 16-bit values range from <code>-32768</code> to <code>32767</code>, and we need a range between <code>+1</code> and <code>-1</code>, we can convert our <code>Int16</code> values to <code>Float32</code> by dividing by 32768. Each sample has more (empty) <em>data</em> but the <em>number</em> of samples remains the same.</p>
<h2 id="heading-using-the-web-audio-api-for-playback">Using the Web Audio API for Playback</h2>
<p>The Web Audio API api possesses a helper method called <code>decodeAudioData()</code>, which plays back an <code>AudioBuffer</code> obtained from the likes of <code>fetch</code>... Alas, it requires the full file be ready, and so is not suitable here. We shall have to make our own pudding, as it were.</p>
<p>All online Web Audio processing takes place within an <code>AudioContext</code>, which represents a graph of audio processing nodes. Should one be not averse, one can use the Web Audio API to perform a substantial array of audio generation and processing. Handily, we are spared such complex manipulations.</p>
<p>Our playback handler needs merely create an AudioContext and return a function which will do naught but play back each chunk at the appropriate time. Said function can then be passed to <code>processStream()</code>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/3781c233-d1bb-4bb4-9b84-913eb68c65b8">https://snappify.com/view/3781c233-d1bb-4bb4-9b84-913eb68c65b8</a></div>
<p> </p>
<p>As <code>processStream()</code> receives each chunk, it requests a single-channel <code>AudioBuffer</code> of the appropriate length and sample rate from the <code>AudioContext</code>. (The <code>1</code> in the call to <code>createBuffer()</code> indicates how many channels the buffer should possess).</p>
<p>The chunk's data is then copied to the first channel therein, and the entire buffer encapsulated within an <code>AudioBufferSource</code> (also kindly provided by the <code>AudioContext</code>). That <code>AudioBufferSource</code> is then connected to the <code>AudioContext</code>'s <em>destination</em>, which, by default, is the system audio output. Then, it's scheduled to start.</p>
<p>Timing in the Web Audio API is interesting, as it's driven by the API itself. Each <code>AudioContext</code> has a <em>currentTime</em>, a <code>Double</code> measured in seconds. Each source can be started at any given positive time, also in seconds, and if the requisite time has already started, the source begins <em>immediately</em>.</p>
<p>This furnishes us with an extremely simple means of scheduling, as well as providing an optional delay. We can simply start each sample at <code>0 + delay + already_queued_samples</code>. This delay also acts as a simplified cache; delay the start of playback by a short period to allow extra audio to be streamed.</p>
<h2 id="heading-some-interface-niceties">Some Interface Niceties</h2>
<p>Connecting <code>doFetch()</code>, <code>playHandler()</code> and <code>processStream()</code> is a straightforward process; we invoke <code>doFetch()</code> with our desired text and voice, pass the <code>streamReader</code> to <code>playHandler()</code>, then pass the returned function to <code>processStream()</code>. Our audio now streams without further intervention.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/8bded591-e832-4ea0-bbe7-b24472d377c0">https://snappify.com/view/8bded591-e832-4ea0-bbe7-b24472d377c0</a></div>
<p> </p>
<p>After adding some nice defaults, I also added a method to return the entire audio data, as well as both stream <em>and</em> return the audio. These are left as an exercise for the reader.</p>
<h1 id="heading-enhancing-tatoebaorg">Enhancing Tatoeba.org</h1>
<h2 id="heading-finangling-the-front-end">Finangling the Front End</h2>
<p>Our work thus far is, sadly, of no import if we do not provide our user a means of requesting a sentence be generated. Let us now turn our attention to the same.</p>
<p>Web Extensions allow one to inject scripts into the body of webpages. They are called <em>content scripts</em> and may be injected either programmatically, or by specifying the pages in advance. We return to the manifest and instruct our extension to insert <code>src/addMissingVoices.ts</code> on every page of the <code>tatoeba.org</code> domain:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/d468deb5-8e84-4834-a298-9a56d3190ccf">https://snappify.com/view/d468deb5-8e84-4834-a298-9a56d3190ccf</a></div>
<p> </p>
<h3 id="heading-the-lists-page">The Lists Page</h3>
<p>I chose to restrict my initial version to Tatoeba's <em>lists</em> feature, whereby users can save lists of sentences. Taking a look, we see the greyed-out, muted volume icon indicates the absence of a spoken example.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706864011885/e7c640e1-3f2b-4784-9d7d-412414564f71.png" alt="A screen capture of a list called &quot;Weather&quot;, on tatoeba.org. It contains three rows, each with a sentence in English or Japanese, as well as some buttons. Notably, the top row has a volume icon, and the other two rows have a muted volume icon." class="image--center mx-auto" /></p>
<p>We are thus presented with two challenges:</p>
<ol>
<li><p>Extract all sentences within a list for which no example exists</p>
</li>
<li><p>Provide a user interface action allowing them to generate said example on demand</p>
</li>
</ol>
<p>Let's tackle each in turn.</p>
<h3 id="heading-whither-missing-examples">Whither Missing Examples?</h3>
<p>In days of yore, a developer might reach for old friend JQuery when interrogating the DOM. We wish to keep our extention's code light and fast, so we shall rely on built in facilities whereever possible.</p>
<p>By peering through the DOM using developer tools, I determined each sentence is contained within a element whose class is <code>sentence-and-translations</code>. By finding all such elements, I could determine whether an audio example was missing by checking whether it contained an element whose <code>aria-label</code> attribute was <code>volume-off</code>.</p>
<p>For each relevant sentence, I would need to collect the actual text in question (by finding a <code>ng-if</code> attribute set to <code>!vm.sentence.furigana</code>, oddly). I also choose to store the sentence ID (<code>a</code> element with an <code>ng-href</code> attribute, contained within <code>md-subheader-content</code>), as my future self might make use of it for caching purposes.</p>
<p>Armed with my CSS Selectors, I set off to achieve the above.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/50e8ff03-03cc-41e3-b9b1-40d1da4b69a2">https://snappify.com/view/50e8ff03-03cc-41e3-b9b1-40d1da4b69a2</a></div>
<p> </p>
<p>(GentleHacker sidenote: Whenever I can use <code>map</code> I feel like a Wizard. This may express deep secrets about my psyche and I don't wish to dwell on it).</p>
<h3 id="heading-shiny-new-buttons">Shiny new buttons</h3>
<p>Now we know what's missing, we can begin to compensate for said absence. This comprises constructing new buttons, adding a <code>eventListener</code> for <code>click</code>, and inserting them into the appropriate place in the DOM.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/efd9fbdf-48d7-4352-b144-8ed02f5c3442">https://snappify.com/view/efd9fbdf-48d7-4352-b144-8ed02f5c3442</a></div>
<p> </p>
<p>Now, you might be asking yourself, why use <code>browser.runtime.sendMessage</code> to call the API, instead of calling it directly via <code>elevenAPI.streamAudio()</code>? That is an excellent question and the answer, as it so often does, comes back to CORS.</p>
<p>Whereas previously, content scripts were afforded an exemption from labouring under the harsh realities of Cross-Origin-Resource-Sharing, modern browsers consider such practices... distasteful. Thankfully, we have a workaround: background scripts.</p>
<h2 id="heading-background-scripts-the-intermediary">Background Scripts: The Intermediary</h2>
<p>Background scripts load in the background, as you might imagine, and have access to the full gamut of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API">WebExtension APIs</a>. They can either persist, or load in response to certain events; The latter, non-persistent scripts will eventually be the only kind.</p>
<p>As such, it behoves us to write our script in a non-persistent manner, which is not at all arduous. It merely requires that listeners are top-level, and we avoid relying on global variables.</p>
<p>To whit:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/61495da9-65a3-48de-9c9c-c6ddd25b1739">https://snappify.com/view/61495da9-65a3-48de-9c9c-c6ddd25b1739</a></div>
<p> </p>
<p>Our manifest contains some new syntax! One advantage of <code>vite-plugin-web-extension</code> is that it allows us to define <code>manifest.json</code> in terms of the browser we're targetting. On Firefox, background scripts are simply called <code>scripts</code>, whereas on Chrome, they're called <code>service_worker</code>. Thankfully, it's trivial to account for both.</p>
<p>Speaking of trivial, <code>background.ts</code> isn't particularly impressive. It polyfills the <code>browser</code> APIs for that rascal Chrome, then adds a listener for messages and, if one is received, fires off <code>streamAudio</code>.</p>
<hr />
<h1 id="heading-triumph">Triumph!</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706868531098/f307033f-7b5b-42b8-9555-0958e87e4555.png" alt="The &quot;Lists&quot; view on Tatoeba.org, showing a list called Weather. There are three rows. The top row has a filled volume icon, indicating a speech sample is available. The second and third rows have a muted, greyed out icon, indicating audio is not available. Next to those, however, is an icon showing bunny ears emerging from a top hat." class="image--center mx-auto" /></p>
<p>Click one of those buttons, gentle reader, and ElevenLabs will spin into action, generating you a lovely audio example. Marvellous.</p>
<hr />
<h1 id="heading-thank-you-gentle-reader">Thank You, Gentle Reader</h1>
<p>We have accomplished what we set out to do! We can access the ElevenLabs API and generate audio where missing, and we've done so in a such a way that we can easily extend it further.</p>
<p>Were I to spend more time working on this extension, I'd consider things such as:</p>
<ul>
<li><p>Using IndexedDB to cache responses</p>
</li>
<li><p>Pulling down the full list of voices as part of the options, and allowing users to choose them in-app, on a per-language basis</p>
</li>
<li><p>Adding a translation challenge mode, where the listener hears a random sentence, then a pause, and must translate it themselves before hearing the "Correct" translation.</p>
</li>
<li><p>A universally available popup, allowing users to check selected text on any webpage to determine whether it's available on Tatoeba.org and, if so, hear a foreign translation in the language of their choice.</p>
</li>
</ul>
<p>But these are tasks for another day.</p>
<p>Thank you again, gracious friend, for your time and attention. If you found this helpful I would sincerely appreciate a follow, like or comment. Sharing this article with your peers helps me <em>greatly</em>, as does following me on Mastodon, at <a target="_blank" href="https://tech.lgbt/@TheGentlehacker">https://tech.lgbt/@TheGentlehacker</a>.</p>
<p>Ta-tah!</p>
]]></content:encoded></item><item><title><![CDATA[A Link Preview Component for SvelteKit (Part 1)]]></title><description><![CDATA[TL;DR of this articleWe introduce the OpenGraph Protocol and the basic use case supported by our component. We create a basic SvelteKit and embed a TypeScript compatible component inside

One of the glories of the Hypertextual medium is the ability t...]]></description><link>https://blog.gentlehacker.io/a-link-preview-component-for-sveltekit-part-1</link><guid isPermaLink="true">https://blog.gentlehacker.io/a-link-preview-component-for-sveltekit-part-1</guid><category><![CDATA[Svelte]]></category><category><![CDATA[Sveltekit]]></category><category><![CDATA[openGraph]]></category><category><![CDATA[components]]></category><category><![CDATA[Scraping]]></category><category><![CDATA[social media]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 29 Jan 2024 05:18:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/pHw08h_EvO4/upload/2c37a402ed9e7bf1856d029766e69cdf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<details><summary>TL;DR of this article</summary><div data-type="detailsContent">We introduce the OpenGraph Protocol and the basic use case supported by our component. We create a basic SvelteKit and embed a TypeScript compatible component inside</div></details>

<p>One of the glories of the Hypertextual medium is the ability to enrich one's messages with links. Indeed, Hyperlinks (to give their proper address) to websites, images, video and such are the very <em>fabric</em> of the Internet, although not without drawbacks.</p>
<p>Sometimes, links may lead to content a reader is already familiar with, or does not wish to consume, and as such it is not surprising that one might wish to provide a <em>preview</em> of the object to which a link pertains.</p>
<p>Sadly, this is not a native faculty possessed by The Internet at large, which may surprise given the common appearance of previews in apps such as Slack:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705973393435/f9e30346-5d11-485c-8e8e-cd25c12c78cb.png" alt="A Link Preview for the GentleHacker article &quot;The New Year, for ADHD Sorcerers&quot;, displayed in Slack." /></p>
<hr />
<h1 id="heading-how-then-are-link-previews-generated">How, then, are link previews generated?</h1>
<p>You might suppose the process involves using a headless browser to take screenshots, or interrogating the HTML to guess the details. Thankfully, this is not the case; often, the required information is supplied via the <a target="_blank" href="https://ogp.me/">Open Graph Protocol</a> which</p>
<blockquote>
<p>enables any web page to become a rich object in a social graph.</p>
</blockquote>
<p>A simple, dependency-free protocol defined under the <a target="_blank" href="https://openwebfoundation.org/legal/the-0-9-agreements---necessary-claims">Open Web Foundation Agreement, Version 0.9</a>, the Open Graph Protocol (henceforth the OGP) allows page authors to mark up specific metadata to help other sites provide information about the linked content.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎩</div>
<div data-node-type="callout-text"><strong>Content</strong> is the key word here; remember that HTTP provides a means of retrieving data <em>in general; </em>it's not specifically limited to web pages. Any resource can be transmitted over HTTP including images, video, and other data constructs. The OGP makes provisions for quite a few data types!</div>
</div>

<p>This, I'm sure you'll agree, is much easier then finangeling a headless browser. Let us make use of it!</p>
<h1 id="heading-the-makeup-of-the-metadata">The makeup of the Metadata</h1>
<p>The OGP is a fairly simple affair, relying on <code>&lt;meta&gt;</code> tags embedded into the <code>&lt;head&gt;</code> tag of a response. Which is appropriate, as this is indeed <a target="_blank" href="https://www.w3schools.com/tags/tag_head.asp">what said tag is for</a>.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🛎</div>
<div data-node-type="callout-text">It is important to note that we are not referring to the HTTP Headers here. The <code>&lt;head&gt;</code> tag is, confusingly, part of the body of a HTTP response.</div>
</div>

<p>Open Graph describes resources as <em>objects</em> and provides some common properties from which to obtain information. Each property name starts with <code>og:</code> to indicate it belongs to the Open Graph namespace. Four properties are required, and seven are considered optional (but generally recommended).</p>
<h2 id="heading-required-properties">Required Properties</h2>
<p><code>og:title</code> has the object's title as it should be displayed when embedded.<br /><code>og:type</code> details what type of object this page contains (such as <code>website</code> or <code>image</code>).<br /><code>og:image</code> is a URL to an image which should be used as part of the preview.<br /><code>og:url</code> provides the <em>canonical</em> URL for the object in question.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">👁‍🗨️</div>
<div data-node-type="callout-text">A <em>canonical </em>URL typically means one that provides a fixed, permanent reference to the primary version of some resource; The OGP uses it to mean a permanent, unique URL for a piece of content.</div>
</div>

<p>This is what the Required Properties might look like for a site detailing Professor Eliza Williams' research into Difference Engine Temperature Compensators:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/36dfac00-1f96-4e74-9326-6a5f28e62892">https://snappify.com/view/36dfac00-1f96-4e74-9326-6a5f28e62892</a></div>
<p> </p>
<h2 id="heading-optional-properties">Optional Properties</h2>
<p>Provisions are also made for additional information; Knowledge is Power after all.</p>
<p><code>og:description</code> is a one-to-two sentence description of the object in question.<br /><code>og:site_name</code> gives the name of the larger website an object belongs to, if any.<br /><code>og:locale</code> indicates which linguistic locale the tags are marked up in.<br /><code>og:locale_alternate</code> offers an array of other locales available.<br /><code>og:determiner</code> supplies the word which should fall before this object's title in a sentence ("<em>the</em>", "<em>an</em>" et al).</p>
<p><code>og:audio</code> provides a URL to an audio file to 'accompany' this object.<br /><code>og:video</code> advises a URL to a video file that 'complements' the object.</p>
<p>(Mostly straightforward, although you might be curious why audio files <em>accompany</em> but video files <em>complement</em>, and what, precisely, those verbs are intended to convey. I am also curious; if you know, I beseech you to leave a comment.)</p>
<p>Professor Eliza is a Modern Woman, so her site includes all of the above:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/18a02c80-cf99-4f7b-ac55-89f6247ff3a4">https://snappify.com/view/18a02c80-cf99-4f7b-ac55-89f6247ff3a4</a></div>
<p> </p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🛎</div>
<div data-node-type="callout-text">You might notice the repeated instance of <code>og:locale_alternate</code>. This is how arrays of data are encoded in the Open Graph protocol. You may find this odd; I agree. And yet.</div>
</div>

<h2 id="heading-further-types-of-object">Further Types of Object</h2>
<p>The <code>website</code> type is perhaps the most common object embedded with the OGP, but the protocol includes many other object. These objects, in turn, have their own structured properties, namespaced as <code>og:type_of_object</code>. For instance, the <code>image</code> type makes the dimensions of an image available as <code>og:image:width</code> and <code>og:image:height</code>.</p>
<p>The full list is available <a target="_blank" href="https://ogp.me/">at the source</a>; We shaln't discuss it here in the interests of brevity.</p>
<hr />
<h1 id="heading-so-what-shall-we-build">So what shall we build?</h1>
<p>Let us build a link preview component for <a target="_blank" href="https://kit.svelte.dev/">SvelteKit</a> which takes a URL and renders for us a lovely preview of the associated content, using the OGP metadata embedded therein.</p>
<p>For ease of use, we shall use TypeScript to help ensure correct data management. The completed component should be pleasing to the eye and behave well different resolutions.</p>
<p>We shall, at first, restrict ourselves to displaying only the basic metadata; Whilst the support for extra content types is robust, we do not, for the first attempt, need the extra complexity.</p>
<p>We shall also make an assumption which, while unkind, is not unfair. Developers are only human and may, at times, make mistakes, causing them to omit some or all of the required fields. As such, our component will <em>not</em> assume that the provided metadata is correct.</p>
<p><strong>Our Requirements are thus:</strong></p>
<ul>
<li><p>Display the basic image along with the title, and provide a link to the canonical URL</p>
</li>
<li><p>Exhibit a responsive, fetching design</p>
</li>
<li><p>Allow for incorrect metadata</p>
</li>
<li><p>Provide developer friendly interface, inc. Typescript.</p>
</li>
</ul>
<hr />
<p>In future entries, we shall endevour to provide more useful default behaviour; enrich our support for divers content types, and package our component as an installable library which can be distributed at will. Such fancies, however, shall have to wait; For now, our target is a component that will look like the following when used in situ:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706501703848/4231c078-76d9-476b-8a63-d4251be09c57.png" alt class="image--center mx-auto" /></p>
<p>That looks rather fetching, don't you think?</p>
<hr />
<h1 id="heading-getting-ready">Getting Ready.</h1>
<p>To begin, I've created a basic <a target="_blank" href="https://kit.svelte.dev/">SvelteKit</a> site posessing an assortment of pages, each professing a different kind of OpenGraph object. The site uses images from <a target="_blank" href="https://unsplash.com/">UnSplash</a> as preview images and is otherwise fairly unremarkable.</p>
<p>Having said that, the index will serve as a gallery showing how the finished component will render for diverse types, so it may prove of some interest. You can find it at <a target="_blank" href="https://ogcomponentdemo.gentlehacker.codes/">https://ogcomponentdemo.gentlehacker.codes/</a> and the source lives at <a target="_blank" href="https://github.com/DylanLacey/OGComponentDemo">https://github.com/DylanLacey/OGComponentDemo</a>.</p>
<hr />
<h1 id="heading-onwards">Onwards!</h1>
<p>In my next missive, we shall implement the basis of our component, and briefly discuss the correct architecture of SvelteKit applications.</p>
]]></content:encoded></item><item><title><![CDATA[The New Year, for Sorcerers.]]></title><description><![CDATA[Greetings, ADHD folk! It's 2024 and I have two messages for you. Firstly HAPPY NEW YEAR! You're going to CRUSH IT this year, I can feel it in my waters.
Secondly, don't buy that new planner/productivity tool/todo app. Seriously. We both know it won't...]]></description><link>https://blog.gentlehacker.io/the-new-year-for-sorcerers</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-new-year-for-sorcerers</guid><category><![CDATA[ADHD]]></category><category><![CDATA[organization]]></category><category><![CDATA[getting started]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Thu, 04 Jan 2024 10:03:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/DKix6Un55mw/upload/9d4ef95258ec55829c5b50e6c50f88c6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Greetings, ADHD folk! It's 2024 and I have two messages for you. Firstly HAPPY NEW YEAR! You're going to CRUSH IT this year, I can feel it in my waters.</p>
<p>Secondly, don't buy that new planner/productivity tool/todo app. Seriously. We both know it won't help; They're not <em>for</em> you. They're for the NTs with their boring, functional executives.</p>
<p>You're a Sorcerer Bard; you can't equip Paladin gear. You need different tools and different spells for your adventure. Herein I shall do my best to identify some. With (some) citations! Lovely.</p>
<h2 id="heading-the-tldr">The TL;DR</h2>
<ol>
<li><p>Take your pills; Eat; Exercise; Sleep.</p>
</li>
<li><p>Body Doubling</p>
</li>
<li><p>Pomodoro</p>
</li>
<li><p>Digital todo <em>schedules</em>, not <em>lists</em></p>
</li>
<li><p>Get a small success in first thing</p>
</li>
<li><p>Break tasks into tiny, well defined tasks, as needed</p>
</li>
<li><p>Be pessimistic in time estimates</p>
</li>
<li><p>Make it your own</p>
</li>
</ol>
<h2 id="heading-potions-and-training">Potions and Training</h2>
<p>Sourcery requires mental focus. Bards need stamina. You need to drink your Potion of Focus (be it Ritalin, Dex, or something else) and make sure you eat, sleep, and exercise, before you're ready to set off on your quest.</p>
<h2 id="heading-its-dangerous-to-go-alone">It's Dangerous to Go Alone</h2>
<p>Arrange a body double; Ideally someone who will do the work of organising said doubling!</p>
<details><summary>What is Body Doubling?</summary><div data-type="detailsContent">Body doubling is the practise of intentionally including someone else in your space, physically or virtually, to ensure accountability and progress.</div></details>

<p>Is it efficacious? Despite being widely practised, significant research is yet to be conducted. Dobrowolski believes that body doubling introduces external pressure which, conversely, helps keep the ADHD learner engaged (Dobrowolski, 2022). Eagle et al found that</p>
<blockquote>
<p>many people were unfamiliar with the term but had intuitively been using the strategy (Eagle et al, 2023)</p>
</blockquote>
<p>Which suggests we're participating because we've found it helpful. Clever us!</p>
<h2 id="heading-short-rests">Short Rests</h2>
<p>I started a linguistics degree in 2021 (because that's a sane thing for a Software Engineer employed full time to do) and part of the course on how to be successful at language learning made us try the Pomodoro technique. I hated it. It also worked <em>really well</em>.</p>
<details><summary>What is Pomodoro?</summary><div data-type="detailsContent">Pomodoro is a time-chunking method where you pick a task, then start a timer for 20-25 minutes and work exclusively on the task. When the timer goes off, you take a 5 minute break. Every 4 cycles, take a 15-30 minute break.</div></details>

<p>Is it efficatious? One of the best ways to get us going is <em>strong incentives</em> (Dovis et al, 2021). Pomodoro provides two such incentives. Firstly, they function as a deadline, providing urgency (found to be helpful in a work environment by Lasky et al, 2016). Secondly, you get a reward every 5 minutes, when you can do whateverthefuck you want! The method includes built in termination times, so you don't hyperfocus into oblivion.</p>
<h2 id="heading-use-specters-not-scribes">Use Specters, not Scribes</h2>
<p>You will forget the planner. I assure you. But, you must still keep track of every tedious task and need in some fashion, and that fashion should be digitally, with the spirits of the cloud.</p>
<p>You cannot lose the cloud.</p>
<p>Furthermore, you should avoid having todo lists and try having a todo <em>schedule</em>. If something is important enough to do, it is important enough to schedule time for. If it isn't, you probably won't remember it anyway.</p>
<p>Please don't take that pointed tone with me. I don't say it to be cruel.</p>
<h2 id="heading-long-rests">Long Rests</h2>
<p>We suffer greatly when forced to estimate exactly how long it will take to ride from Candlekeep to Waterdeep, or how the amount of time we need to mend that <em>damnable</em> gauntlet that keeps snagging. Be pessimistic. Double estimates. Record how many Pomodoros it takes to do something, so you'll have a good idea for next time.</p>
<h2 id="heading-stab-the-goblin-squishy-squishy-goblins">Stab the Goblin. Squishy, Squishy Goblins.</h2>
<p>Start each day with a simple, easy to accomplish task, so you gather XP at the first step. If it's something you enjoy, it's like getting some loot, as well.</p>
<p>Perhaps you don't have a simple, easy to accomplish task? I very much doubt that, but in that case, what is the smallest possible thing you could do to progress a larger task? You might not be ready to slay the Wyvern, but you can ask at the tavern if anyone's lost any sheep, lately. Turn the bigger task into a pile of Goblins <em>as you need too</em>, rather than all at once.</p>
<h2 id="heading-write-your-own-spells">Write your own spells</h2>
<p>You may, <em>of course</em>, need more systematic structure. Conjure it yourself! Any system with rules from outside will make you feel stifled and stuck.</p>
<p>Try to construct the most minimal system you can, and feel free to change it up. We get bored easily, and productivity systems can be <em>dreadfully</em> dreary. Your system should be friviolous, and rewarding.</p>
<h2 id="heading-a-grand-experiment">A grand experiment</h2>
<p>This year, I am trying something new myself, that being <em>themes</em>. I have no evidence this will work, but I shall attempt to tackle shame by committing to <em>anything</em> that isn't for the furthering of my themes. I am still free to dabble and drift, but shall only be focusing, when concerned, on two areas:</p>
<ol>
<li><p>Career - I shall launch the app I have been working on, on and off, for two years now.</p>
</li>
<li><p>Health - Heavy things, the repetitive lifting and lowering of said.</p>
</li>
</ol>
<h2 id="heading-good-luck-on-your-quest">Good luck on your quest!</h2>
<hr />
<h1 id="heading-references">References</h1>
<p>Dobrowolski, S. (2022). <em>Attention Is Not the Solution: Experiences of University Students With ADHD With Remote Learning</em> (Bachelor's thesis, University of Twente).</p>
<p>Dovis, S., Van der Oord, S., Wiers, R.W. <em>et al.</em> Can Motivation Normalize Working Memory and Task Persistence in Children with Attention-Deficit/Hyperactivity Disorder? The Effects of Money and Computer-Gaming. <em>J Abnorm Child Psychol</em> <strong>40</strong>, 669–681 (2012). <a target="_blank" href="https://doi.org/10.1007/s10802-011-9601-8">https://doi.org/10.1007/s10802-011-9601-8</a></p>
<p>Eagle, T., Baltaxe-Admony, L. B., &amp; Ringland, K. E. (2023, October). Proposing Body Doubling as a Continuum of Space/Time and Mutuality: An Investigation with Neurodivergent Participants. In <em>Proceedings of the 25th International ACM SIGACCESS Conference on Computers and Accessibility</em> (pp. 1-4).</p>
<p>Lasky, A. K., Weisner, T. S., Jensen, P. S., Hinshaw, S. P., Hechtman, L., Arnold, L. E., ... &amp; Swanson, J. M. (2016). ADHD in context: Young adults’ reports of the impact of occupational environment on the manifestation of ADHD. Social Science &amp; Medicine, 161, 160-168.</p>
]]></content:encoded></item><item><title><![CDATA[Approaching from the North]]></title><description><![CDATA[Many, many thousands of Developers have worked on data and infrastructure solutions for Northwind Traders, the speciality foods import/export group. In fact, I'd suggest that it's the most widely re-implemented system of all time (second only to devs...]]></description><link>https://blog.gentlehacker.io/approaching-from-the-north</link><guid isPermaLink="true">https://blog.gentlehacker.io/approaching-from-the-north</guid><category><![CDATA[Devops]]></category><category><![CDATA[Heroku]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[PaaS]]></category><category><![CDATA[northface]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Tue, 12 Sep 2023 05:25:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/iDzKdNI7Qgc/upload/a36573b11bf8474d587839e558dd337c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many, <em>many</em> thousands of Developers have worked on data and infrastructure solutions for Northwind Traders, the speciality foods import/export group. In fact, I'd suggest that it's the most widely re-implemented system of all time (<em>second only to devs writing ToDo apps</em>).</p>
<p>Why is that? Because Northwind Traders is the fictitious company Microsoft uses for examples and education of its database products, starting way back in the <a target="_blank" href="https://en.wikiversity.org/wiki/Database_Examples/Northwind">Microsoft Access</a> days. Modelling Northwind's customer/product/invoice relationships was my first exposure to normalisation, linking, and calculations.</p>
<h1 id="heading-yes-and">Yes, and?</h1>
<p>I was thinking about Northwind today because I have another data modelling task that relies on Office-esque tooling. Microsoft Access might be long gone, but I'd argue the replacement was there all along: Spreadsheets. Simple, powerful, and basic tabular data is managed <em>waaaaaay</em> easier than by spinning up a CRUD instance.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💥</div>
<div data-node-type="callout-text"><strong>Fact Check </strong>Access <a target="_blank" href="https://www.microsoft.com/en-au/microsoft-365/access">still exists</a>?! Mind blown.</div>
</div>

<p>That's why I used it to build what is effectively a CSV generator using Google Sheets and now, reasons which are good but not interesting., I've built an app on top of it and I need to put it somewhere.</p>
<details><summary>The Good But Not Interesting Reasons, should you care</summary><div data-type="detailsContent">I started learning Japanese three years ago, and my primary tool has been the flashcard app, <a target="_blank" href="https://apps.ankiweb.net/">Anki</a>. It does everything I need, but the card-adding UX <em>sucks</em>. However, Anki supports CSV, so I use Sheets to manage my card data and just import the data into Anki. I have several thousand cards now and it's working great... With the sole exception of search. Due to the wonders of the <a target="_blank" href="https://en.wikipedia.org/wiki/Japanese_writing_system">Japanese writing system</a>, combined with the not-great search interface, I decided to build a full-text search front-end, while relying on Sheets for data management and, frankly, because it's easier then coding up a full web-based editor.</div></details>

<p>Crucially, I need to get a SvelteKit/Sheets API/MongoDB app online, and I <em>want</em> to do that without having to worry about infrastructure, deployment config, or complicated environmental syncing. So, I decided to check out another North-named company, <a target="_blank" href="https://northflank.com/">Northflank</a>.</p>
<h1 id="heading-what-is-northflank">What is Northflank?</h1>
<p>Northflank is a comprehensive developer platform designed for scale. It provides a slick, unified interface for all your services and projects, optimized for DevOps and fully configurable. Northflank's goal is to make managing complex cloud native applications simple, accommodating any stack, providing API/CLI/UI access, and generally providing your entire team with the chance to <em>use</em> your tools, instead of spending all your time <em>supporting</em> them.</p>
<h1 id="heading-getting-started">Getting Started</h1>
<p>I signed up for Northflank, pootled around the docs, and realised I only needed to do three things:</p>
<ol>
<li><p>Link my repo to a new "Service" inside a "Project"</p>
</li>
<li><p>Add a MongoDB instance</p>
</li>
<li><p>Do some basic environment configuration</p>
</li>
</ol>
<h1 id="heading-project-setup">Project Setup</h1>
<h2 id="heading-new-project">New Project</h2>
<p>When setting up a new Service, I was prompted to create a project first; Since I've got exactly one user (me) and my needs aren't that complex, I happily fit within Northflank's free tier.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694483207956/0487676b-8da5-480c-8719-a708367828fc.png" alt class="image--center mx-auto" /></p>
<p>Then I had to do the hard thing and name it, along with choosing a colour for the icon, which is a neat touch.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Northflank names are globally unique and form part of the identifier used when referring to the project externally. They also form the public domain name, with the format <code>[port]--[service]--[project]--[entity-dns-id].code.run</code></div>
</div>

<p>Right now (September '23) Northflank supports six regions in <a target="_blank" href="https://northflank.com/docs/v1/application/getting-started/create-a-project#project-region">Europe, US and SE-Asia</a>. I was offered a choice of US Central or Europe West for my project, which I'm guessing is a free tier limitation. Australia? Never heard of it.</p>
<h2 id="heading-new-service">New Service</h2>
<p>Next up, my app code. Northflank calls user-based code "Services", and choosing to add a new one lets me grab code from a Git repo or a Docker registry.</p>
<p>I could also have chosen to add background or async tasks as a "Job", databases and message queues as "Addons", or create a group of encrypted secrets... And if I wasn't sure what to do, Northflank provides several one-click deployments.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694483753639/7bf13abb-d0b8-48bc-a6cb-0b737cdbc17d.png" alt class="image--center mx-auto" /></p>
<p>I grabbed my web app's service "frontend" because I am very creative. I allowed the Northflank app access to Github, then proceeded to stare at "Build Options" for a while.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694484046855/8a96eabd-6434-4d6a-b05e-61b65d2e8b84.png" alt class="image--center mx-auto" /></p>
<p>I don't have a Dockerfile, but the "Buildpack" option implies it needs to be a <em>Heroku</em> buildpack, and I also don't have one of those. I <em>am</em> a lazy developer, though, so I decided to see if that option would work.</p>
<p>I left everything else default and clicked "<em>create service</em>", and Northflank got on with the hard work of building and deploying.</p>
<p>Then it fell down.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694484172328/9db0c3f6-5806-4ca1-b73d-26e0a281cd33.png" alt class="image--center mx-auto" /></p>
<p>Clicking the failed build shows us our build log, so we're able to check out what happened, and what happened is that there's no configured MongoDB instance.</p>
<pre><code class="lang-bash">2023-09-12T02:02:13.866831708Z stderr F RollupError: <span class="hljs-string">"NF_MONGODB_MONGO_SRV"</span> is not exported by <span class="hljs-string">"<span class="hljs-variable">$env</span>/static/private"</span>, imported by <span class="hljs-string">"src/lib/db.ts"</span>.
2023-09-12T02:02:13.866758961Z stderr F error during build:
2023-09-12T02:02:13.866751237Z stderr F 4: await client.connect()
2023-09-12T02:02:13.866723785Z stderr F 3: const client = new MongoClient(NF_MONGODB_MONGO_SRV)
2023-09-12T02:02:13.866701994Z stderr F             ^
2023-09-12T02:02:13.866616414Z stderr F 2: import { NF_MONGODB_MONGO_SRV } from <span class="hljs-string">'$env/static/private'</span>
2023-09-12T02:02:13.866607016Z stderr F 1: import { MongoClient } from <span class="hljs-string">"mongodb"</span>
</code></pre>
<p>Which makes sense; I'm not on my local machine and I've not created a Mongo database in my Northflank project yet. Let's do that.</p>
<h1 id="heading-adding-mongodb">Adding MongoDB</h1>
<p>Adding a MongoDB instance is as simple as clicking "Create New", choosing "Addon", and filling in the blanks. My options included Postgres, MySQL, Redis, and MongoDB (along with MinIO and RabbitMQ if you're so inclined). I clicked MongoDB and added a name (as well as a name for the database within Mongo itself), and that was it. A few minutes wait and Mongo was provisioned.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694485063005/550bf64d-253a-4bcc-b1d0-8fd189a39ac9.png" alt class="image--center mx-auto" /></p>
<p>However, there's a catch. Those variables are name-spaced to the Mongo instance itself; they're not yet available to other services in the project. Let's fix that, shall we?</p>
<h1 id="heading-environment-configuration">Environment Configuration</h1>
<h2 id="heading-add-a-secret-group">Add a Secret Group</h2>
<p>Clicking the "Link to secret groups" button created me a new secret group. Secret groups are exactly what they sound like; a group of secrets that can be bound to services. It's nice to be able to control which services can see which secrets, instead of giving access to everything.</p>
<p>Northflank also allows you to control whether your secrets are available during <em>buildtime</em>, <em>runtime</em>, or <em>both</em>. That's a neat touch!</p>
<p>Northflank automatically namespaces your variables as <code>NF_[addon_name]_[variable_name]</code>, which is a little lengthy for my tastes, so I just aliased this back to <code>MONGO_SRV</code> :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694485468249/6df35be2-677a-4363-a8de-acf97ec5ce5d.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-rebuild-using-secrets">Rebuild using Secrets</h2>
<p>Northflank helpfully applied this group to all services and jobs, so I should now be able to re-run my frontend build and get...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694492310385/41652c4e-7821-4398-91ce-5235f2025d0b.png" alt class="image--center mx-auto" /></p>
<p>Ahh.</p>
<pre><code class="lang-bash">2023-09-12T04:16:19.940548529Z stderr F Error [MongoServerSelectionError]: <span class="hljs-built_in">read</span> ECONNRESET
</code></pre>
<p>I see.</p>
<p>This took some documentation diving to figure out. It turns out that your <em>build environment</em> is isolated from your <em>run environment</em>. My app is trying to connect to Mongo during the build process (for... <em>reasons</em>) and can't do so, because I've not made it publically available.</p>
<p>I can do so by going into the Mongo addon network settings and choosing "<em>Publically accessible</em>", which I don't <em>love</em> as a solution because I'd rather it be categorically impossible to contact my Mongo instance from outside the project itself, but what can you do.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">❓</div>
<div data-node-type="callout-text"><em>I am aware that in my case, what you can do is fix the bug that tries to access the DB during build. This wouldn't be viable if I was, say, loading some content for a static site, or building a white-labeling product for a suite of clients.</em></div>
</div>

<p>Adding the variable caused the service to rebuild, and this time, it succeeded. Northflank has graciously given us a domain name and as such...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694494091603/1736b77b-a90d-478d-94dc-abcd11c7669a.png" alt class="image--center mx-auto" /></p>
<p>Superb.</p>
<h2 id="heading-connect-to-our-dev-environment">Connect to our Dev Environment</h2>
<p>One last piece; Because Northflank is designed for CI/CD workflows and multi-environment teams, it would be helpful to be able to access cloud resources from my local machine, for debugging, remediation, and general development.</p>
<p>Handily, they provide a CLI which can do just that. A little <code>npm i -g @northflank/cli</code> and a touch of <code>northflank login</code>, and I'm able to fully control my setup with the CLI.</p>
<p>From there, you create a <em>context</em>, a set of related permissions, options and resources. Contexts empower things such as providing a 3rd party tool with limited control to restart services, or separating your team and personal projects.</p>
<p>I want to spin up my project locally but connect to the cloud Mongo instance. I can do this with the public URL but, if I had not had to enable public access for the build step, the <code>northflank</code> tool would let me run <code>sudo northflank forward addon --projectId ankiedit --addonId mongo</code> to forward traffic to the private networking address. (Conveniently, the command to do this is pre-filled in connection settings, so you don't even have to remember what the CLI flags are.)</p>
<h1 id="heading-thoughts">Thoughts</h1>
<p>Northflank is <em>quite</em> nice. I appreciate the balance it strikes between control and convenience; The unified, team-accessible control panel is very powerful and having internal logging should go some way to providing in-app remediation. They've sunk a lot of effort into ensuring that Northflank works <em>with</em> your tools instead of <em>against</em> them, with multiple means of interaction, ample monitoring and useful in-built options.</p>
<p>Also, free tiers are a rare beast now, and sometimes you just want to put a wee app online, without paying $5 for a whole server or being invoiced every month for a single-digit amount of cents.</p>
<p>There are caveats. Free tiers have a habit of becoming "Free" trials (or simply getting cancelled), and I wouldn't use them to deploy anything you <em>must</em> have online unless you're prepared to pay at some point. The richness of options is occasionally confusing in a manner the documentation doesn't solve <em>particularly</em> well, and it's easy to leave things default because you just don't notice them.</p>
<p>Finally, the inability to allow access to private resources during the build step is a bit wonky; it's quite common to front-load assets, branding or content processing at build time, so it would be nice to be able to do so without giving up the security of internal-only networking.</p>
<p>All in all, using Northflank was fun, and impressive, and I think it's worth checking out.</p>
]]></content:encoded></item><item><title><![CDATA[The Week That Was: October 10th, 2022]]></title><description><![CDATA[There really is no apology here.  It has been almost a month.  It was a very rough month, indeed, ending as it did with two examinations, an abortive attempt at seeing a doctor, a dental filling, an essay, and two conferences in a row.
Well, the seco...]]></description><link>https://blog.gentlehacker.io/the-week-that-was-2022-10-10</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-week-that-was-2022-10-10</guid><category><![CDATA[Productivity]]></category><category><![CDATA[Browsers]]></category><category><![CDATA[storage]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Fri, 21 Oct 2022 01:20:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/RsAssD3GGt8/upload/v1666315364992/8MCK8_7j1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There really is no apology here.  It has been almost a month.  It was a very rough month, indeed, ending as it did with two examinations, an abortive attempt at seeing a doctor, a dental filling, an essay, and two conferences in a row.</p>
<p>Well, the second is still imminent.  I shall be travelling to San Francisco to present at <a target="_blank" href="https://qconsf.com/">QConf</a>, 'pon the topic: "Are Programming Languages... actually Languages?".  A brief exploration of Second Language Acquisition, and what it can teach us about upskilling our teams.  I am Quite Excited About It.</p>
<p>Nevertheless!  Onward and upward.</p>
<h1 id="heading-for-developers">For Developers</h1>
<h2 id="heading-be-productive">Be Productive</h2>
<p><a target="_blank" href="https://abinoda.substack.com">Abi Noda</a> has summarised a paper from Meyer et al detailing what makes for a <a target="_blank" href="https://abinoda.substack.com/p/developer-workday">Good Developer Workday</a>. Good days are those with an appropriate amount of collaboration, where the developer feels they were productive and added value, and most of all:</p>
<blockquote>
<p>Good Days go as planned.</p>
</blockquote>
<h2 id="heading-store-things-in-the-users-browser">Store Things in the User's Browser</h2>
<p>There are many, unique and wonderful APIs in hidden in the depths of Chromium and Friends.  One of those is <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a>, a client-side API for storing structured data on the client side.  This can be a most efficacious way of storing data such as, say, search indexes, document drafts, or infrequently-updated reference data.</p>
<p>The GentleHacker suggests perusing the Web.Dev introduction, <a target="_blank" href="https://web.dev/indexeddb/">should you wish to learn more</a>.</p>
<h2 id="heading-or-perhaps-not">Or perhaps not!</h2>
<p><a target="_blank" href="https://www.rdegges.com/">Randel Degges</a> concurs, partly because he believes that one of the alternatives, that of LocalStorage, is <em>always a bad idea</em>.  You can read his take <a target="_blank" href="https://dev.to/rdegges/please-stop-using-local-storage-1i04">here</a>; it's excellent nuanced.</p>
<h2 id="heading-be-secure">Be secure</h2>
<p>It is a sad fact that the internet is awash with rapscallions, vagabonds and charlatans of the highest water.  As such, the clever girls and boys who work in technology have come up with a multiplicity of techniques for ensuring the safety and security of you and your customers.</p>
<p>One such technique is <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">Subresource Integrity</a>, which allows you to specify a cryptographic hash for each resource you retrieve, keeping you safe from MITM attacks on CDNs and such.</p>
<p>How clever.</p>
<h1 id="heading-with-thanks">With Thanks</h1>
<p>Should you have enjoyed this edition of The Week That Was, I would greatly appreciate you sharing with your fellows, or a Subscription or Follow over on <a target="_blank" href="https://blog.gentlehacker.io/">My Web Log</a>.</p>
<p>Should you wish to find previous entries, you shall find them all collected <a target="_blank" href="https://blog.gentlehacker.io/series/the-week-that-was">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The Week That Was]]></title><description><![CDATA[Once Again
Seven Days makes a week.  Unless things have gone very wrong... which is always a possibility!
I am coming up on the Difficult™️ part of my Trimester, whereupon I need to perfect my Japanese; Get ready to be a Teaching Assistant for Testin...]]></description><link>https://blog.gentlehacker.io/the-week-that-was-26-sep-2022</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-week-that-was-26-sep-2022</guid><category><![CDATA[emulators]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[documentation]]></category><category><![CDATA[React]]></category><category><![CDATA[team]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Tue, 27 Sep 2022 02:32:44 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-once-again">Once Again</h1>
<p>Seven Days makes a week.  Unless things have gone <em>very</em> wrong... which is always a possibility!</p>
<p>I am coming up on the Difficult™️ part of my Trimester, whereupon I need to perfect my Japanese; Get ready to be a Teaching Assistant for <a target="_blank" href="https://hopin.com/events/testing-for-good-workshops/registration">Testing for Good</a>, complete a new talk (more on those later); and sundry other responsibilities.</p>
<p>Such fun.</p>
<h1 id="heading-the-gentlehacker-at-large">The GentleHacker At Large</h1>
<h2 id="heading-testing-for-good">Testing for Good</h2>
<p>Perhaps this is your first time hearing of <a target="_blank" href="https://hopin.com/events/testing-for-good-workshops/registration">Testing for Good</a>.  It is a free up-skilling workshop for all testers, SDETs and Developers, run by industry leaders, with hands-on exercises and immediately applicable knowledge.  While free, you may make donations in thanks, with the entire sum going towards Charitable Works.  For October, the funds are going towards the <a target="_blank" href="https://www.ewg.org/who-we-are/our-mission">Environmental Working Group</a>.</p>
<h2 id="heading-qconf">QConf</h2>
<p><a target="_blank" href="https://qconsf.com/">QConf</a> is a Product-Pitch-Free conference for technology leaders, helping them stay on top of emerging trends.  I am fortunate enough to be speaking in October, upon the topic of whether <a target="_blank" href="https://qconsf.com/">Programming languages are <em>actually languages</em></a>.</p>
<p>This talk grew out of my observations as a Linguistics student, that while complicated tooling and languages probably don't meet the full requirements to count as a human language, they do share many similarities.  As such, research on Second Language Acquisition should have much to tell us about how we can learn new tools and techniques.  I intend to review the contemporary research, demonstrate the similarities and differences, and discuss what this <em>means</em> for a modern team.</p>
<p>It is a brand new talk and to be quite honest feels a bit like I've invented myself a Thesis, except one without marks, or an ensuring qualification.  Nevertheless!  I am most keen to deliver it.  Should you be interested, please grab a ticket, or invite me to your own conference!</p>
<h1 id="heading-items-of-interest">Items of Interest</h1>
<h2 id="heading-for-developers">For Developers</h2>
<h3 id="heading-writing-an-emulator">Writing an Emulator</h3>
<p>A fun little read by <a target="_blank" href="https://hashnode.com/@literalEval">Ravidev Pandey</a> on writing your own emulator.  Of an emulator.  <a target="_blank" href="https://literaleval.hashnode.dev/guide-to-writing-your-first-emulator-chip-8">A meta-emulator, if you will</a>.</p>
<h3 id="heading-conditionally-spreading-an-object-in-javascript">Conditionally Spreading an Object in JavaScript</h3>
<p>Object spreading allows you to populate one object with the properties of another:</p>
<pre><code><span class="hljs-keyword">let</span> raven = { <span class="hljs-attr">notes</span>: <span class="hljs-string">"flat"</span>, <span class="hljs-attr">letters</span>: <span class="hljs-string">"aberruu"</span> }
<span class="hljs-keyword">let</span> writing_desk = { ...raven, <span class="hljs-attr">bills</span>: <span class="hljs-string">"outstanding"</span> }

<span class="hljs-built_in">console</span>.log(writing_desk)
<span class="hljs-comment">// { notes: "flat", letters: "aberruu", bills: "outstanding" }</span>
</code></pre><p>But supposing you only wish to spread an object under certain circumstances?  Well, <a target="_blank" href="https://www.amitmerchant.com/conditionally-spreading-objects-in-javascript/">Amir Merchant</a> has a solution for you.</p>
<h3 id="heading-is-your-memory-poor">Is Your Memory Poor?</h3>
<p>Mine certainly is, which is why I like using <a target="_blank" href="https://kapeli.com/dash">Dash</a> for documentation.  (Although, ironically, I often forget to do so).</p>
<p>This is why <a target="_blank" href="https://hynek.me/articles/productive-fruit-fly-programmer/">Hynek Schlawack's article</a> on the Dash Ecosystem was a must-read.</p>
<h3 id="heading-search">Search</h3>
<p>I stumbled across <a target="_blank" href="https://www.meilisearch.com/pricing">Meilisearch</a>, a configurable search solution with a free Open Source version and low-cost hosted edition.  I've not used it yet but it remains in my mind, pending.</p>
<h2 id="heading-for-designers">For Designers</h2>
<h3 id="heading-react-and-all-its-options">React and all it's Options</h3>
<p>Part of the love for <a target="_blank" href="https://reactjs.org/">React</a> stems from the ability to create composable, separate components, allowing teams to easily mix-and-match elements as needed.  This all sounds very good in theory but can prove something of a stumbling block for new players.</p>
<p>Which is why <a target="_blank" href="https://twitter.com/jxnblk">Brent Jackson</a>'s list of <a target="_blank" href="https://jxnblk.com/blog/patterns-for-style-composition-in-react/">Style Composition Patterns</a> is an excellent resource.</p>
<h2 id="heading-for-managers">For Managers</h2>
<h3 id="heading-are-the-teams-alright">Are The Teams Alright?</h3>
<p>Atlassian has <a target="_blank" href="https://www.atlassian.com/blog/state-of-teams">recently published a "State of Teams" report</a>, looking at whether teams are healthy, and what that even means.</p>
<p>Of sad interest is that only 20% of teams are "Healthy".  This is not due to location (although nearly 2/3rds of the healthy teams are Remote or Hybrid-Remote); Instead, it reflects a lack of alignment and psychological safety.</p>
<p>The GentleHacker finds it miserable and dolorous that over half of the notional adults in our workplaces are unable to conduct themselves in a way that re-enforces psychological safety.  Proper Workplace Conduct should be <em>paramount</em>.  Is your own team healthy?  What are the most effective measures for ensuring it is thus?  Please leave a Comment!</p>
<h1 id="heading-note-taking-app-of-the-month-joplin">Note Taking App of the Month : Joplin</h1>
<p>Joplin is an Open Source, Privacy and (ironically) Open-ness focused app for note taking.  It features end-to-end encryption, a plugin architecture, and the project works with security researchers to enhance and improve functionality.</p>
<p>Over at Opensource.com, Richard Chambers interviewed the developer behind Joplin, <a target="_blank" href="https://twitter.com/laurent2233?lang=en">Laurent Cozic</a>, and it is <a target="_blank" href="https://opensource.com/article/22/9/joplin-interview">Worth a Read</a></p>
<h1 id="heading-with-thanks">With Thanks</h1>
<p>Should you have enjoyed this edition of The Week That Was, I would greatly appreciate you sharing with your fellows, or a Subscription or Follow over on <a target="_blank" href="https://blog.gentlehacker.io/">My Web Log</a>.</p>
<p>Should you wish to find previous entries, you shall find them all collected <a target="_blank" href="https://blog.gentlehacker.io/series/the-week-that-was">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The Fortnight That Was]]></title><description><![CDATA[Apologies, dear reader.  The previous two weeks have been a flurry of activity, whose result was the postponement of this collection of entertainments.  It was remiss of me, and I beg your understanding.
Speaking of understanding, I am quite unsure o...]]></description><link>https://blog.gentlehacker.io/the-fortnight-that-was-19-sep-2022</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-fortnight-that-was-19-sep-2022</guid><category><![CDATA[links]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[Design]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Tue, 20 Sep 2022 01:20:05 GMT</pubDate><content:encoded><![CDATA[<p>Apologies, dear reader.  The previous two weeks have been a flurry of activity, whose result was the postponement of this collection of entertainments.  It was remiss of me, and I beg your understanding.</p>
<p>Speaking of understanding, I am quite unsure of what to make of <a target="_blank" href="https://www.theatlantic.com/technology/archive/2015/11/programmers-should-not-call-themselves-engineers/414271/">this older article</a> from the Atlantic.  I myself completed an Engineering degree and must agree that I often find the rigour lacking in the Software industry. As it happens, many countries (including Australia and Canada) consider Engineering a <a target="_blank" href="https://en.wikipedia.org/wiki/Chartered_(professional)">Chartered</a> profession, thus describing all technologists as such is not only wrong, it may even breach the boundaries of legality.</p>
<p>My uncertainty arises from the unpleasantness of contradicting someone's identity, along with the faint whiff of snobbery engendered therein... But there is something to be said for dis-allowing people to use well understood titles if they are unwilling to fulfil the obligations therein.</p>
<p>Speaking also of titles, it is a sad day for many in the Commonwealth countries, including the Gentlehacker.  I think perhaps it is that, regardless of some substantial failings in the institute of Monarchy, the Queen herself seemed to treat being Monarch as a position of service, and lived her life accordingly.  I greatly respect how diligent she was in her attempts to service the emotional needs of her subjects; for strength, for comfort, for unity and a sense of belonging to something great.</p>
<p>She will be missed.  Vale, your Majesty Elizabeth Alexandra Mary Windsor.  Rest in Peace.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663553497770/drJYh6gJ2.png" alt="image.png" /></p>
<h1 id="heading-for-developers">For Developers</h1>
<h2 id="heading-community">Community</h2>
<h3 id="heading-hacktoberfest">Hacktoberfest</h3>
<p><a target="_blank" href="https://hacktoberfest.com/">Impends</a>.  Hacktoberfest, a month-long virtual hackathon, encourages folks of all persuasions to collaborate on open-source projects.  Despite some... regrettable happenings... in earlier years, Digital Ocean's event provides a wonderful opportunity to contribute to Open Source.</p>
<p>Additionally, the design of the website is <em>wonderful</em>.</p>
<h2 id="heading-convenience">Convenience</h2>
<h3 id="heading-automatic-key-vaulting-for-everyone">Automatic Key Vaulting for Everyone</h3>
<p><a target="_blank" href="https://hashnode.com/@SimonSickle">Simon Sickle</a> has written a guide, demonstrating how you can store keys in 1password and have them automatically synced to your terminal.  <a target="_blank" href="https://blog.simonsickle.com/1password-meets-git">Clever Lad</a>. </p>
<h2 id="heading-design">Design</h2>
<h3 id="heading-ascii-diagrams">ASCII Diagrams</h3>
<p>Bring back the old days of funny-symbol-only diagrams, with <a target="_blank" href="https://asciiflow.com/#/">Asciiflow</a>, a lovely free diagramming tool.  It's simple to use, although the options currently are a little basic.</p>
<h3 id="heading-micro-animations">Micro-Animations</h3>
<p>Micro-Animations are those which provide your users with helpful feedback that their actions have been noticed.  Here is a <a target="_blank" href="https://blog.openreplay.com/micro-interactions-using-anime-js">tidy little guide</a> on implementing them using Anime-JS.</p>
<h3 id="heading-open-source-design-system">Open Source Design System</h3>
<p><a target="_blank" href="https://penpot.app/">Penpot</a> is a web-based, open source design system for cross-domain teams, and looks very pleasant.  The Gentlehacker would try it in a heartbeat, if he had any kind of design skill whatsoever.</p>
<h2 id="heading-machine-learning">Machine Learning</h2>
<h3 id="heading-aws-visual-conversation-builder">AWS Visual Conversation Builder</h3>
<p><a target="_blank" href="https://aws.amazon.com/about-aws/whats-new/2022/09/amazon-visual-conversation-builder/">The Zaibatsu</a> has released a visual conversation builder for Amazon Lex, thus enabling no-code chatbot/assistant building.  Quite neat.</p>
<h1 id="heading-for-everyone">For everyone</h1>
<h2 id="heading-flying-car">FLYING CAR</h2>
<h3 id="heading-this-is-not-a-drill">THIS IS NOT A DRILL</h3>
<p>The future has finally arrived!  Samson Sky's <a target="_blank" href="https://thenextweb.com/news/switchblade-flying-car-alot-less-spontaneous-than-hoped">Flying Car</a> has passed FAA Approval!</p>
<p>Yes, it requires a conversion step to go between Road and Air vehicle, but it's a step in the right direction and the beginning of the fulfillment of the promise of future times.  The Future has been a disappointment thus far.  Let me have this, I beg you.</p>
<h1 id="heading-edifying-reading">Edifying Reading</h1>
<h2 id="heading-upon-serverless-applications-and-sessions-thereof">Upon Serverless Applications, and Sessions Thereof</h2>
<p>I have recently begun a series on <a target="_blank" href="https://blog.gentlehacker.io/introduction-building-testing-an-api-for-serverless-sessions">Building an API for Serverless App Sessions</a>, of which I'm rather proud.</p>
<p>Therein, I discuss using API Gateway, DynamoDB and Lambda to build a flexible session authentication system.  Features include separate Web and API authentication, session expiry, and extensibility for differing permissions (inc. authentication delegation).</p>
<p> I would appreciate a share and follow.</p>
<h2 id="heading-for-usability">For Usability</h2>
<p>Usability expert <a target="_blank" href="https://www.nngroup.com/articles/author/jakob-nielsen/">Jacob Nielsen</a> recommends you <a target="_blank" href="https://www.nngroup.com/articles/first-rule-of-usability-dont-listen-to-users/">Stop Listening to Users</a>.</p>
<h1 id="heading-with-thanks">With Thanks</h1>
<p>Should you have enjoyed this edition of <em>The Week That Was</em>, I would greatly appreciate you sharing with your fellows, or a Subscription or Follow over on <a target="_blank" href="https://blog.gentlehacker.io/">My Web Log</a>.</p>
<p>Should you wish to find previous entries, you shall find them all collected <a target="_blank" href="https://blog.gentlehacker.io/series/the-week-that-was">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Step One: Configuring our Data Layer]]></title><description><![CDATA[DynamoDB & Your Sessions
As you may have read in the previous entry in this series, our Session Management API shall use DynamoDB for data storage, and, specifically, will not be using Redis. The reasons for this are many, but the most pertinent one ...]]></description><link>https://blog.gentlehacker.io/step-one-configuring-our-data-layer</link><guid isPermaLink="true">https://blog.gentlehacker.io/step-one-configuring-our-data-layer</guid><category><![CDATA[serverless]]></category><category><![CDATA[sst]]></category><category><![CDATA[sessionStorage]]></category><category><![CDATA[DynamoDB]]></category><category><![CDATA[API Gateway]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 19 Sep 2022 11:45:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Ihml-Sigf6s/upload/v1663310748318/_8zWnrOqJ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-dynamodb-amp-your-sessions">DynamoDB &amp; Your Sessions</h1>
<p>As you may have read in the <a target="_blank" href="https://blog.gentlehacker.io/introduction-building-testing-an-api-for-serverless-sessions">previous entry in this series</a>, our Session Management API shall use DynamoDB for data storage, and, specifically, will <em>not</em> be using Redis. The reasons for this are many, but the most pertinent one is cost. For more on this, consult your nearest Cloud Architect.</p>
<p>(Yes, this is Step One. The previous post is <a target="_blank" href="https://blog.gentlehacker.io/introduction-building-testing-an-api-for-serverless-sessions">The Introduction</a>. Should you wish to follow along with the code as a whole, <a target="_blank" href="https://github.com/DylanLacey/Blog-DynamoDB-Sessions">it's over on GitHub</a>.)</p>
<p>Let the Dynamo spin!</p>
<h1 id="heading-create-a-new-table">Create a new Table</h1>
<p>My SST Stack configurations already has a DynamoDB table called <code>MarukiTable</code>; let's add another one, called <code>MarukiSessionTable</code>:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/12b4e15b-0c00-47c7-acfe-be1ba9a5b258">https://snappify.com/view/12b4e15b-0c00-47c7-acfe-be1ba9a5b258</a></div>
<p> </p>
<p>This table has a standard <code>partition</code> and <code>sort</code> key, along with a Global Secondary Index (w/ requisite keys), along with an <code>expiry</code> column. That column is being marked as the <code>timeToLiveAttribute</code>. DynamoDB allows you to <a target="_blank" href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html">configure a column</a> whose value represents the expiry time (in Unix <a target="_blank" href="https://en.wikipedia.org/wiki/Unix_time">epoch</a> time) of that item. DynamoDB will automatically run background processes to identify and remove expired items, at no cost.</p>
<p>(Because erased data is written to DynamoDB Streams, this feature is useful for a myriad of things, including removing subscriptions, censoring data after specific times, and billing.)</p>
<h1 id="heading-provide-table-access-to-functions-amp-site">Provide table access to functions &amp; site</h1>
<p>I'll also need to give my functions access to my new table, as well as making its name available to my Static Site (Which I'm doing with <code>sst-env</code>, which is not germane to our discussion):</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/156ea541-9f6f-450f-b8b2-328072ecf4ee">https://snappify.com/view/156ea541-9f6f-450f-b8b2-328072ecf4ee</a></div>
<p> </p>
<p>Why am I creating a separate table for sessions, instead of keeping things in the erstwhile Single Table realm? Paranoia, Dear Reader, paranoia. Whilst DynamoDB won't erase anything whose TTL exceeds 5 years in the past (and thus my data is safe even should blank entries evaluate to <code>0</code>), I simply don't like the risk. It would be all too easy for me to, with a moment's inattention, write erroneous code whose execution would add contemporary expiry dates to my entire table. I shan't risk it.</p>
<h1 id="heading-define-session-data-layer">Define Session Data Layer</h1>
<p>I am using the delightful <a target="_blank" href="https://www.dynamodbtoolbox.com/">DynamoDBToolbox</a> to work with my session data, along with <a target="_blank" href="https://day.js.org/">DayJS</a> for time management and <a target="_blank" href="https://github.com/ulid/javascript">ulid</a> for ... ulids. Let's install those now, by adding them to our package.json:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/5c4aec74-1e9b-4139-99ec-130e659ea265">https://snappify.com/view/5c4aec74-1e9b-4139-99ec-130e659ea265</a></div>
<p> </p>
<p>In my <code>functions</code> directory, I have a <code>data</code> subdirectory, where we shall now scribe our table definition:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/64aeaf98-43e0-492d-b04f-7331bcf8fac8">https://snappify.com/view/64aeaf98-43e0-492d-b04f-7331bcf8fac8</a></div>
<p> </p>
<p>The only notable thing here is the table name; I like to provide an obviously mistaken fallback option. That can help short-circuit debugging when you accidentally omit environment variables; It also stops TypeScript complaining that <code>undefined</code> is not <code>string</code>.</p>
<p>Next, we need to create a session <code>Entity</code>. We'll need a unique partition key, along with a way to store the authorized party (the <em>principal</em>) and what mechanism authenticated them. We additionally need a <code>timeToLive</code> value so that automatic session expiry takes place, and it might be worthwhile storing the originating IP of the authentication event. Finally, everyone loves a created/modified timestamp, so we'll add those as well:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/093588a6-dcfb-4965-86a8-509058c04824">https://snappify.com/view/093588a6-dcfb-4965-86a8-509058c04824</a></div>
<p> </p>
<p>DynamoDBToolbox is providing us automatic <code>creation</code> and <code>modified</code> timestamps, as well as helping us fill in some of our data. We're prefixing the session &amp; principal IDs so the data makes a bit more sense when we look at it "<em>raw</em>", lines 21-29. On line 32, we set a default value for <code>expiry</code>, using dayjs to set a value one hour in the future, in the required Unix Epoch Time Format. Finally, we make the authorization mechanism and origin required values.</p>
<h1 id="heading-add-a-global-secondary-index">Add a Global Secondary Index</h1>
<p>One thing we're missing is the ability to look up all sessions for a specific principal. DynamoDB stores data such that data of a specific nature is stored in a specific <em>partition</em>, inside of which individual rows are <em>sorted</em> (hence the key names). Ideologically, DynamoDB expects that users (in this case, our application) know what nature of data (and thus which partition and thus, partition ID) they wish to retrieve data from. As such, almost every operation expects you to provide partition key, with the exception of <code>Scan</code>. The latter scans and returns every item in the table sequentially, optionally filtering items <em>after retrieval but before responding</em>. Scan is not the fastest operation. Generally speaking, don't use Scan.</p>
<blockquote>
<p>Scan is not the fastest operation. Generally speaking, don't use Scan.</p>
</blockquote>
<p>Currently, we can retrieve a session if we know the ID (and we should, since validating IDs is this code's entire balliwick), but finding all sessions for a user would require a <code>Scan</code>, as we don't know the session ID (and thus partition key) on which to query.</p>
<p>We can solve this by adding data into our Global Secondary Index (GSI) fields. GSI's contain a subset of attributes from the table and support the <code>Query</code> operation. They have their own, unique partition key and sort key, which we can instruct DynamoDBToolbox to populate:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.com/view/a4f38912-968a-4489-945f-0aa449607cfc">https://snappify.com/view/a4f38912-968a-4489-945f-0aa449607cfc</a></div>
<p> </p>
<p>We're setting these attributes to <code>hidden</code> because they're not unique; They're simply duplicating data on the table. Each has a prefix, again, to make the raw data clearer, and then we're setting their value with a function.</p>
<p>By querying the table's GSI for <code>Principal|some_principal_id</code>, we can retrieve all sessions for a principal at once, allowing us to (for instance) show them to the user, or bulk invalidate them.</p>
<p>(We could also consider making an index that uses <code>authorizationOrigin</code> as the partition key. That way, we could easily see every authorization request from a specific IP address.)</p>
<h1 id="heading-and-thats-that">And that's that.</h1>
<p>We're ready to write the more finicky bits; Those actively managing sessions. But firstly, dear reader, let us take a break. We shall resume in Step Two!</p>
<p>(Should you enjoy this content, or wish to be notified of the publication of updates, I humbly request you provide me with a Follow and a Like. Of such actions are Viral Content made, and I wish only to help as broadly as I might.)</p>
]]></content:encoded></item><item><title><![CDATA[Introduction - Building & Testing an API for... Serverless Sessions?]]></title><description><![CDATA[Sessions, Dear Reader, are an integral part of how the web functions.  Sessions allow us to overcome the Stateless nature of HTTP and implement niceties such as "knowing who our users are" and "remembering what our users are doing".
I'm currently bui...]]></description><link>https://blog.gentlehacker.io/introduction-building-testing-an-api-for-serverless-sessions</link><guid isPermaLink="true">https://blog.gentlehacker.io/introduction-building-testing-an-api-for-serverless-sessions</guid><category><![CDATA[DynamoDB]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[API TESTING]]></category><category><![CDATA[APIs]]></category><category><![CDATA[sauce-labs]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 19 Sep 2022 04:26:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Ihml-Sigf6s/upload/v1663559078293/Ncz2xkBrn.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Session">Sessions</a>, Dear Reader, are an integral part of how the web functions.  Sessions allow us to overcome the Stateless nature of HTTP and implement niceties such as "<em>knowing who our users are</em>" and "<em>remembering what our users are doing</em>".</p>
<p>I'm currently building a tool to help developers track and share what they're working on, provisionally called <em>Maruki</em>.  Because it's a tool for developers, I want it to have both a full-featured API and a front end.  I also want to avoid as much infrastructure management as I can, and as such, I'm building a Serverless architecture, using the lovely <a target="_blank" href="https://sst.dev">SST</a> toolkit.</p>
<p>Maruki uses Lambda to access a DynamoDB powered data-layer (with what I <em>hope</em> is a good first attempt at <a target="_blank" href="https://www.alexdebrie.com/posts/dynamodb-single-table/">Single Table Design</a>), along with a <a target="_blank" href="https://kit.svelte.dev/">Sveltekit</a> powered front-end.  Working in Sveltekit has been <em>lovely</em>, with one slight caveat.  </p>
<blockquote>
<p>Session Management in Sveltekit has been the very dickens.</p>
</blockquote>
<p>Because it's relatively new, Sveltekit is undergoing rapid changes.  I had just got a naïve implementation of session management working when <a target="_blank" href="https://github.com/sveltejs/kit/discussions/5883">this update</a> was released, necessitating major changes throughout the app, including session management.  This, coupled with the paucity of documentation (owing to the new changes) has meant that my experience of Session Management in Sveltekit has been the very dickens.</p>
<p>Still, I have the basic "<em>Send Login Get Cookie Nom Nom Nom</em>" flow working, and now need to actually authenticate users &amp; construct unique sessions. </p>
<h1 id="heading-the-problem-were-solving">The problem we're solving</h1>
<p> I need to support both API and website access, so I might as well have a unified mechanism.  I want it to be serverless, scalable, and as hands-off as possible.  </p>
<p>For now, users will have a username and password, but in the future I'd like to allow alternative access methods.  Users are already identified in my system with a unique ID that's separate from their email and username.  I'd also like to have a non-password authentication system for my API.</p>
<p>Sessions should expire at some point, and I'd like to be able to force-expire them myself.</p>
<p>I'd also like to have things be well tested and monitored, because Knowing is Half the Battle, or so I hear.</p>
<h1 id="heading-how-were-solving-it">How we're solving it</h1>
<h2 id="heading-design">Design</h2>
<p>Let's implement traditional, server-side sessions, using API Gateway Lambda Authorizers to enforce access requirements and DynamoDB for session storage.</p>
<p>Website users will log in with their username and password, and receive an associated cookie for access.  Sessions will expire every week or so.  Reasons for the "Or So" are detailed below.</p>
<p>API users will generate a request token using their API Key.  That token will expire every hour (or so).  This helps prevent replay attacks, and also saves us a bit of compute (by virtue of only running our access-key computation once per generation).</p>
<h3 id="heading-components">Components</h3>
<h4 id="heading-api-gateway">API Gateway</h4>
<p>API Gateway is Amazon's solutions for scalable, controllable API proxying.  We're making use of it's ability to dispatch HTTP requests to an underlying Lambda function.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663560111907/r4MEXU_PF.webp" alt="czNmcy1wcml2YXRlL3Jhd3BpeGVsX2ltYWdlcy93ZWJzaXRlX2NvbnRlbnQvbHIvcGQ0My00OS1jaGltLmpwZw.webp" /></p>
<h5 id="heading-jeff-bezos-and-investors-tour-api-gateway"><em>Jeff Bezos and Investors tour API Gateway</em></h5>
<h4 id="heading-api-gateway-lambda-authorizers">API Gateway Lambda Authorizers</h4>
<p>Built into API Gateway is the ability to add a Lambda function as an <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html">Authorizer</a> to any route.  This function receives either a bearer token <em>or</em> some configurable subset of the request being made, and needs to return an IAM Policy in return.</p>
<p>Using an authorizer function gives me a few things.  By denying access before even hitting my functions, it provides me with a level of abuse protection.  I can implement complicated access patterns because I'm returning an IAM Policy, and have the flexibility to use whatever authentication mechanism I want.  Plus, the result is automatically cached.</p>
<h4 id="heading-lambda">Lambda</h4>
<p>AWS Lambda is "<em>a compute service that lets you run code without provisioning or managing servers</em>".  Basically, we upload stand-alone pieces of code which, when needed, are executed in a controlled environment, then terminated.  There is no additional management required.  It's <em>wonderful</em>.</p>
<h4 id="heading-dynamodb">DynamoDB</h4>
<p>Yes, I could use Redis as an in-memory Session Store, but the pricing doesn't make sense; at the lowest cost I'd be paying nearly 40c/hr to keep Amazon MemoryDB online, which is enough to pay for 1.5 million DynamoDB reads or a Gb/Month of storage.  Even using Elasticache would cost us 1.6¢/hr.  It's not a fair comparison because you have to consider all usage costs.... But even a back of envelope calculation puts DynamoDB far ahead of the alternatives.</p>
<p><em>But what about the speed</em> I hear you ask!  In practical terms, I've observed DynamoDB response times for my authentication logic are consistently below 20ms.  Additionally, I feel that tiny delays in areas like security and "calculation" activities make users feel more reassured.</p>
<h4 id="heading-typescript">Typescript</h4>
<p>I profess no special TypeScript knowledge, nor do I claim to be demonstrating the "right" way to achieve any particular goal.  I am merely a dilettante!  However, I do find type checking helps keep some of Javascript's more egregious peccadillos in check.</p>
<h1 id="heading-next-up">Next up</h1>
<p>I think our plan is sufficient reading for now!  Should you wish to follow the rest of this series, please be so kind as to Follow me here, or over on <a target="_blank" href="https://twitter.com/dylanlacey">Twitter</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The Week That Was]]></title><description><![CDATA[Dear Reader; I am panicking
Perhaps that is hyperbolic, but I am a wee bit stressed.  See, I have forgotten one of the core truths: writing content takes an eternity.
For a passion project, I'm creating a developer activity log; A kind of free-form s...]]></description><link>https://blog.gentlehacker.io/the-week-that-was-2022-08-29</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-week-that-was-2022-08-29</guid><category><![CDATA[Developer]]></category><category><![CDATA[links]]></category><category><![CDATA[Microfrontend]]></category><category><![CDATA[performance]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 29 Aug 2022 18:30:01 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-dear-reader-i-am-panicking">Dear Reader; I am panicking</h1>
<p>Perhaps that is hyperbolic, but I am a wee bit stressed.  See, I have forgotten one of the core truths: <em>writing content takes an eternity</em>.</p>
<p>For a passion project, I'm creating a developer activity log; A kind of free-form standup tool for making notes to oneself and one's team.  As part of that I'm building a session management system using DynamoDB, API Gateway Authorizers and some Svelte-y bits.  I decided to blog about it and it's turned into... A bit of a monster.  I'm yet to even complete the first draft.</p>
<p>(Should you wish to read it once complete, <em>Subscribe</em> to my content by email or follow me here, on Dev.to.)</p>
<p>Part of my vexation arises from having problems deciding what to do with the length; Should I spilt it into multiple posts, <em>or</em> publish one, long, mega-tutorial?  Which leads me to a new idea; A Question of the Week.  You are cordially invited to respond below in the comments!</p>
<blockquote>
<p>Should longer how-to guides be published as one large chunk, or a series of smaller, inter-related pieces?</p>
</blockquote>
<h1 id="heading-developer-stuff">Developer Stuff</h1>
<h2 id="heading-new-heroicons">New Heroicons</h2>
<p>The developers of TailwindUI have just released <a target="_blank" href="https://heroicons.com/">Heroicons 2.0</a> for your delightful SVG icon pleasure.  Nicely done and only lightly styled, they'll prove useful for all kinds of project.</p>
<h2 id="heading-microfrontends">Microfrontends</h2>
<p>Sahas Purkuti has an interesting comparison of the <a target="_blank" href="https://scanskill.com/frontend/micro-frontends/">pros and cons of micro-frontends</a>; That is, the idea that a team should revolve around a specific, small feature of an app.</p>
<p>The idea is an interesting one... But one I fear may introduce more opportunity for peril then it removes.</p>
<h2 id="heading-i-implore-you">I implore you</h2>
<p>Activate MFA on your AWS Account, lest you incur a <a target="_blank" href="https://www.reddit.com/r/aws/comments/x03vay/hacked_aws_account_is_facing_200000_in_charges/">$200'000 bill</a> due to abuse.</p>
<h2 id="heading-meaningful-statistics">Meaningful Statistics</h2>
<p>SWEOR have an <a target="_blank" href="https://www.sweor.com/firstimpressions">interesting collection of stats</a> about how Website performance impacts a customer's impression of your site.  Worth keeping in mind.  I particularly liked the advice to use an F-shaped pattern to advantage the fact that it takes 2.6 seconds for a user’s eyes to land on the area of a website that most influences their first impression.</p>
<h1 id="heading-developer-experience">Developer Experience</h1>
<h2 id="heading-dxi">DXI</h2>
<p>An excellent read from Kenneth.io </p>
<h1 id="heading-task-manager-of-the-week">Task Manager of the Week</h1>
<p>Everyone is going to write a task manager at some point.  It is an inevitability, like death, or a certain Billionaire saying something self-involved and stupid. </p>
<p>(This could be several people and I shaln't clarify which.)</p>
<p>This week's featured task manager is <a target="_blank" href="https://github.com/kakengloh/tsk">tsk</a>, a Go-powered, terminal based manager with a reasonably CLI and filtering/tagging.  I imagine it's quite reasonable!</p>
<h1 id="heading-for-joy">For Joy</h1>
<p>I have never seen a better reason to have robust education for your products.  Just <a target="_blank" href="https://twitter.com/dutraweather/status/1555154185938763776?s=21&amp;t=RUISgfx6BIvDNPsydiC7fQ">enjoy</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The Week That Was]]></title><description><![CDATA[I had an excellent week
It's been an exciting week at work!  First and foremost, Jason Baum joined us as the Director of Community at Saucelabs (Hi, Boss!).
Secondly, my friend Kunal joined up with Jecelyn Yeen from the Chrome DevTools team to show o...]]></description><link>https://blog.gentlehacker.io/the-week-that-was-22-08-22</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-week-that-was-22-08-22</guid><category><![CDATA[links]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Developer]]></category><category><![CDATA[CSS]]></category><category><![CDATA[weekly review]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Tue, 23 Aug 2022 00:56:12 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-i-had-an-excellent-week">I had an excellent week</h1>
<p>It's been an exciting week at work!  First and foremost, <a target="_blank" href="https://www.linkedin.com/in/jasonebaum/">Jason Baum</a> joined us as the Director of Community at Saucelabs (Hi, Boss!).</p>
<p>Secondly, my friend Kunal joined up with Jecelyn Yeen from the Chrome DevTools team to show off how you can export user flows from Chrome's DevTools and play them back with Sauce.  Check it out <a target="_blank" href="https://twitter.com/ChromeDevTools/status/1558888443563982850?s=20&amp;t=xyFk9tGhchJUrPpj-a0RjQ">here</a>!  Nicely done Kunal!</p>
<h1 id="heading-developer-stuff">Developer Stuff</h1>
<h2 id="heading-check-your-flaky-jest-tests-are-better-now">Check your flaky Jest tests are better now</h2>
<p>I take a fairly strong opinion towards flaky tests: <strong>I think they're worthless</strong>.  Unless you're testing something inherently indeterministic, your tests should always be <em>more</em> reliable than your code.  If they're not, you should delete 'em; They're not providing you any value.</p>
<p>So, I was a bit hesitant to read <a target="_blank" href="https://blog.olek.it/how-to-run-a-flaky-test-multiple-times-using-jest">Jakub Olek's article</a> about running a flaky test multiple times in Jest... Until I did so, and found it's a neat trick for re-running no-longer-flaky tests enough times to ensure you've fixed the flakiness.  It's good stuff!</p>
<h2 id="heading-serverless-storefronts-with-shopify">Serverless Storefronts with Shopify</h2>
<p>Did you know Shopify has a global hosting solution?  It's called Oxygen and, from what I can tell, it exists to enable their new "Headless Commerce" solution called <a target="_blank" href="https://hydrogen.shopify.dev/">Hydrogen</a>. Hydrogen is a React-based storefront you can deploy to Oxygen for free, letting you build custom stores with Shopify-backed processing.</p>
<p>I suspect part of this is a retention play by Shopify to stop people fleeing for full-custom Serverless solutions, but it's neat nonetheless!</p>
<h1 id="heading-unix">Unix</h1>
<h2 id="heading-plan-files">.Plan files</h2>
<p>Did you know that Unix has a <code>.plan</code> file in your home directory, intended to let other users know what you're doing when they use the <code>finger</code> command?  Neither did I.</p>
<p><a target="_blank" href="https://www.oreilly.com/library/view/wyntk-unix-system/1565921046/ch07s11.html">O'reilly does, though</a>.</p>
<h1 id="heading-design">Design</h1>
<h2 id="heading-the-603010-rule">The 60/30/10 Rule</h2>
<p>I am awful at design, which is why Yuri's article on the <a target="_blank" href="https://yuricodesbot.hashnode.dev/the-603010-color-rule-for-web-design">60/30/10 Rule for Beginners</a> appeals to me.  Yuri breaks down how to select colours, the proportions they should be used in, and gives you alternatives for those patterns.</p>
<p>It also linked me to the neat tool <a target="_blank" href="https://colorhub.vercel.app/">Colorhub</a>!</p>
<h1 id="heading-reading">Reading</h1>
<h2 id="heading-kanji-and-why-its-so-hard-to-read">Kanji and why it's so hard to read.</h2>
<p>Did you know I'm doing a Linguistics degree?  I am!  Because I wasn't already busy enough, apparently.  Anyhoo, majoring in Japanese led me to both an understanding of why the Japanese writing system is so difficult <em>and</em> a core CSS feature designed specifically to help with it?  Weird.  <a target="_blank" href="https://blog.gentlehacker.io/little-known-css-features-ruby-layout-part-1">You can read part one of my article about it here</a>.</p>
<h2 id="heading-the-genesis-of-devops-and-why-it-matters-for-writing-terraform">The Genesis of DevOps and why it matters for writing Terraform</h2>
<p>This article by <a target="_blank" href="https://zwischenzugs.com/2022/08/08/who-should-write-the-terraform/">Ian Miell</a> is great. He goes through the evolution of DevOps, the (<em>questionable</em>) motivations behind organizational decisions and why it often, DevOps initiatives failed to deliver their desired outcomes.</p>
<p>But, this is all just context to answer the question; <em>Who should write your Terraform documents?</em></p>
<h2 id="heading-janet-jackson-considered-harmful">Janet Jackson considered harmful</h2>
<p>I'm going to drop this one in with no context.  Just <a target="_blank" href="https://devblogs.microsoft.com/oldnewthing/20220816-00/?p=106994">enjoy</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Little known CSS Features: Ruby Layout (Part 1)]]></title><description><![CDATA[What is it

 (Ruby Layout) provides the rendering model and formatting controls related to the display of ruby annotation. Ruby annotation is a form of interlinear annotation, consisting of short runs of text alongside the base text

That clarified n...]]></description><link>https://blog.gentlehacker.io/little-known-css-features-ruby-layout-part-1</link><guid isPermaLink="true">https://blog.gentlehacker.io/little-known-css-features-ruby-layout-part-1</guid><category><![CDATA[Japanese,]]></category><category><![CDATA[CSS]]></category><category><![CDATA[linguistics]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Kanji]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 22 Aug 2022 04:16:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/16BsoIasXH0/upload/v1661141614599/FvNkEtGxg.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-what-is-it">What is it</h1>
<blockquote>
<p> (<em>Ruby Layout</em>) provides the rendering model and formatting controls related to the display of ruby annotation. Ruby annotation is a form of interlinear annotation, consisting of short runs of text alongside the base text</p>
</blockquote>
<h1 id="heading-that-clarified-nothing">That clarified nothing</h1>
<p>I know; This one requires a bit of background.  I promise you'll at least learn something, and you may find a new technique for enhancing your CSS Designs with:</p>
<ul>
<li>Pronunciation guides</li>
<li>Short, snarky comments</li>
<li>Translations</li>
<li>Synonyms</li>
</ul>
<h1 id="heading-so-im-a-fool-pandemic-hobbies-edition">So I'm a fool -- Pandemic Hobbies edition</h1>
<p>When the Pandy got its hooks in, many people took up a new hobby, mostly baking Sourdough.  I already baked Sourdough because I'm insufferable, so I went to the next logical step: acquiring a Linguistics degree.</p>
<p>Well, I must clarify; acquiring a Diploma of Japanese I subsequently decided to expand into a full three-year Linguistics degree majoring in Japanese, while still working full time.</p>
<h2 id="heading-japanese-is-hard">Japanese is hard</h2>
<p>This has been ... stressful. Japanese is considered one of the <a target="_blank" href="https://langfocus.com/language-features/japanese-one-of-the-hardest-languages-in-the-world/">most difficult languages</a> for English speakers to learn, primarily because of their writing system.</p>
<h2 id="heading-japanese-writing">Japanese writing</h2>
<p>Japanese has an alphabet called <strong>Hiragana</strong>, which is a <em>syllabary</em>, that is, a set of characters tell you how to pronounce a word.  Once you can read Hiragana, you can pronounce any word in the Japanese language.  Compare this to English, where the pronunciation of "<em>live</em>" depends on whether you mean "not pre-recorded" or "not die".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661141486778/Ej1nYGvJY.png" alt="1536px-Table_hiragana.svg.png" /> (Thank you Wikipedia)</p>
<p>Japanese also has <strong>Katakana</strong>, which is basically a 1:1 equivalent for <strong>Hiragana</strong>, used for writing foreign loan words (amongst other things).</p>
<p>(Despite being called <em>syllabaries</em> these two scripts technically don't express syllables but instead <em>mora</em>, which is a timing-based unit that isn't germane to this discussion.  If you want to learn more, leave a comment and I'll write another article!) </p>
<p>Japanese also has a <em>logographic</em> (picture based) writing system called <strong>Kanji</strong> which they kind-of borrowed wholesale from China. Actually, they're complicated enough to deserve their own H1.</p>
<h1 id="heading-why-are-kanji-so-hard">Why are Kanji so hard?</h1>
<h2 id="heading-1-because-theres-so-many">1. Because there's <em>so many</em></h2>
<h3 id="heading-seriously-tens-of-thousands">Seriously, tens of thousands</h3>
<p>One of the definitive dictionaries, the Dai Kan-wa Jiten, contains over 50'000 characters.</p>
<h3 id="heading-lies-no-one-could-learn-that-many">Lies.  No-one could learn that many.</h3>
<p>You're right.  Japanese students learn Kanji all the way through graduating high-school, gradually getting through the 2136 <em>jōyō</em> kanji, the official Government list of characters required for functional literacy in Japanese.</p>
<h3 id="heading-but-wait-theres-more">But wait, there's more!</h3>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Kanji#Total_number_of_kanji">Wikipedia</a> points out that there are roughly 1000 additional, commonly used characters (including those for names) as well as some specialised industry characters.</p>
<h2 id="heading-2-because-theyre-not-always-read-the-same-way">2. Because they're not always read the same way</h2>
<h3 id="heading-sound-readings-andamp-meaning-readings">Sound Readings &amp; Meaning Readings</h3>
<p>Because Kanji were borrowed from China, they already had a meaning <em>and</em> a pronunciation in Chinese.  The Japanese had their own words for most of the meanings, so the characters could be read with the Japanese word for the Chinese reading.  However, the Japanese <em>also</em> decided to import the pronunciations, adjusted for their syllable structure.</p>
<p>Just to spice things up further, the Japanese also didn't import meanings wholesale; Some characters had their meanings adjusted.</p>
<p>For instance, the character 田 is Middle Chinese for Field, pronounced <em>/den/</em>.  In Japanese, it specifically means <em>Rice Paddy</em>, not field, and so not only can it be pronounced <em>den</em>, it can also be pronounced <em>ta</em>.</p>
<h3 id="heading-but-wait-theres-still-more">But wait, there's still more!</h3>
<p>More readings, that is.  See, languages aren't static things; they change.  As China's pronunciation of their characters changed, Japan imported the new pronunciations... <em>and kept some of the old ones!</em></p>
<p>Not only that, but the <em>meanings</em> of characters differed as well, depending on which region of Japan you're in.  A character might mean "Shovel" in one region, and "Rake" in another, and over time, both meanings spread nationwide... Which led to Japanese having multiple sound readings <em>and</em> multiple meaning readings for each character.</p>
<h2 id="heading-so-how-do-you-tell-readings-apart">So how do you tell readings apart?</h2>
<p>Context, mainly.  Let's look at an example!  This is the Kanji for Electrical Appliances:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.io/view/139a76b1-96d9-4d37-aa7e-01f10c2fbe6f">https://snappify.io/view/139a76b1-96d9-4d37-aa7e-01f10c2fbe6f</a></div>
<p>Those are the kanji for <em>Electricity</em>, <em>Child</em>, <em>Manufacture</em> and <em>Goods</em>.</p>
<p>The kanji for <em>Child</em> shows up in lots of words, pronounced in several ways:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.io/view/f6e86424-da6e-4bb3-ab85-a7ffc6cdf3b2">https://snappify.io/view/f6e86424-da6e-4bb3-ab85-a7ffc6cdf3b2</a></div>
<p>As does the Kanji for <em>Goods</em>:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.io/view/e3a7806f-a329-434a-9eb1-322808bc920a">https://snappify.io/view/e3a7806f-a329-434a-9eb1-322808bc920a</a></div>
<h2 id="heading-that-is-stunningly-unhelpful">That is stunningly unhelpful</h2>
<p>Yes!</p>
<p>There are some general rules that can help, which I'm not going to cover here.  Roughly speaking, there's one <em>primary</em> reading of each type which is commonly used.  The structure of the word (Kanji alone, Multiple Kanji with no Kana, etc) can suggest which readings to use.  And then it comes down to knowing the word and recognising it in context (Just like you do for English!)</p>
<h1 id="heading-so-what-does-this-have-to-do-with-css">So what does this have to do with CSS?</h1>
<h2 id="heading-japanese-children-arent-fully-literate-until-they-graduate-highschool">Japanese Children aren't fully literate until they graduate Highschool</h2>
<p>Remember when I said that Japanese study Kanji right up until graduation?  That means that, functionally speaking, for the first 18 years of their life the Japanese can't read all of the characters they're likely to encounter.</p>
<p>In fact, given the large number of Kanji, the existence of technical vocabulary and rare characters and such, it's unlikely that most adults in Japan know every Kanji, even if we restrict ourselves to just those used in the last 100 years.</p>
<h2 id="heading-why-this-isnt-a-problem">Why this isn't a problem</h2>
<h3 id="heading-the-japanese-speak-japanese">The Japanese speak... Japanese</h3>
<p>This gives them a <em>huge</em> advantage over a foreign leaner.  Even if a Japanese child doesn't know how a Kanji is read, they can use their language knowledge and the other Kanji in a sentence to guess.</p>
<p>Take <em>Denshiseihin</em>, the "Electrical Goods" I referenced before.  If a Japanese child knows the kanji for <em>Electricity</em> (Which is also used in Phone and Light), the Kanji for <em>Child</em> can be pronounced as "shi<em>" in "bou</em>shi<em>" (Meaning Hat) and </em>Goods<em> can be pronounced "</em>hin<em>" in "shoku</em>hin<em>" (Foodstuffs), and has heard the word "</em>Denshiseihin<em>" before, they can make an educated guess that the unknown character is "</em>sei*".</p>
<h3 id="heading-they-cheat-with-furigana">They Cheat with Furigana</h3>
<p>If you've read any Japanese, you might have noticed that some Kanji words are printed with Hiragana above them.  This is called <em>Furigana</em> and indicates to the reader how a word is pronounced:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661140487147/hAO1xB_Ep.png" alt="Screen Shot 2022-08-22 at 1.54.43 pm.png" /></p>
<p>These are common in children's texts, and also appear in Newspapers and print where an average adult reader might not be familiar with a character.</p>
<h1 id="heading-this-is-a-lot">This is a lot</h1>
<p>You're right, it is a lot, and I've still not come to the point.  Yikes.</p>
<p>Tell you what; Subscribe to me here on Hashnode, and you'll get notified when Part 2 comes out, and I finally explain what all of this has to do with CSS.  It'll be good, I promise.</p>
]]></content:encoded></item><item><title><![CDATA[The Week/Fortnight That Was]]></title><description><![CDATA[I missed a week
Probably not an auspicious start.  Still, an interesting melange of links to share this week.
Speaking of melanges, dear reader, have any of you given a tech talk in a language you're not currently proficient in?  My Japanese is N4 le...]]></description><link>https://blog.gentlehacker.io/the-weekfortnight-that-was-2022-08-01</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-weekfortnight-that-was-2022-08-01</guid><category><![CDATA[streaming]]></category><category><![CDATA[cli]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[Unison]]></category><category><![CDATA[lego]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 15 Aug 2022 18:02:01 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-i-missed-a-week">I missed a week</h1>
<p>Probably not an auspicious start.  Still, an interesting melange of links to share this week.</p>
<p>Speaking of melanges, dear reader, have any of you given a tech talk in a language you're not currently proficient in?  My Japanese is N4 level <em>at best</em>, but I'd quite like to submit a talk for <a target="_blank" href="https://jsconf.jp/2022/">JSConfJP</a> (For which submissions are currently open).  Perhaps a worthy goal to work towards; Perhaps a way to look foolish.</p>
<p>A few links this week pertain to a re-vivification of my personal stream over on Twitch, as well as Sauce Labs' professional stream, still in the making.  I hope you enjoy.</p>
<h1 id="heading-caveats">Caveats</h1>
<p>All opinions are my own; I don't guarantee any of this to be breaking news; Pineapple absolutely belongs on pizza as long as it's not too wet; No-one is paying me to talk about their stuff; I don't accept liability etc; My cat is adorable and I won't be taking questions at this time.</p>
<h1 id="heading-cool-stuff">Cool Stuff</h1>
<h2 id="heading-for-development">For Development</h2>
<h3 id="heading-lovely-command-line-scripting-with-gum">Lovely command line scripting with Gum</h3>
<p>It has been quite some time since I last wrote a command-line script, but it has been an occurrence that has... occurred.  Next time it... occurs... (oh dear) I'll be implementing it with the assistance of the lovely <a target="_blank" href="https://github.com/charmbracelet/gum">Gum</a> library.</p>
<p>Gum makes it easy to create well formatted, easy-to-use, attractive CLI utilities with animation and colour.  Observe:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660528114374/VQiAKzyNT.gif" alt="68747470733a2f2f73747566662e636861726d2e73682f67756d2f64656d6f2e676966.gif" /></p>
<p>Gum's developers, <a target="_blank" href="https://charm.sh/">Charm</a>, are awash with their namesake; Lovely tools with Aesthetic.  Delightful.</p>
<h3 id="heading-unison-the-ironically-named-distributed-programming-language">Unison, the ironically named distributed programming language</h3>
<p>Unison is... quite an adventure.  Every Unison function has a unique, deterministic address that references it's implementation, arguments, and dependent functions.</p>
<p><a target="_blank" href="https://www.unison-lang.org/learn/the-big-idea/">The Unison team claims</a> this allows for more flexible distributed computing, where chunks of code can be shipped around on the fly to different compute nodes.  Content addressing also gives them nice code management tools, 0-time builds and other neat things.</p>
<p>Go read that link; It's a quite interesting idea.</p>
<h3 id="heading-wysiwyg-markdown-editing-with-milkdown">WYSIWYG Markdown editing with Milkdown</h3>
<p><a target="_blank" href="https://twitter.com/saulmirone">Saul Mirone</a> brings us <a target="_blank" href="https://milkdown.dev/">Milkdown</a>, an editor framework for Markdown.  A plugin based architecture allows users to build the exact editor they want.</p>
<h3 id="heading-system-6-style-css-with-systemcss">System 6-style CSS with system.css</h3>
<p>Well.  Sakofchit has really outdone themselves creating <a target="_blank" href="https://github.com/sakofchit/system.css">system.css</a>.  This design system enables you to bring that retro, MacOS System6 feel to your webpages.  Just look at it!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660527352277/fAU3E_jkE.png" alt="image.png" /></p>
<h2 id="heading-for-video-production">For Video Production</h2>
<h3 id="heading-nodejs-powered-visual-effects-with-nodecg">NodeJS powered visual effects with NodeCG</h3>
<p><a target="_blank" href="https://www.nodecg.dev/">NodeCG</a> is a visual effects tool with an extremely simple concept: Render a webpage.</p>
<p>NodeCG has no graphics or drawing primitives and instead provides a structure for code and an API to facilitate moving data between your dashboard, server, and graphics libraries.  The result is imported into any live production app which can render HTML, such as OBS Studio.</p>
<h3 id="heading-h2r-graphics-for-chroma-keyd-visual-effects">H2r Graphics, for Chroma-Key'd visual effects</h3>
<p><a target="_blank" href="https://h2r.graphics/">H2r Graphics</a> is from Here to Record; a content team focused on live video production.  This simple solution offers dynamic data, lower thirds, messages and looks robust and straightforward.</p>
<h3 id="heading-css-based-effects-themeing-with-holographics">CSS-based effects themeing with Holographics</h3>
<p>Working on Windows and Mac, <a target="_blank" href="https://hologfx.io/">Holographics</a> offers simple widget based composition, with an additional open source NodeJS SDK for dynamic updates.</p>
<p>The team claims it's designed to do "Basic graphics really well", and that other applications are better suited to uses like sports broadcasts.</p>
<p>It's still in it's infancy, but there are quite a few additions planned.</p>
<h3 id="heading-casparcg-for-open-source-broadcast-television">CasparCG for open source broadcast television</h3>
<p>CasparCG is heavyweight, layer-based, config-forward composition.  It's been used since 2006 by most European broadcasters, but remains free and open source.</p>
<p>I imagine with some heavy lifting in code, you can do almost anything with it!  Find it <a target="_blank" href="https://github.com/CasparCG/">here</a>.</p>
<h2 id="heading-for-joy">For Joy</h2>
<h3 id="heading-this-is-fine-build-your-denial">This is Fine: Build your Denial</h3>
<p><strong>Legotruman</strong> needs you to vote for his Lego design.  The reasons I support this should be obvious:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660527064978/5K0Gk9DRq.png" alt="image.png" /></p>
<p>Make everything fine <a target="_blank" href="https://ideas.lego.com/projects/a6a24359-d8d3-4276-97a0-3c998deb1206">here</a>.</p>
<h1 id="heading-edifying-reading">Edifying Reading</h1>
<h2 id="heading-development">Development</h2>
<h3 id="heading-microsoft-devs-on-why-cmd-will-never-change">Microsoft Devs, on why <code>CMD</code> will never change</h3>
<p>Like them or not, Microsoft is labouring under a great weight of commercial customer expectations, as shown by this Microsoft dev, explaining why <a target="_blank" href="https://twitter.com/theshalvah/status/1532843981528350733?s=20&amp;t=J1lpH0ijMP68NKJ6Lq-r7A">it's never a case of "just" making a change</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The Week That Was]]></title><description><![CDATA[The Week & How it Found Me
I gave in.  I decided it was time to start a public log/dashboard, despite the slightly obnoxious feeling that comes with publishing what's caught your eye.
Why?
Mainly ADHD.  No really!  I have about ∞ tabs open at any one...]]></description><link>https://blog.gentlehacker.io/the-week-that-was-22-07-25</link><guid isPermaLink="true">https://blog.gentlehacker.io/the-week-that-was-22-07-25</guid><category><![CDATA[Diary]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[DevRel]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[linguistics]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 01 Aug 2022 02:58:11 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-the-week-andamp-how-it-found-me">The Week &amp; How it Found Me</h1>
<p>I gave in.  I decided it was time to start a public log/dashboard, despite the slightly obnoxious feeling that comes with publishing what's caught your eye.</p>
<h2 id="heading-why">Why?</h2>
<p>Mainly ADHD.  No really!  I have about ∞ tabs open at any one time, full of things I either want to investigate or am half way through investigating.  I get distracted easily. 
 I get excited about upcoming portions of projects and research them <em>well</em> before I need too.  Deleting things without doing anything with them triggers the "<em>oh god what am I doing with my life</em>" response, as well as a hefty bout of FOMO.</p>
<p>I also tend to feel crappy about what I have achieved, whether or not that's legitimate, and working as the only developer in my timezone, I don't get to share some of the cool stuff I find. So I figure, why not let The Internet know what that stuff is!</p>
<h2 id="heading-wait-who-even-are-you">Wait who even are you?</h2>
<p>Oh!  I'm Dylan, the Manager of Developer Relations at Sauce Labs.  I live in beautiful Brisbane, Australia with my husband and a cat called Galena.  I prefer doing Ruby but mostly spend my time in JS/TS land.  You might have seen me speak recently at Eurostar 2022.</p>
<p>When I'm not doing tech, I'm gaming, baking, or studying a Linguistics degree at uni, majoring in Japanese and <em>hopefully</em> minoring in Auslan, the predominant sign language in Australia.</p>
<h1 id="heading-caveats">Caveats</h1>
<p>All opinions are my own; I don't guarantee any of this to be breaking news; Janeway is the superior Starfleet Captain; No-one is paying me to talk about their stuff; I don't accept liability etc; My cat is adorable and I won't be taking questions at this time; </p>
<h1 id="heading-cool-stuff">Cool Stuff</h1>
<h2 id="heading-developer-andamp-devrel-code-assistance">Developer &amp; DevRel Code Assistance</h2>
<h3 id="heading-sourcegraph">Sourcegraph</h3>
<p><a target="_blank" href="https://about.sourcegraph.com/">Sourcegraph</a> is a cool-looking tool that lets you search across all your code.  Like, all of it; Github, folders, Bitbucket, Perforce, the works.</p>
<p>It also offers nice looking code notebooks for samples and snippets, as well as bulk manipulation tools.  I'm planning to investigate it for keeping docs and samples up to date, as well as helping our implementation team find stuff.  (We have integrated Sauce Labs with a <em>lot</em> of DevOps tool chains.</p>
<h3 id="heading-rich-codex">Rich Codex</h3>
<p>Neat little tool that runs commands or places code snippets in terminal windows, then generates screenshots from the inputs and outputs.  <a target="_blank" href="https://github.com/ewels/rich-codex">Find it here</a>.</p>
<h3 id="heading-scribe">Scribe</h3>
<p>What if you could create step-by-step process guides by recording your screen, having your interactions highlighted, and easily editing the output?  That's the promise of <a target="_blank" href="https://scribehow.com/">Scribe</a>.</p>
<p>I'm looking forward to demoing this one, even while I'm a <em>little</em> leary.</p>
<h2 id="heading-developer-tools">Developer Tools</h2>
<h3 id="heading-brev">Brev</h3>
<p>Docker is great for easy environment setup... Once Docker itself is setup.  <a target="_blank" href="https://www.brev.dev/">Brev</a> decided that a great experience is one that lives, hidden in the background.</p>
<p>Once it's set up, you use all the same tools you already do, with Brev chugging away in the background, making things work seamlessly.  It looks pretty neat, costing 10c USD an hour for the personal plan.</p>
<h3 id="heading-cerbos">Cerbos</h3>
<p><a target="_blank" href="https://cerbos.dev/">Cerbos</a> is a lovely-looking, open source Access Control solution, designed as a configuration-over-code tool for authentication and permissions.  I'm going to give it a spin in my latest sample app, so expect more Opinions™️ soon.</p>
<h2 id="heading-assets">Assets</h2>
<h3 id="heading-programmingfonts">ProgrammingFonts</h3>
<p>I assume <a target="_blank" href="https://www.programmingfonts.org/">Programming Fonts</a> is old news, but it's a lovely way to find the next BodyMod for your code.</p>
<p>(<em>FWIW I'm partial to <a target="_blank" href="https://www.programmingfonts.org/#gintronic">Gintronic</a></em>)</p>
<h3 id="heading-glassmorphism-ui">Glassmorphism-UI</h3>
<p>I am <em>awful</em> at graphic design, so I rely on assets to get things completed in a manner which doesn't sear the eyeballs.  For example, the lovely, modern elements of S M Rony's <a target="_blank" href="https://lottiefiles.com/marketplace/glassmorphism-ui">Glassmorphism UI</a>.</p>
<h1 id="heading-we-call-books-content-now-or-what-ive-been-reading">We Call Books Content Now (Or, what I've been reading)</h1>
<h2 id="heading-the-shallowness-of-google-translate">The Shallowness of Google Translate</h2>
<p>The Atlantic published an <a target="_blank" href="https://www.theatlantic.com/technology/archive/2018/01/the-shallowness-of-google-translate/551570/">article</a> by no less then Hofstadter himself, discussing how well a newer implementation of Google Translate encompasses the art of translation itself.</p>
<p>Extremely prescient timing; Not only do I make a lot of use of Google Translate to check my working when studying Japanese, I'm also taking a unit about translation this semester.</p>
<p>Hofstadter is not well pleased at the Zaibatsu's work.  He dissects the difference between decoding and understanding, gives several examples, and makes this interesting point:</p>
<blockquote>
<p>It’s almost irresistible for people to presume that a piece of software that deals so fluently with words must surely know what they mean.</p>
</blockquote>
<p>Well worth a read, for linguists and developers alike.</p>
<p>(The article dates from 2018, and the technology has no doubt improved since then, however, my own experience shows that Translate is often lacking... Especially when tenses vary or words are written in Hiragana, in lieu of Kanji.</p>
<p>If you don't understand that last sentence and would like to know more, leave a comment and I may do an article on it.)</p>
<h2 id="heading-the-end-of-localhost">The End of Localhost</h2>
<p>A neat little discussion about whether <a target="_blank" href="https://dx.tips/the-end-of-localhost">development should be done on Localhost</a> by <a target="_blank" href="https://hashnode.com/@swyx">swyx</a>.  Far more nuanced then most articles of the kind, deep and interesting.</p>
<p>Fun quote:</p>
<blockquote>
<p>Literally all of Hacker News hates it (and Reddit too but edgily)</p>
</blockquote>
<h1 id="heading-and-thats-it">And that's it!</h1>
<p>Should you have found this little diary interesting, please give me a follow and/or give this article a react.  Thanks folks!</p>
]]></content:encoded></item><item><title><![CDATA[A Bug in Typo Clothing]]></title><description><![CDATA[I am extremely fortunate.  My job as Manager of Developer Relations affords me the opportunity to play with a bunch of new frameworks.  At the moment, I'm exploring Svelte and SST, building out data-driven user behaviour test demo.
(Oh, BTW, SST is a...]]></description><link>https://blog.gentlehacker.io/a-bug-in-typo-clothing</link><guid isPermaLink="true">https://blog.gentlehacker.io/a-bug-in-typo-clothing</guid><category><![CDATA[CORS]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[Bugs and Errors]]></category><category><![CDATA[sst]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Wed, 27 Jul 2022 04:06:25 GMT</pubDate><content:encoded><![CDATA[<p>I am extremely fortunate.  My job as Manager of Developer Relations affords me the opportunity to play with a <em>bunch</em> of new frameworks.  At the moment, I'm exploring Svelte and SST, building out data-driven user behaviour test demo.</p>
<p>(Oh, BTW, SST is a really neat tool for making Serverless development faster.  <a target="_blank" href="https://sst.dev/">Check it out here</a>.)</p>
<p>A lot of this is new to me, but one thing wasn't; an error.  Specifically, a CORS error, bane of my existence:</p>
<h1 id="heading-i-hate-cors-so-so-much">I Hate CORS so, so much</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890504003/tKSyTLGHU.png" alt="Screen Shot 2022-07-27 at 12.54.51 pm.png" /></p>
<p>The weird part is, the root, er, route, works fine.  Hmm.  Let's take a look at my route definition:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.io/view/b764b6f5-2185-429f-8ea6-201784d0555e">https://snappify.io/view/b764b6f5-2185-429f-8ea6-201784d0555e</a></div>
<p>Well OK.  Maybe there's some sort of SST config that only allows cross origin requests for the top level route?  CORS support is on by default, but maybe if I add it explicitly, as shown in the <a target="_blank" href="https://docs.sst.dev/constructs/Api#cors">SST Docs</a>:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.io/view/15c03bba-b308-4bf6-8d9a-4422d3ab6e0e">https://snappify.io/view/15c03bba-b308-4bf6-8d9a-4422d3ab6e0e</a></div>
<p><strong>Reload</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890504003/tKSyTLGHU.png" alt="Screen Shot 2022-07-27 at 12.54.51 pm.png" /></p>
<p>Nope, still no luck.  Perhaps I need to add headers to individual responses?  That will suck, but at least I'll have something that works!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.io/view/15c03bba-b308-4bf6-8d9a-4422d3ab6e0e">https://snappify.io/view/15c03bba-b308-4bf6-8d9a-4422d3ab6e0e</a></div>
<p><strong>Reload</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658890504003/tKSyTLGHU.png" alt="Screen Shot 2022-07-27 at 12.54.51 pm.png" /></p>
<p>This is becoming irritating.</p>
<h1 id="heading-to-the-logs">To the Logs!</h1>
<p>Weirdly, the Lambda logs don't show anything relevant, and neither do SST's invocation logs.  So <em>maybe</em> it's API gateway being weird?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658891631176/hGEV6HtmV.png" alt="Screen Shot 2022-07-27 at 1.13.36 pm.png" /></p>
<p>Dear reader, it was <em>not</em> API Gateway being weird.  At least, not if the root level CORS settings apply to all routes, and I don't see why they shouldn't.  Shouldn't they?  More log spelunking!  Additional documentation reading!  FIRE UP THE STACKOVERFLOW THRESHER!</p>
<h1 id="heading-desperate-measures">Desperate Measures</h1>
<p>I re-deployed the stack.  Nothing.  I restarted the Dev-side server.  Nothing.  I do <em>both</em> of those things and curse a little.  Nothing.</p>
<p>SST lets me make local changes without re-deploying, which implies there's something clever going on with server proxying.  <em>Maybe</em> there's a bug with local servers not managing CORS headers correctly?  That doesn't sound right, given that API Gateway will over-ride any CORS responses from your backend (should it be configured to handle CORS) but perhaps this is a special case?</p>
<p>I dig into the docs about <a target="_blank" href="https://docs.sst.dev/live-lambda-development">how SST works</a>.  Very neat!<br />In local mode, the lambda invocations are proxied via websocket connection to your local machine.  Sadly, this rules out my final hypothesis; There should still be records of my invocations, even if they had CORS sadness.  I set a breakpoint in VS Code in a desultory fashion; Something SST allows you to do.  (Putting Breakpointing Lambda functions, that is, not being desultory.)  It doesn't work.  I'm out of ideas.</p>
<h1 id="heading-reader-it-was-all-my-fault">Reader, it was All My Fault</h1>
<p>No evidence of missed config.  No function logs.  No evidence that requests are even reaching API Gateway.</p>
<p>Wait.  Ponder that a moment.</p>
<p>|<em>no evidence that requests are even reaching API Gateway</em></p>
<p>Oh.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658894621660/6plEn_aFg.png" alt="Screen Shot 2022-07-27 at 2.03.23 pm.png" /></p>
<p>Oh Dear.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658892802105/KXX-tawSY.png" alt="Screen Shot 2022-07-27 at 1.32.26 pm.png" /></p>
<p>API Gateway was responding to my request for a non-existent route with <code>{"message":"Not Found"}</code>.  This response, apparently, didn't include CORS headers, leading to the console error message.</p>
<h1 id="heading-a-fix">A Fix</h1>
<p>I fixed the error by removing the typo, and by adding a default route to catch all future mistaken requests:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://snappify.io/view/926932d9-a261-4db9-b991-85a9bed816b9">https://snappify.io/view/926932d9-a261-4db9-b991-85a9bed816b9</a></div>
<p>I can ensure that I will at least make the <em>real</em> error message more obvious.</p>
<h1 id="heading-so-what-did-i-learn">So what did I learn?</h1>
<p>This was <em>almost</em> a complete waste of time but if I dwell on that the existential dread will set in SO!  Let's focus on discoveries:</p>
<ol>
<li>I learned SST has some very neat setup to make Serverless development faster, locally</li>
<li>I learned that API Gateway can handle CORS completely, or delegate it to your backend</li>
<li>I learned a bit about SST configuration</li>
</ol>
<p>I was also reminded of my general practise of adding default cases to help with debugging.  Given how much time was spent... <em>learning</em>... I regret not doing so here.</p>
<p>Still.  Onto the next typo!</p>
]]></content:encoded></item><item><title><![CDATA[You're It!]]></title><description><![CDATA[How good is tagging?
Rhetorical question; Tagging is great; my favourite organisation system, in fact.  Test systems with tagging allow you the flexibility to organize your tests according to what code they're testing, while running tests according t...]]></description><link>https://blog.gentlehacker.io/youre-it</link><guid isPermaLink="true">https://blog.gentlehacker.io/youre-it</guid><category><![CDATA[Cypress]]></category><category><![CDATA[tags]]></category><category><![CDATA[Testing]]></category><category><![CDATA[sauce-labs]]></category><dc:creator><![CDATA[Dylan Lacey]]></dc:creator><pubDate>Mon, 18 Jul 2022 04:09:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658117064015/9ivd_dP3o.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-how-good-is-tagging">How good is tagging?</h2>
<p>Rhetorical question; Tagging is <em>great</em>; my favourite organisation system, in fact.  Test systems with tagging allow you the flexibility to organize your tests according to <em>what code</em> they're testing, while running tests according to <em>what kind of test</em> you're after.</p>
<h2 id="heading-tagging-andamp-cypress">Tagging &amp; Cypress</h2>
<p>Unfortunately, Cypress lacks native support for tagging, and this makes me very sad face emoji.</p>
<p>So, I'm going to use <a target="_blank" href="https://github.com/saucelabs/saucectl"><code>saucectl</code></a> to retrofit tagging functionality onto our Cypress tests, whether you're running locally in Docker mode, or remotely against Sauce Labs' cloud.</p>
<h2 id="heading-wait-whats-saucectl">Wait, what's saucectl?</h2>
<p>Let's go to the README!</p>
<blockquote>
<p>The <code>saucectl</code> command line interface orchestrates the tests in your framework, providing rich parallelization, test history filtering, and analytics in Sauce Labs.</p>
</blockquote>
<p>Basically, <code>saucectl</code> is an amplifier for your tests.  It drags your Cypress, Playwright, TestCafe or Puppeteer kicking and screaming onto the Sauce Labs cloud, giving you delicious Sauce features (like analytics) along with all the goodies your framework has to offer.  Saucectl can <em>also</em> run your tests with Docker, giving you a quick and reliable test environment.  It's a great solution for using Cypress with CI.</p>
<p>(<em>Disclosure: I work for Sauce Labs.  I still really like <code>saucectl</code>.</em>)</p>
<h1 id="heading-our-approach">Our Approach</h1>
<p>In <code>config.yml</code> (the config file for <code>saucectl</code>), you can specify individual suites <a target="_blank" href="https://docs.saucelabs.com/web-apps/automated-testing/cypress/yaml/#suites">see here</a> which let you provide individual config options to Cypress.</p>
<p>By passing the <code>testFiles</code> option to Cypress, you can control which individual files it runs.  We're going to combine these two, to give us individually named test suites, each of which runs tests with a specific set of tags.</p>
<h2 id="heading-cant-you-do-that-with-cypress-alone">Can't you do that with Cypress alone?</h2>
<p>Yes, <em>technically</em>, but I prefer this approach for... let's call them ideological reasons.</p>
<p>While you <em>could</em> have individual config files, and call <code>cypress run --config-file somesuite.json</code> for each one, it's messy. If you make changes to one suite file, you have to make it across all of them, which is easy to forget.</p>
<p>With this solution, all your Cypress config stays as is, and only the tests being executed are changed.  Even better, control of which tests run is maintained in the <em>infrastructure</em> configuration, and infrastructure is likely to have the most significant impact over what 'flavours' of test you want to run.</p>
<h2 id="heading-how-were-implementing-tagging">How we're implementing tagging</h2>
<p>This solution makes use of <em>globbing</em>, a way of selecting a group of files. It's pretty common; Common enough to have a <a target="_blank" href="https://en.wikipedia.org/wiki/Glob_(programming)">Wikipedia</a> page, and you've likely seen at least one glob before.</p>
<p>We're going to alter our tests so each test suite is configured to run a set of globs corresponding to our tagged tests.</p>
<h2 id="heading-example">Example</h2>
<h3 id="heading-suite-files">Suite Files</h3>
<p>Say this is our test suite.  Each file contains tests that should only run on a specific browser (or combination of browsers).  We've named them so each filename includes the relevant tags, separated by underscores, like <code>_chromeOnly</code> or <code>_ieOnly</code>.  These could just as easily be <code>_ui</code> or <code>_FrenchLanguage</code> or anything you want to tag by.</p>
<pre><code>specs<span class="hljs-operator">/</span>
  ui<span class="hljs-operator">/</span>
    login_chromeOnly_spec.js
    login_firefoxOnly_spec.js
  accounting<span class="hljs-operator">/</span>
    jsMathBugFix_ieOnly_spec.js
    chromiumCantCount_edgeOnly_chromeOnly_spec.js
</code></pre><h3 id="heading-configyml">Config.yml</h3>
<p>Here's the suites component of our <code>config.yml</code>. Each suite has a unique glob for testFiles which finds all tests in your specs folder which include the respective tag/s.</p>
<pre><code>  <span class="hljs-attribute">suites</span>:
    - <span class="hljs-attribute">name</span>: <span class="hljs-string">"Where There's Smoke There's"</span>
      <span class="hljs-attribute">browser</span>: <span class="hljs-string">"firefox"</span>
      <span class="hljs-attribute">platformName</span>: <span class="hljs-string">"Windows 10"</span>
      <span class="hljs-attribute">config</span>:
        <span class="hljs-attribute">testFiles</span>: [<span class="hljs-string">"specs/**/*firefoxOnly*_spec.js"</span>]

    - <span class="hljs-attribute">name</span>: <span class="hljs-string">"Cream on"</span>
      <span class="hljs-attribute">browser</span>: <span class="hljs-string">"chrome"</span>
      <span class="hljs-attribute">platformName</span>: <span class="hljs-string">"Windows 10"</span>
      <span class="hljs-attribute">config</span>:
        <span class="hljs-attribute">testFiles</span>: [<span class="hljs-string">"specs/**/*chromeOnly*_spec.js"</span>]

    - <span class="hljs-attribute">name</span>: <span class="hljs-string">"Livin' on the"</span>
      <span class="hljs-attribute">browser</span>: <span class="hljs-string">"edge"</span>
      <span class="hljs-attribute">platformName</span>: <span class="hljs-string">"Windows 10"</span>
      <span class="hljs-attribute">config</span>:
        <span class="hljs-attribute">testFiles</span>: [<span class="hljs-string">"specs/**/*edgeOnly*_spec.js"</span>, <span class="hljs-string">"specs/**/*ieOnly*_spec.js"</span>]
</code></pre><h3 id="heading-how-it-works">How it works</h3>
<p>Each glob starts with <code>specs/**/*</code>.  This glob means "<em>start in the specs folder (<code>specs/</code>), then look in any subfolder(<code>**</code>), for any characters at all(<code>*</code>) followed by an underscore(`</em>`)".  </p>
<p>Let's look at how the first glob continues.  After <code>specs/**/*</code> we have <code>firefoxOnly*_spec.js</code>.  This means "_find anything containing the phrase <code>firefoxOnly</code>, followed by zero or more of any character(*), followed by <code>_spec.js_</code>".  For our tests, that only matches <code>specs/ui/login_firefoxOnly_spec.js</code>.</p>
<p>The second glob is <code>specs/**/*chromeOnly*_spec.js</code>, so it matches every file in every subfolder of <code>specs</code> which contains <code>chromeOnly</code>, followed by zero or more of any character, followed by <code>_spec.js</code>.  In our case, that's <code>specs/ui/login_chromeOnly_spec.js</code> AND <code>specs/accounting/chromiumCantCount_edgeOnly_chromeOnly_spec.js</code></p>
<h3 id="heading-did-you-notice">Did you notice?</h3>
<p>The <code>testFiles</code> parameter doesn't take a glob directly; it takes an <em>array</em> of globs.  That lets us pass in multiple entries, which is how suite three works. We're able to pass in two entries, one for any test tagged <code>edgeOnly</code> and one for any tagged <code>ieOnly</code>.  It will match any file in <em>either</em> of those blobs; in this case <code>specs/accounting/jsMathBugFix_ieOnly_spec.js</code> and <code>chromiumCantCount_edgeOnly_chromeOnly_spec.js</code>.</p>
<p>Additionally, test files can have <em>multiple tags</em>.  This is really useful when you want to run some tests in multiple scenarios; Say you have a limited set of functional tests that are also used to ensure deploys go well; You could tag them <code>_functional_postDeploy_spec.js</code> and they'd run when using <em>either</em> the <code>_functional</code> tag or the <code>_postDeploy</code> one.</p>
<h1 id="heading-and-thats-it">And that's it!</h1>
<p>A small change to how you name and select tests, and you're left with a more flexible testing system.  And of course, you can still run a default suite with <em>no</em> tags, and make sure you're running everything.</p>
<p>Happy Testing!</p>
<h4 id="heading-credits">Credits</h4>
<p>Photo by <a target="_blank" href="https://unsplash.com/@angelekamp?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Angèle Kamp
</a> on <a target="_blank" href="https://unsplash.com/photos/KaeaUITiWnc?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditShareLink">Unsplash</a></p>
]]></content:encoded></item></channel></rss>