<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/styles.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Lindsay Wardell</title><description>Programmer and writer.</description><link>https://lindsaykwardell.com/</link><language>en-us</language><item><title>An Hour Before Showtime</title><link>https://lindsaykwardell.com/blog/an-hour-before-showtime/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/an-hour-before-showtime/</guid><pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&quot;I think we should start looking for other people.&quot;&lt;/p&gt;
&lt;p&gt;It was around 9 AM on Tuesday, May 24th, 2022. The internet was still out at home, despite my best efforts to resolve the problem. I had called CenturyLink, but the earliest a technician could come out was that Friday. I had finished making breakfast, and was busy getting myself out the door, not something I had typically done in the morning since the start of the pandemic. In just under an hour, I would be going live to give a talk at the Vue Global Summit. It was my second year participating, after being introduced to the folks at Geekle by Tracy Lee of This Dot Labs the year prior.&lt;/p&gt;
&lt;p&gt;I heard my spouse at the time say it, felt my body stutter at the impact of the words. It wasn&apos;t so much as shock as realization. Things hadn&apos;t been good for awhile, and no matter what I did she was always angry or disappointed or just resentful of me. I wasn&apos;t brave enough to call it abuse, but I had spoken to a couple friends about how wrong things had felt, how to know when divorce was the right thing, how scared I was about life falling apart around me again. Four years prior, when I had shed both the religion and gender I had grown up being told were true, I had been left with few pillars on which to rebuild anything. Tearing down another one, even when it was due, left me terrified.&lt;/p&gt;
&lt;p&gt;At the time, I nodded, understanding. This wasn&apos;t the first time the topic had come up, but it had always been deferred, a future thing, in case my transition made her too uncomfortable. It had no bearing on me, since our relationship had already ended by most metrics. Now, though, it was real, it was happening, but I didn&apos;t have time for that. Didn&apos;t have time to parse that life as I had known it was over.&lt;/p&gt;
&lt;p&gt;I couldn&apos;t pause, because I had less than an hour until showtime.&lt;/p&gt;
&lt;p&gt;I got in the car, heart pounding, the words repeating in my head. I pulled up to a friend&apos;s house, settled my laptop on the desk, and got ready. My heart refused to slow, panic and dread and the more general nervous energy before stepping out on virtual stage. I reviewed my notes as the previous talk ended, and then it was my turn.&lt;/p&gt;
&lt;p&gt;I couldn&apos;t tell you what I talked about. I had two sessions that conference, one of them about functional programming, but I think that one was another day. I smiled, I joked, I answered questions, and then the camera went off. Quietly, heart hammering, I packed up my laptop, and went downstairs to the kitchen.&lt;/p&gt;
&lt;p&gt;My friend was there; she asked if I was okay. I must have been showing more emotion than I realized. Tears flowed, and I couldn&apos;t hold them back any longer.&lt;/p&gt;
&lt;p&gt;I now look back on that day and see someone who hurt me, again and again, as I did everything I could to sustain a broken relationship. I hear the words she spoke over the years, threatening and abusive.&lt;/p&gt;
&lt;p&gt;I am grateful that time is passed. I regret that I walked into it at all.&lt;/p&gt;
&lt;p&gt;Now, I&apos;m free from that world. That chapter of my life is over, and I am all the happier for it. I have an amazing, wonderful partner who loves me for who I am. We go on adventures together, hiking and exploring the world around us. We are both writers and artists, and I love sharing what we make with each other, or just reading and snuggling together. I love peaceful days, and restful nights, and knowing that at long last, I am home.&lt;/p&gt;
</content:encoded></item><item><title>Keep Learning</title><link>https://lindsaykwardell.com/blog/keep-learning/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/keep-learning/</guid><pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have a long-running interest in taking technology that is either old, or underpowered, and pushing it as far as I can. I have fond memories of my &lt;a href=&quot;https://en.wikipedia.org/wiki/Asus_Eee_PC&quot;&gt;EeePC&lt;/a&gt; 701 from the mid-2000&apos;s, with it&apos;s 4GB of storage, and trying to figure out how to make it do something useful (I think I got Skype working on it). A later edition, the EeePC 1000HA, came with a bit more power, but running Windows XP in 2008. I managed to get that running a version of Photoshop, which could take ten minutes to save a PSD, but it &lt;em&gt;worked&lt;/em&gt;, and at the time that was all that mattered.&lt;/p&gt;
&lt;p&gt;More recently, I&apos;ve been slowly nursing a 2013 Macbook Air to life. Its battery is failing, it only has 128GB of storage, but running Ubuntu 24.04 has been pretty smooth. I&apos;ve turned it into my personal, non-tech laptop, mainly for writing and browsing the internet. I do have Zed installed, along with the latest LTS version of Node, so that I can make adjustments to my personal site, but it&apos;s never going to be where I do my main tech work (that still falls to my trusty M1 Macbook Air).&lt;/p&gt;
&lt;p&gt;Yesterday, I had my first Linux kernal panic. I&apos;m kind of amazed that Linux hasn&apos;t previously thrown that curveball at me, but when the purple screen popped up I thought that maybe the hardware had been fried due to the battery going out. It really doesn&apos;t like to be put in sleep mode, and in a hurry I had closed it without turning it off, so it was a reasonable theory in my head.&lt;/p&gt;
&lt;p&gt;Being that we&apos;re in 2026, and AI has been adopted across the tech ecosystem, I had two options: use AI to try and solve my problem, or do it the old fashioned way and hit up a search engine. And since I am of an age where search engines were cool, I opted for the second option, and found &lt;a href=&quot;https://imadsaddik.com/blogs/fix-kernel-panic-linux&quot;&gt;this blog post&lt;/a&gt; by Imad Saddik that matched exactly the issue that I was having, and was a pretty cool website on top of that!&lt;/p&gt;
&lt;p&gt;I followed the instructions and was able to quickly resolve the issue, uninstalling the kernal version at fault. Success! And as a bonus, I have now experienced one more semi-common issue with Linux, and feel more confident in fixing it in the future.&lt;/p&gt;
&lt;p&gt;As I mentioned in &lt;a href=&quot;/blog/dont-replace-yourself-with-ai&quot;&gt;a previous blog post&lt;/a&gt;, I said that I&apos;ve been doing some experimentation with AI just to have an idea of what&apos;s going on. I&apos;ve given it tasks both simple and complex, and one thing I can tell you from my experience is that whatever command or directions it offers, it doesn&apos;t stick in my brain. When I&apos;m using AI in this way, I am getting something done, but I&apos;m not learning, not growing as an IT professional or software engineer. &lt;a href=&quot;https://news.harvard.edu/gazette/story/2025/11/is-ai-dulling-our-minds/&quot;&gt;This is not an unknown experience&lt;/a&gt;, and is in fact what I was talking about in that last post I wrote. When we turn over the wheel to AI, we don&apos;t just stop thinking, we stop learning.&lt;/p&gt;
&lt;p&gt;Tech companies, of course, want you to feel that AI is helpful for your learning or work, and are trying to find ways to make it more helpful, such as note takers, learning tutors, or coding assistants. And of course, they can provide value, and they do give you results. The question is not always, &quot;does this thing do what it says it does,&quot; although that is a very important question for any AI-focused app. The question should be, &quot;what tradeoffs am I making by using this tool instead of another method?&quot;&lt;/p&gt;
&lt;p&gt;As an example, we don&apos;t say that you should only use paper and pen to write, and that computers are the bane of written existence. But we do say that typically, &lt;a href=&quot;https://www.scientificamerican.com/article/why-writing-by-hand-is-better-for-memory-and-learning/&quot;&gt;handwritten notes or journals have a better chance of sticking in your brain than something you type&lt;/a&gt;. That&apos;s a tradeoff: do I want to write fast, or do I want to remember what I wrote?&lt;/p&gt;
&lt;p&gt;This same tradeoff is something that needs to be considered when deciding whether to reach for AI or not. If you&apos;re going to reach for AI, you are passing up learning opportunities, moments of struggle, and breakthroughs. You&apos;re disregarding the muscle memory that comes with doing those mundane, repetitive tasks, or the years of experience that when put together make you exceptional at your job.&lt;/p&gt;
&lt;p&gt;Again, I&apos;m not telling you how to do your job. I am not a purist on this topic by any means. But I would rather make a mistake and learn from it than have a computer ensure I never make another mistake again, because to me, learning and growing is what makes life have meaning.&lt;/p&gt;
&lt;p&gt;Keep trying. Keep struggling, failing, picking yourself up again.&lt;/p&gt;
&lt;p&gt;Keep learning.&lt;/p&gt;
</content:encoded></item><item><title>ParentShield: A Mindful Communication App</title><link>https://lindsaykwardell.com/blog/parentshield-a-mindful-communication-app/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/parentshield-a-mindful-communication-app/</guid><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Don&apos;t Replace Yourself With AI</title><link>https://lindsaykwardell.com/blog/dont-replace-yourself-with-ai/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/dont-replace-yourself-with-ai/</guid><pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Artificial intelligence, as we regrettably know, is everywhere. I see articles praising it for its speed and efficiency at getting work done, but I also see &lt;a href=&quot;https://www.wheresyoured.at/the-case-against-generative-ai/&quot;&gt;article&lt;/a&gt; &lt;a href=&quot;https://henry.codes/writing/ai-at-work-is-anti-labor-by-design/?utm_source=the-index&amp;amp;utm_medium=newsletter&quot;&gt;after&lt;/a&gt; &lt;a href=&quot;https://gomakethings.com/ai-and-cigarettes/&quot;&gt;article&lt;/a&gt; talking about how AI is doing &lt;a href=&quot;https://www.engadget.com/ai/moltbook-the-ai-social-network-exposed-human-credentials-due-to-vibe-coded-security-flaw-230324567.html&quot;&gt;more harm than good&lt;/a&gt;, &lt;a href=&quot;https://disconnect.blog/generative-ai-closes-off-a-better/&quot;&gt;if it is doing any good at all&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;m not going to try to convince you one way or the other, in part because I regularly feel torn about it. As a technology, it doesn&apos;t interest me a lot, because I feel like it&apos;s just another step down the machine learning path that we were already on. I don&apos;t like that the tech industry has decided to make this the de facto future of computing. I have used it, in large part so that I know what&apos;s going on, but I also now have an entire computer dedicated to having zero AI tools on it. I&apos;m building &lt;a href=&quot;https://gaze.lindsaykwardell.com/&quot;&gt;a social photography app&lt;/a&gt; that is dedicated to being free of AI tools and respecting its users, but I&apos;ve also been working on a tool that uses a local AI model to &lt;a href=&quot;https://parentshield.lindsaykwardell.com/&quot;&gt;filter abusive texts&lt;/a&gt; from people I&apos;m legally unable to cut out of my life.&lt;/p&gt;
&lt;p&gt;In short, AI is potentially helpful, but always harmful to creativity and expression. We should not be relying on it, and yet we are. And it is to this point that I am writing.&lt;/p&gt;
&lt;h2&gt;Abdicating thought&lt;/h2&gt;
&lt;p&gt;AI has been pitched as a way to get things done &lt;em&gt;faster&lt;/em&gt;. It can write for you, teach you, code for you, be a helpful assistant for you. At work, it can do all of the things you don&apos;t want to do, like writing boilerplate code, or managing tests, or even write entire features for you, all with a press of a button and without any human supervision.&lt;/p&gt;
&lt;p&gt;On the surface, that&apos;s pretty cool! But here&apos;s what I have seen: AI can write the code (some of it decent), it can do the tasks, but despite advertising to the contrary, it cannot think. It doesn&apos;t know why you&apos;re writing the code or email, it doesn&apos;t understand what the end user actually needs. All it knows is patterns, which we as humans see as intelligence. Humans are really good at pattern matching, so when a computer is good at it too, we think it&apos;s amazing. Which, fair, a computer knowing what I want without having to type in a shell command is really nice, but it&apos;s not intelligent. It&apos;s smart in the same way a smart home is smart, by doing things that look like a human is doing them.&lt;/p&gt;
&lt;p&gt;But in all of this, the computer has no better understanding of what&apos;s going on than before. If we rely on AI to do our critical thinking for us, then we are abdicating our responsibility to our employers, our customers and ourselves. Submitting or responding to a PR request, providing a design decision, or any other use of AI to replace your individual opinion, viewpoint, and voice is doing just that, stating through actions that a computer is more capable than you are.&lt;/p&gt;
&lt;h2&gt;In your own words&lt;/h2&gt;
&lt;p&gt;It&apos;s tempting to let AI take care of all of our work. Shows like Star Trek show a future with powerful computers, capable of even generating physical objects, including food and tools. It&apos;s kind of amazing to just let the computer do your job for you, so that you can live you best utopian life. However, even in Star Trek, the characters we root for are always the ones calling the shots.&lt;/p&gt;
&lt;p&gt;We don&apos;t cheer for Geordie because he came up with the correct prompt to make the computer AI fix the warp core, or Picard for getting the computer to come up with negotiation techniques. We care about these people because Geordie has earned his position in engineering, because we know Picard is going to make the best choices for his crew and those around him. These are people, using their personalities, identities, and skills to make a difference in the universe around them.&lt;/p&gt;
&lt;p&gt;Especially in the current environment, where everyone has access to AI tools, using your own voice and opinions is the only way to stand out. There characters we love from Star Trek are depicted as having full, rewarding lives, with hobbies, interests, friendships, relationships. They live, they feel, they are alive, and they bring all of themselves to the things they do. And we should too.&lt;/p&gt;
&lt;p&gt;Use your words, give your opinions, share your insights. Don&apos;t let the AI speak for you. Even if you&apos;re just doing your job, your voice matters. You matter. Don&apos;t give up on that because it&apos;s easier to let a computer do your job.&lt;/p&gt;
</content:encoded></item><item><title>Moments and Moonlight</title><link>https://lindsaykwardell.com/blog/moments-and-moonlight/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/moments-and-moonlight/</guid><pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I often feel like I don&apos;t have much to write about, much less something to write about that others will find interesting. I&apos;m hoping to break that pattern this year, but it&apos;s something that has stopped me from joining in conversations online (such as on Bluesky) on more than one occasion. I&apos;m just someone online, after all; why would anyone read what I have to say?&lt;/p&gt;
&lt;p&gt;The same is true of other creative activities, too. I&apos;ve got a handful of other projects that I haven&apos;t talked about or shared, or if I have only vaguely. I&apos;d wager it&apos;s a fear of rejection, or something to that effect. If I do it for myself, I can appreciate it, and I don&apos;t have to worry about what anyone else is saying or thinking about it. The only problem is, it&apos;s fun to share things that you create, or find, or just like. Because someone else might enjoy it too.&lt;/p&gt;
&lt;p&gt;A couple nights ago, I stepped outside to take some garbage out, and I happened to look up and see a beautiful sky of stars and a nearly full moon right above my house. As many people do, I&apos;m sure, I&apos;ve tried taking pictures of the moon with my phone, but it never works. I have a Canon 60D that&apos;s been sitting around collecting dust, but I almost always forget its there for one reason or another (mostly because it&apos;s bulky and doesn&apos;t fit in my pocket like a phone would). But this time, for whatever reason, I decided to charge up the battery earlier that day.&lt;/p&gt;
&lt;p&gt;I run into the house, grab the battery, plug it in, and turn on the camera. Nothing happens. After fumbling with it, I realize there&apos;s no SD card in it, and grab the first one I can find (from a Raspberry Pi that is also collecting dust on a shelf). I plug it in, and the camera turns on. I rush back outside and try to take some shots of the moon through the clouds. After nine photos, I get an &quot;out of memory&quot; error. Of course! I had just used a card that was previously running an operating system, it makes sense that it&apos;s practically full.&lt;/p&gt;
&lt;p&gt;Rushing back inside, I grab my Macbook Air to empty the card, but of course it doesn&apos;t have an SD card reader because it&apos;s from 2020 and Apple has courage. I pick up my writing computer, which is a 2013 Macbook Air (running Ubuntu), take off the pictures, and reformat the card. Now free to photograph whatever I want, I run back outside and grab a few more shots, although the clouds had meandered off.&lt;/p&gt;
&lt;p&gt;After a bit more photography, my wife comes outside to join me, and we just stand there in the driveway, enjoying the stars above us, naming the ones that we know, feeling connected to the universe around us.&lt;/p&gt;
&lt;p&gt;The universe may never know we&apos;re there, but we can still enjoy its beauty in the quiet moments, watching starlight that has taken years to reach us. It doesn&apos;t matter that they&apos;re small, or numerous, or sometimes hard to see. They&apos;re beautiful, and it&apos;s worth slowing down to enjoy them, take them in, and feel connected to something bigger than our world.&lt;/p&gt;
</content:encoded></item><item><title>Rekindling</title><link>https://lindsaykwardell.com/blog/rekindling/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/rekindling/</guid><pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been awhile since I just wrote a blog post, and a lot has happened. My last actual blog post was a review of 2022, which was probably the worst year of my life. Since then, I&apos;ve gotten married, I have a new job that I love, and have discovered that I was probably hyperfixating a bit too much on tech.&lt;/p&gt;
&lt;p&gt;I&apos;ve always been into technology (I&apos;ve &lt;a href=&quot;/blog/when-i-grow-up&quot;&gt;previously written about it&lt;/a&gt;), but it was in early 2018 when I made the hard pivot from IT manager/administrator into software development. Coincidentally, this was the same time that I left the church I grew up attending and came out as transgender. I did a speed run of frontend best practices for my first project, going from jQuery to vanilla JS to Vue in the span of a few weeks. The backend did a similar run from PHP and MySQL to NodeJS and Postgres.&lt;/p&gt;
&lt;p&gt;In that first year, I graduated from The Tech Academy, switched my bachelor&apos;s program, and wrote a new web-based application to replace my company&apos;s previous solution from 1995. I dove into every podcast I could find, read as much as I could, and explored almost all the common frontend frameworks (sorry Angular). I even built a real-time LAN-based board game, just to see if I could.&lt;/p&gt;
&lt;p&gt;Looking at it from today&apos;s perspective, I was using code to avoid (or replace) feelings that I was having as two of the central pillars of my identity crashed to dust around me, and I didn&apos;t stop until, well, 2023. That was the year I started my tech podcast, Human Side of Dev, and tried to keep a weekly cadence of editing recordings. I started the podcast while living in a basement too small for my height, trying to figure out what I was supposed to do next.&lt;/p&gt;
&lt;p&gt;But then, by luck, happenstance, or the aligning of stars, I met the person I would marry. And while I was still scraping the bottom of my emotional barrel, and filling the holes with Javascript-shaped duct tape, I also learned that I was someone worth loving, and not just because I&apos;d written a cool technical article on Astro or interviewed really cool people.&lt;/p&gt;
&lt;p&gt;It&apos;s still hard, even now, to really feel comfortable talking about the things I enjoy beyond tech. I felt panicked just writing my new description on the &lt;a href=&quot;/about&quot;&gt;about page&lt;/a&gt;. But I&apos;m going to start doing it anyway, because I don&apos;t need to define myself by my job, or my tech stack, or what the latest discussions are among framework authors.&lt;/p&gt;
&lt;p&gt;For 2026, I&apos;m rekindling my website as a place for creativity and joy. I think it&apos;s going to be a very fun year.&lt;/p&gt;
</content:encoded></item><item><title>No Words</title><link>https://lindsaykwardell.com/blog/no-words/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/no-words/</guid><pubDate>Sun, 02 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have no words.&lt;/p&gt;
&lt;p&gt;I see reports of immigrants taken&lt;br /&gt;
from homes, from jobs, from hospital,&lt;br /&gt;
and I have no words.&lt;/p&gt;
&lt;p&gt;I read orders erasing my identity,&lt;br /&gt;
wiping my rights away one by one,&lt;br /&gt;
and I have no words.&lt;/p&gt;
&lt;p&gt;I watch headlines of raised tarriffs,&lt;br /&gt;
fired officials,&lt;br /&gt;
leaked personal information,&lt;br /&gt;
crashing planes,&lt;br /&gt;
and I have no words.&lt;/p&gt;
&lt;p&gt;I hear the cries&lt;br /&gt;
of people fearing,&lt;br /&gt;
crying,&lt;br /&gt;
hurting,&lt;br /&gt;
dying,&lt;br /&gt;
and I have no words.&lt;/p&gt;
&lt;p&gt;There are no words&lt;br /&gt;
for atrocities such as this.&lt;br /&gt;
They all fall flat,&lt;br /&gt;
too hollow,&lt;br /&gt;
too frail.&lt;/p&gt;
&lt;p&gt;And so I watch, read, listen, and mourn,&lt;br /&gt;
wordless in the torrent,&lt;br /&gt;
hoping it passes swiftly,&lt;br /&gt;
fearing it may not pass at all.&lt;/p&gt;
&lt;p&gt;I have no words&lt;br /&gt;
for the cruelty with which&lt;br /&gt;
we now live.&lt;/p&gt;
</content:encoded></item><item><title>Grief Is Not A Casual Thing</title><link>https://lindsaykwardell.com/blog/grief-is-not-a-casual-thing/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/grief-is-not-a-casual-thing/</guid><pubDate>Sat, 11 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Grief is not a casual thing.&lt;/p&gt;
&lt;p&gt;It&apos;s not something you pick up as a hobby,&lt;br /&gt;
or go to watch at the movies.&lt;/p&gt;
&lt;p&gt;It&apos;s not a snack, or a treat,&lt;br /&gt;
or a get-rich-quick scheme.&lt;/p&gt;
&lt;p&gt;It&apos;s not a mural on a building&lt;br /&gt;
or a meme shared by a friend.&lt;/p&gt;
&lt;p&gt;It&apos;s not even a poem&lt;br /&gt;
written in a moment of clarity.&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Grief is more than that.&lt;/p&gt;
&lt;p&gt;Grief is a way of life,&lt;br /&gt;
a modality of viewing the world around you.&lt;/p&gt;
&lt;p&gt;Grief is the ocean, filling the horizon&lt;br /&gt;
in all directions until you can&apos;t see the land.&lt;/p&gt;
&lt;p&gt;Grief is the sky, filled with dazzling stars,&lt;br /&gt;
pinpricks of pain and sorrow on an empty canvas.&lt;/p&gt;
&lt;p&gt;When approaching grief,&lt;br /&gt;
do not think yourself in control.&lt;br /&gt;
Allow the grief to come over you,&lt;br /&gt;
fill you,&lt;br /&gt;
pierce your soul&lt;br /&gt;
without backing down&lt;br /&gt;
or looking away.&lt;/p&gt;
&lt;p&gt;Drink it in,&lt;br /&gt;
and feel&lt;br /&gt;
what&lt;br /&gt;
remains&lt;br /&gt;
in&lt;br /&gt;
its&lt;br /&gt;
wake.&lt;/p&gt;
&lt;p&gt;Keep watching it, closely,&lt;br /&gt;
without guilt or shame,&lt;br /&gt;
as tears well&lt;br /&gt;
and the colors of the world&lt;br /&gt;
dim around you.&lt;/p&gt;
&lt;p&gt;They won&apos;t dim forever.&lt;/p&gt;
&lt;p&gt;But this is important.&lt;/p&gt;
&lt;p&gt;A casual grief can bear much,&lt;/p&gt;
&lt;p&gt;But a transformative grief,&lt;br /&gt;
that pain is powerful enough&lt;br /&gt;
to heal a life.&lt;/p&gt;
</content:encoded></item><item><title>To the Past</title><link>https://lindsaykwardell.com/blog/to-the-past/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/to-the-past/</guid><pubDate>Tue, 17 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To the past&lt;br /&gt;
that wove my history&lt;br /&gt;
before me,&lt;/p&gt;
&lt;p&gt;That laid the stars&lt;br /&gt;
by which I sailed,&lt;/p&gt;
&lt;p&gt;That planted the&lt;br /&gt;
thorns and brambles&lt;br /&gt;
I had to cross,&lt;/p&gt;
&lt;p&gt;The past&lt;br /&gt;
and the wounds it left me,&lt;/p&gt;
&lt;p&gt;Goodbye.&lt;/p&gt;
</content:encoded></item><item><title>Divorce</title><link>https://lindsaykwardell.com/blog/divorce/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/divorce/</guid><pubDate>Tue, 10 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I feared divorce because of what it meant.&lt;/p&gt;
&lt;p&gt;A failed life.&lt;br /&gt;
A poor choice.&lt;br /&gt;
A broken home.&lt;/p&gt;
&lt;p&gt;I feared divorce because of what it was.&lt;/p&gt;
&lt;p&gt;An end.&lt;br /&gt;
An exhale.&lt;/p&gt;
&lt;p&gt;I feared divorce because of what it implied.&lt;/p&gt;
&lt;p&gt;What it said.&lt;/p&gt;
&lt;p&gt;What it whispered in the dark, in the corners of my mind.&lt;/p&gt;
&lt;p&gt;I feared divorce, and so I avoided it. I wouldn&apos;t look it in the eye, wouldn&apos;t say &quot;good morning&quot; as it strolled by. I drove by on rainy days, leaving it in the cold.&lt;/p&gt;
&lt;p&gt;Until I couldn&apos;t run from it any more.&lt;/p&gt;
&lt;p&gt;I turned, rage in my heart and tears in my eyes. I lashed out in agony, in bitter sorrow. All the broken promises, all the lies, all the pain of a lifetime I gave to it. My throat, raw with emotion and hoarse with exertion, unleashed all that brimmed within me.&lt;/p&gt;
&lt;p&gt;And then, when my rage was spent, it held me close. Allowed the heat of my words to linger on the air. My head rested on its shoulder, tears streaming down my face. We sat there, a timeless moment, a point of time that stretched on, a sea of stars enveloping us.&lt;/p&gt;
&lt;p&gt;I realized then, that I didn&apos;t fear the one who now buoyed me in this new place. Where once I had seen failure, I saw hope. Where once had been an end, I saw a way forward.&lt;/p&gt;
&lt;p&gt;I did not fear divorce.&lt;/p&gt;
&lt;p&gt;I feared what would happen if I did not.&lt;/p&gt;
</content:encoded></item><item><title>A Mistake</title><link>https://lindsaykwardell.com/blog/a-mistake/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/a-mistake/</guid><pubDate>Wed, 28 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I made a mistake one day.&lt;/p&gt;
&lt;p&gt;Well, more than one. Probably I can’t remember the first step on this particular trail, it was so small. Maybe it was deciding to go on a mission. Maybe choosing to pray and ask a false god whether a charlatan’s book was true. Or trusting the adults in my life over my instinct to run, get away, go to college, start my life.&lt;/p&gt;
&lt;p&gt;Maybe it was knowing my mission was a mistake, then confiding that to the architect of my personal hell. Maybe if I had left, even then, I could have avoided the worst of what was yet to come.&lt;/p&gt;
&lt;p&gt;Maybe it was taking in the repeated words and deeds of decades that told me I was worth less than dirt, and if I wasn’t making myself ill in service to God, then I wasn’t doing it right.&lt;/p&gt;
&lt;p&gt;Maybe it was not listening to my inner voice plead for me to just &lt;em&gt;listen&lt;/em&gt;, recognize the situation for what it was, instead of casting aside the only compass worth a damn.&lt;/p&gt;
&lt;p&gt;Or, it could have been giving my heart to someone who immediately held a dagger to it, the blade digging into me until I was unable to even recognize the significance of my words when I told my therapist I’d be all right if she died, I’d be relieved.&lt;/p&gt;
&lt;p&gt;It could easily have been when I prayed to know whether it was time to have a child, because if any one decision sealed my path, it was this.&lt;/p&gt;
&lt;p&gt;So yes, I made a mistake one day. Probably more than one. I know that my path was mine to choose, and I chose what I had been taught was &lt;em&gt;right&lt;/em&gt;, and &lt;em&gt;good&lt;/em&gt;, and would lead me to &lt;em&gt;joy&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But I was wrong.&lt;/p&gt;
&lt;p&gt;Now I break free of that path, of those choices, desperate to get away. I carry the small child I created in my arms as we navigate a dense, unknown jungle. But They know where she is, those who seek to turn her inward, selfish, spiteful, hateful, just like Them. She know the path They want her to walk. She imitates Them, scratching and biting, screaming, as desperate to go back as I am to break free, fearful that I won’t follow her if she does.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;won’t&lt;/em&gt; follow.&lt;/p&gt;
&lt;p&gt;I made a mistake one day. More than one; I made dozens, hundreds, &lt;em&gt;thousands&lt;/em&gt;, each one glimmering with tarnished trust, false hope, and stolen time. But I’m done making those mistakes, living that life, walking that path. I’m done. I won’t do it any more.&lt;/p&gt;
&lt;p&gt;I own my choices, and I embrace the difficult road through vines and brambles I now face. Every step, every act, every cut through their tangled grasp is one more gasping stroke closer to true joy, free from the mistakes I made before.&lt;/p&gt;
&lt;p&gt;Never again will I serve a false god.&lt;/p&gt;
&lt;p&gt;Never again will I allow myself to be abused.&lt;/p&gt;
&lt;p&gt;Never again will I give my enemies that which I treasure.&lt;/p&gt;
&lt;p&gt;I am free. And this child is coming with me, until she chooses her own path, makes her own mistakes. I helped bring her into this world, cared for her, loved her, held her in my arms, sheltered her in the darkest night. I sang to her, rocked her, and kept her from harm. I will not leave her to the wolves, and I will teach her to fight against abuse so she can stand on her own one day.&lt;/p&gt;
&lt;p&gt;I am resolved, and I am fierce. I do not wait. I leave my past, once and for all, and I bring the child who bears the weight of all my mistakes within her soul. The past fades behind, and the new trail opens before us.&lt;/p&gt;
&lt;p&gt;New trails. True love. Joy found in the simplest flower. A tender kiss, and I know the way forward.&lt;/p&gt;
&lt;p&gt;And I &lt;em&gt;never&lt;/em&gt; look back.&lt;/p&gt;
&lt;p&gt;I made a mistake one day.&lt;/p&gt;
&lt;p&gt;But it is not this day.&lt;/p&gt;
</content:encoded></item><item><title>When You Dream</title><link>https://lindsaykwardell.com/blog/when-you-dream/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/when-you-dream/</guid><pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When you dream,&lt;br /&gt;
Dream a big dream.&lt;/p&gt;
&lt;p&gt;Filled not with toys,&lt;br /&gt;
but love,&lt;/p&gt;
&lt;p&gt;Not greed,&lt;br /&gt;
but joy.&lt;/p&gt;
&lt;p&gt;Not jealousy,&lt;br /&gt;
but room for kindness.&lt;/p&gt;
</content:encoded></item><item><title>The Life</title><link>https://lindsaykwardell.com/blog/the-life/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/the-life/</guid><pubDate>Sat, 28 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I hold up my life to the light&lt;br /&gt;
and observe the shapes drawn across it.&lt;/p&gt;
&lt;p&gt;Swirls of delight, bright dashes of joy,&lt;br /&gt;
Long, gentle moments with friends.&lt;br /&gt;
The light shimmers joyously here.&lt;/p&gt;
&lt;p&gt;Strokes of bitterness, anger,&lt;br /&gt;
Where the colors are dull and uneven.&lt;br /&gt;
Blotches mark where patches were placed.&lt;/p&gt;
&lt;p&gt;There are cracks in it, too; I touch them gently.&lt;br /&gt;
Moments when all seemed lost,&lt;br /&gt;
But not forever.&lt;br /&gt;
Not now.&lt;/p&gt;
&lt;p&gt;Some parts have snapped off completely,&lt;br /&gt;
Whether by my hand or another’s;&lt;br /&gt;
Here the colors are vibrant,&lt;br /&gt;
The memories most sharp, the light most attuned.&lt;/p&gt;
&lt;p&gt;It is not a pretty thing, all bulky and misshapen.&lt;br /&gt;
It won’t win any contests, and it’s too big for a shelf.&lt;br /&gt;
I don’t suppose someone would pay for it&lt;br /&gt;
And show it to their friends.&lt;/p&gt;
&lt;p&gt;And yet, it’s beauty is undeniable.&lt;br /&gt;
The light etches across it like the embrace of time,&lt;br /&gt;
Full of nostalgia and promise at once.&lt;br /&gt;
It’s taken a lifetime to make,&lt;br /&gt;
And I think I see some room left for more changes.&lt;/p&gt;
&lt;p&gt;I set my life on the table before me.&lt;br /&gt;
It rests there, all crooked and wobbly.&lt;br /&gt;
I feel every moment I poured it.&lt;br /&gt;
Every tear.&lt;br /&gt;
Every laugh.&lt;br /&gt;
Every sigh.&lt;br /&gt;
Every word.&lt;br /&gt;
I feel them all, breathing them in.&lt;/p&gt;
&lt;p&gt;I embrace the creation before me.&lt;br /&gt;
Its sharp edges prick my arms,&lt;br /&gt;
But I don’t back away.&lt;br /&gt;
They deserve love, too.&lt;/p&gt;
&lt;p&gt;I breathe, and the smells of moments long past fill me.&lt;br /&gt;
I listen, and songs dance in my soul.&lt;br /&gt;
I feel, and I feel it all.&lt;/p&gt;
&lt;p&gt;I release, and before me &lt;br /&gt;
The poor thing I made is beautiful, &lt;br /&gt;
Filled with wonder and sorrow,&lt;br /&gt;
Yearning and mourning.&lt;/p&gt;
&lt;p&gt;Its beauty is unique, unlike any other.&lt;br /&gt;
Some may not see it,&lt;br /&gt;
But I do.&lt;br /&gt;
And I will treasure it always.&lt;/p&gt;
</content:encoded></item><item><title>Once</title><link>https://lindsaykwardell.com/blog/once/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/once/</guid><pubDate>Wed, 29 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Once, I dreamt of stars blazing,&lt;br /&gt;
Sunrise burning,&lt;br /&gt;
Horizons beckoning.&lt;/p&gt;
&lt;p&gt;Once, I dreamt of wild forests,&lt;br /&gt;
Soaring mountains,&lt;br /&gt;
Flowing rivers.&lt;/p&gt;
&lt;p&gt;Once, the dream died.&lt;br /&gt;
No more stars.&lt;br /&gt;
No more forests.&lt;/p&gt;
&lt;p&gt;Once, the dream became an echo,&lt;br /&gt;
The echo a memory,&lt;br /&gt;
The memory forgotten.&lt;/p&gt;
&lt;p&gt;Once, the dream died.&lt;/p&gt;
&lt;p&gt;But only once.&lt;/p&gt;
&lt;p&gt;Now, the stars’ flame is renewed,&lt;br /&gt;
The trees whisper once more,&lt;br /&gt;
Horizons call out, “Come back.”&lt;/p&gt;
&lt;p&gt;And this time,&lt;br /&gt;
I will listen.&lt;/p&gt;
</content:encoded></item><item><title>At the End</title><link>https://lindsaykwardell.com/blog/at-the-end/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/at-the-end/</guid><pubDate>Thu, 09 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At the end of winter, the flowers bloom.&lt;/p&gt;
&lt;p&gt;At the end of a rainstorm, the clouds part.&lt;/p&gt;
&lt;p&gt;At the end of night, the dawn breaks.&lt;/p&gt;
&lt;p&gt;At the end of grief, hope is reborn.&lt;/p&gt;
</content:encoded></item><item><title>2022 In Review</title><link>https://lindsaykwardell.com/blog/2022-in-review/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/2022-in-review/</guid><pubDate>Tue, 17 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2022 was, for me, the year that never ends. I started the year on the high of 2021; amazing opportunities, getting to work at two fantastic companies, and making great strides in my personal and professional lives. This year, I end on a low that was altogether unexpected. There was certainly good in this year, which I&apos;ll get to, but this year stands out as bitter compared to most.&lt;/p&gt;
&lt;h2&gt;Past Expectations&lt;/h2&gt;
&lt;p&gt;At the start of 2022, this is what I wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Looking forward into 2022, I’m very excited for what is to come in the future. I plan on continuing to blog and podcast about the tech that’s interesting to me, and take care of myself and my family as we continue to navigate the pandemic. I wish you all the best, and that you are all able to stay safe and be well. Here’s to 2022!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was able to continue blogging and do some podcasting, as well as streams and conferences. I&apos;ve had a lot of fun appearing on streams such as Some Antics and at ViteConf 2022, both as a presenter and a moderator. I was able to end the year recording an episode of PodRocket, which released at the start of January. Podcasting and appearing in public streams is something that I really enjoy, and I hope to continue doing more of that.&lt;/p&gt;
&lt;p&gt;The family note is more painful, and I will explain that as best I can as I talk about more specifics to the year.&lt;/p&gt;
&lt;h2&gt;Major Events&lt;/h2&gt;
&lt;h3&gt;Transition Progress&lt;/h3&gt;
&lt;p&gt;I started HRT in 2021, and so this was the first full year of HRT. The emotional and physical changes have been amazing, and I have felt so much more comfortable in my body that previous years. I started my transitioning journey socially in 2018, and while it helped a lot to be addressed by the gender that I am, the ability to tap into my emotions more has been phenomenal, and my body feels much more comfortable to be in.&lt;/p&gt;
&lt;h3&gt;Transportation Woes&lt;/h3&gt;
&lt;p&gt;In January, I was in a car crash. A driver ran a red light as I was turning, and rammed into the front of my car. Thankfully my only injury was a concussion, but recovery was still unpleasant. Using screens and devices was nausea inducing, and there was a very frustrating brain fog that made it hard to remember some words. It took a couple weeks to recover to where I had been. Thankfully as well, I was the only one in the car, and the other driver was relatively fine as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/car-crash.jpeg&quot; alt=&quot;Photo of my car that I took at the scene, the front bumper is mostly off and liquids are draining into the road.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This was not my only accident on the road this year unfortunately. We decided not to replace the car, and instead embrace biking. I live in Portland, Oregon, and it&apos;s a pretty bike friendly city. I bought a wonderful folding bike, the &lt;a href=&quot;https://blixbike.com/products/vika-electric-folding-bike&quot;&gt;Vika+ Flex folding bike by Blix&lt;/a&gt;. It&apos;s been wonderful, although it took a bit of getting used to. In August, after about a month of riding around, my bike went out from under me and I crashed to the ground. This gave me concussion #2 for the year, and very painful scrapes and some bruised ribs. The concussion healed much faster, but the other injuries took much longer to heal properly.&lt;/p&gt;
&lt;p&gt;The third accident took place in downtown Portland in October, when my bike tire got stuck in the Max tracks. This led to scraping up the other side of my body, but thankfully no concussion this time, and the bike itself was perfectly fine! I&apos;ll take the small wins when they come.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/bike.jpeg&quot; alt=&quot;My red Vika+ Flex!&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Conferences, Streams, and Podcasts&lt;/h3&gt;
&lt;p&gt;I got to participate in a number of different events this year, which was a blast as always. I returned to Vue Global Summit, and was able to participate in ViteConf as both a presenter and moderator. I also presented at The Tech Academy for the second time, which was a fun event with newer developers. It was a lot of fun to participate in those events. I also got to be on Some Antics, JavaScript Jabber, FSJam, and Software Unscripted.&lt;/p&gt;
&lt;p&gt;2022 was the year I stepped away from Views on Vue. There were multiple reasons for this, but the big one for me was the added stress of watching and discussing the Vue ecosystem while my job had me learning and investing in the Elm ecosystem. This felt like too much of a mental burden at the time, and so I made the decision to step away. I really love podcasting, and hope this year to start a new podcast that I can participate on with some regularity.&lt;/p&gt;
&lt;h3&gt;Travel &amp;amp; Adventures&lt;/h3&gt;
&lt;p&gt;I got to travel a lot more in 2022. In March, I got to go out to Hood River, Oregon, and while there went with some friends to hike around at Catherine Creek, Washington. It&apos;s so beautiful there, I highly recommend anyone visit who can.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/catherine-creek.jpeg&quot; alt=&quot;Catherine Creek, Washington state&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In April, my company held a retreat in San Antonio, Texas. This was my first time flying since the pandemic began, and it was also right as airlines were allowing travel without a mask. There were two large stress moments in that trip. First, leaving Portland was very rough. I had set my alarm for 3, but I woke up at 2 from anxiety. Then, while double checking my bags, one of them broke, and I had to quickly repack everything. Then, my Lyft ride was 30 minutes late due to construction on the highway. After all that, I made it onto my flight from PDX to Denver with 5 minutes to spare.&lt;/p&gt;
&lt;p&gt;The rest of the trip to Texas went without issue, and after arriving a few of us gathered and had an off script lunch. It was a really fun experience. After getting to the retreat, we received our rooms, and some rapid tests for COVID. The plan was to test prior to attending dinner, and then testing every day to be safe. While my test came up negative, someone I had contact with tested positive, and so I was required to quarantine. This was obviously not what I had hoped for out of this trip, but the travel was nice.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/flight-over-houston.jpg&quot; alt=&quot;Photo from above Houston, Texas, from an airplane&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In May, I got to go camping with my sister in law. We did a roadtrip around Mt. Hood, went up to Catherine Creek (I had loved it so much the first time I had to go back), and then camped at Oxbow Park. It was the first time I had camped in years, and it was so much fun to get to do that again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mt-hood.jpg&quot; alt=&quot;Mt Hood, Oregon&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Star Commander&lt;/h3&gt;
&lt;p&gt;I have been working on a long running side project to make a board/card game for over a decade, titled &lt;a href=&quot;https://www.starcomgame.com/&quot;&gt;Star Commander&lt;/a&gt;. This year I made tremendous progress, getting to do a lot of testing and refining with friends and acquaintances. At the end of the year, I printed a fresh copy of the game, using AI generated art for the cards. It&apos;s not done yet, and things aren&apos;t perfect, but it feels so good to play a game that feels real in a way the prototypes couldn&apos;t. Excited to see what comes of this in the coming year!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/starcom.jpeg&quot; alt=&quot;Photo of Star Commander&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Family Issues&lt;/h3&gt;
&lt;p&gt;I&apos;m really uncomfortable writing this section, but in short my marriage ended. 2022 ended with my moving out on my own. I&apos;m still processing this turn of events. There is a lot of grief and loss here that I will need to work through.&lt;/p&gt;
&lt;p&gt;I wrote a poem titled &lt;a href=&quot;/blog/between-hope&quot;&gt;Between Home&lt;/a&gt; as I was processing my feelings and emotions.&lt;/p&gt;
&lt;h2&gt;Looking Forward&lt;/h2&gt;
&lt;p&gt;At the end of 2021, I was hopeful to continue podcasting and participating in other events. I plan to continue to do that, and have already started by recording an episode of PodRocket. I also hope to dive more into music, as I enjoy singing and haven&apos;t really done anything with a group since before the pandemic. Finally, I plan to find peace and healing, as this past year&apos;s events have really taken their toll on me and it&apos;s time to rest.&lt;/p&gt;
&lt;p&gt;Part of why this is being written midway through January is that 2023 has started with a case of COVID that knocked me out for a couple weeks, so hopefully there is some time for that rest in the near future.&lt;/p&gt;
&lt;p&gt;Here&apos;s to a healthful 2023.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/willamette-river.jpeg&quot; alt=&quot;Photo looking west across the Willamette River at downtown Portland, Oregon&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>What Could I Say?</title><link>https://lindsaykwardell.com/blog/what-could-i-say/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/what-could-i-say/</guid><pubDate>Wed, 21 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Once, in a dream,&lt;br /&gt;
I stood before my younger self.&lt;br /&gt;
She looked up at me,&lt;br /&gt;
unrecognizing,&lt;br /&gt;
curious,&lt;br /&gt;
wondering.&lt;/p&gt;
&lt;p&gt;I looked down at her,&lt;br /&gt;
and saw her fears,&lt;br /&gt;
her worries,&lt;br /&gt;
her questions.&lt;/p&gt;
&lt;p&gt;What could I say to her?&lt;br /&gt;
What could I tell her&lt;br /&gt;
of the life she would lead,&lt;br /&gt;
of the people she would know?&lt;/p&gt;
&lt;p&gt;That within a year,&lt;br /&gt;
an entire school would mock her,&lt;br /&gt;
and she would feel alone&lt;br /&gt;
among her peers?&lt;/p&gt;
&lt;p&gt;In a handful more,&lt;br /&gt;
she would find friendships,&lt;br /&gt;
all while hiding &lt;br /&gt;
who she truly was?&lt;/p&gt;
&lt;p&gt;&quot;Don&apos;t do that,&quot;&lt;br /&gt;
her mom would say,&lt;br /&gt;
&quot;they&apos;ll call you a girl.&quot;&lt;/p&gt;
&lt;p&gt;Pretending to wear a skirt&lt;br /&gt;
while getting dressed&lt;br /&gt;
so nobody would know?&lt;/p&gt;
&lt;p&gt;Walking the halls of high school,&lt;br /&gt;
noticing the other girls,&lt;br /&gt;
and wishing only &lt;br /&gt;
to be one of them?&lt;/p&gt;
&lt;p&gt;What could I say to her&lt;br /&gt;
that would even make sense?&lt;br /&gt;
Her body changing in all the wrong ways,&lt;br /&gt;
living mostly in her mind?&lt;/p&gt;
&lt;p&gt;And then whisked away&lt;br /&gt;
to unknown lands,&lt;br /&gt;
called to serve at the whims&lt;br /&gt;
of men who didn&apos;t know her.&lt;/p&gt;
&lt;p&gt;&quot;Don&apos;t do that,&quot;&lt;br /&gt;
they would say,&lt;br /&gt;
&quot;focus on your mission.&quot;&lt;/p&gt;
&lt;p&gt;While all she did&lt;br /&gt;
was write her feelings&lt;br /&gt;
as if they were fiction.&lt;/p&gt;
&lt;p&gt;Trying desperately to help,&lt;br /&gt;
sharing a message of love,&lt;br /&gt;
using flawed tools&lt;br /&gt;
to help flawed people.&lt;/p&gt;
&lt;p&gt;Standing by when another said,&lt;br /&gt;
&quot;You know being gay&apos;s a sin, right?&quot;&lt;br /&gt;
And the three men nodded&lt;br /&gt;
their heads in shame.&lt;/p&gt;
&lt;p&gt;Standing by when young men&lt;br /&gt;
abused authority over others&lt;br /&gt;
to shout, belittle, and burn,&lt;br /&gt;
all in the service of heaven.&lt;/p&gt;
&lt;p&gt;Standing by, as a witness,&lt;br /&gt;
when a woman was hit by a car,&lt;br /&gt;
because she had been told&lt;br /&gt;
not to get involved.&lt;/p&gt;
&lt;p&gt;What could I say to her then,&lt;br /&gt;
wearing a suit and tie,&lt;br /&gt;
nametag emblazoned on her pocket,&lt;br /&gt;
that could truly ease her guilt?&lt;/p&gt;
&lt;p&gt;But soon enough she would&lt;br /&gt;
return home, only to begin again.&lt;br /&gt;
Home wasn&apos;t the same any more;&lt;br /&gt;
how could anything be?&lt;/p&gt;
&lt;p&gt;A whirlwind of activity would follow.&lt;br /&gt;
Marriage, job, child to love.&lt;br /&gt;
Friendships and hobbies cast aside&lt;br /&gt;
to serve this new family.&lt;/p&gt;
&lt;p&gt;And then, the Discovery.&lt;br /&gt;
Writing a note to her wife,&lt;br /&gt;
&quot;I think I&apos;m trans,&lt;br /&gt;
I&apos;m really scared.&quot;&lt;/p&gt;
&lt;p&gt;Who was she?&lt;br /&gt;
What truly was the world?&lt;br /&gt;
Faith and understanding gone&lt;br /&gt;
in the blink of an eye.&lt;/p&gt;
&lt;p&gt;What could I say to help&lt;br /&gt;
in that moment of utter darkness?&lt;br /&gt;
What words could I share to ease&lt;br /&gt;
the pain of relearning the world?&lt;/p&gt;
&lt;p&gt;Years would pass from then,&lt;br /&gt;
slowly becoming who she always was.&lt;br /&gt;
Feeling joy in herself&lt;br /&gt;
and in her life.&lt;/p&gt;
&lt;p&gt;&quot;We should find other partners.&quot;&lt;br /&gt;
World shattered again.&lt;br /&gt;
The deepest hell coming true;&lt;br /&gt;
her wife did not want her as she is.&lt;/p&gt;
&lt;p&gt;New grief, new pain,&lt;br /&gt;
an entire world unraveling&lt;br /&gt;
in anguish and tears.&lt;br /&gt;
But at least she had learned to cry.&lt;/p&gt;
&lt;p&gt;I stand before her,&lt;br /&gt;
from all those years ago,&lt;br /&gt;
without the pain of experience&lt;br /&gt;
to teach her.&lt;/p&gt;
&lt;p&gt;What could I say?&lt;br /&gt;
She looked up with eager eyes,&lt;br /&gt;
unsure who she was even looking at,&lt;br /&gt;
awaiting my next words.&lt;/p&gt;
&lt;p&gt;I looked down at her,&lt;br /&gt;
the child who did not know&lt;br /&gt;
what was to come,&lt;br /&gt;
and would not understand.&lt;/p&gt;
&lt;p&gt;&quot;I love you.&quot;&lt;br /&gt;
I smiled.&lt;br /&gt;
So did she.&lt;br /&gt;
What else could I say?&lt;/p&gt;
</content:encoded></item><item><title>I Am Not Dead</title><link>https://lindsaykwardell.com/blog/i-am-not-dead/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/i-am-not-dead/</guid><pubDate>Wed, 30 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I am not dead.&lt;br /&gt;
I did not die.&lt;br /&gt;
I became myself.&lt;br /&gt;
I will not apologize for it.&lt;/p&gt;
</content:encoded></item><item><title>Integrating Mastodon with Astro</title><link>https://lindsaykwardell.com/blog/integrate-mastodon-with-astro/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/integrate-mastodon-with-astro/</guid><pubDate>Tue, 22 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In case you have missed it, Twitter is going through a series of upheavals. This has led to a large number of folks leaving the platform for alternatives. I&apos;ve been interested in &lt;a href=&quot;https://joinmastodon.org/&quot;&gt;Mastodon&lt;/a&gt; as a potential alternative to Twitter for some time, especially since it&apos;s open source, but I was never able to make the jump. Now that it feels everyone is trying out alternative platforms, the number of folks I know on Mastodon has grown significantly, and I&apos;ve been able to replicate a lot of what I was doing on Twitter.&lt;/p&gt;
&lt;p&gt;Before I dive in too far, let&apos;s talk about microblogging and federation as Mastodon describes it. For microblogging, the &lt;a href=&quot;https://docs.joinmastodon.org/&quot;&gt;Mastodon docs&lt;/a&gt; say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Similar to how blogging is the act of publishing updates to a website, microblogging is the act of publishing small updates to a stream of updates on your profile. You can publish text posts and optionally attach media such as pictures, audio, video, or polls. Mastodon lets you follow friends and discover new ones.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For me, a microblog is a way to share what&apos;s going on or what I&apos;m interested in with people in a less formal, more conversational way. I can talk about life, current events, things my kid says, or just share things that I found that interest me. Twitter has been my microblogging platform for the past few years, while I write my more longform blog posts here on my site.&lt;/p&gt;
&lt;p&gt;How does Mastodon define federation?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Federation is a form of decentralization. Instead of a single central service that all people use, there are multiple services, that any number of people can use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Twitter is very centralized; there&apos;s only one &quot;instance&quot;, and you can&apos;t communicate from Twitter to other platforms. Federated platforms, on the other hand, allow for multiple providers to communicate together in a standardized way. The easiest comparison is email, which is another federated system. Different email providers establish rules and filter content as they want, and allow you to interact with other email providers beyond what they host. Mastodon is similar, in that each instance has its own moderators and rules, and allows for interoperation between other Mastodon instances.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note before we keep going: This post is not intended as a pitch for switching to Mastodon. I know it&apos;s not for everyone. But I&apos;m really liking it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the really neat things about Mastodon is that you can host your own instance, and have it federate with the rest of Mastodon. This allows you to control your own data, and host on your own site. This sounds really cool! It&apos;d be neat to host my own social media account under a URL that I own, it seems like an easy way to verify myself. However, I switched to using a static site (specifically using Astro) specifically because I didn&apos;t want to pay monthly for a server. It didn&apos;t make sense to start paying just to have an account on social media, so I set the idea aside.&lt;/p&gt;
&lt;h2&gt;Integrating to the Fediverse&lt;/h2&gt;
&lt;p&gt;Later, I read an article by &lt;a href=&quot;https://mastodon.online/@maartenballiauw&quot;&gt;Maarten Balliauw&lt;/a&gt; titled &quot;&lt;a href=&quot;https://blog.maartenballiauw.be/post/2022/11/05/mastodon-own-donain-without-hosting-server.html&quot;&gt;Mastodon on your own domain without a server&lt;/a&gt;&quot;, in which he described using the &lt;a href=&quot;https://webfinger.net/&quot;&gt;WebFinger&lt;/a&gt; spec to inform Mastodon of your account. I highly recommend reading the whole article, but in short, Mastodon is looking for a resource at &lt;code&gt;/.well-known/webfinger&lt;/code&gt; that returns JSON in a specific structure. Mastodon is passing a query parameter to specify the account, but if you&apos;re hosting your own site, you can ignore the parameter and just return a JSON structure for your account. This sounded interesting, and I wanted to give it a shot!&lt;/p&gt;
&lt;p&gt;My site is built on Astro, and is hosted with Netlify. To meet the requirement for the endpoint, I created an endpoint at &lt;code&gt;/.well-known/webfinger.json&lt;/code&gt;, which just includes the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function get() {
  return {
    body: JSON.stringify({
      subject: &apos;acct:lindsaykwardell@mastodon.social&apos;,
      aliases: [
        &apos;https://mastodon.social/@lindsaykwardell&apos;,
        &apos;https://mastodon.social/users/lindsaykwardell&apos;,
      ],
      links: [
        {
          rel: &apos;http://webfinger.net/rel/profile-page&apos;,
          type: &apos;text/html&apos;,
          href: &apos;https://mastodon.social/@lindsaykwardell&apos;,
        },
        {
          rel: &apos;self&apos;,
          type: &apos;application/activity+json&apos;,
          href: &apos;https://mastodon.social/users/lindsaykwardell&apos;,
        },
        {
          rel: &apos;http://ostatus.org/schema/1.0/subscribe&apos;,
          template: &apos;https://mastodon.social/authorize_interaction?uri={uri}&apos;,
        },
      ],
    }),
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in my Netlify config (&lt;code&gt;netlify.toml&lt;/code&gt;), I added a redirect:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[[redirects]]
  from = &quot;/.well-known/webfinger&quot;
  to = &quot;/.well-known/webfinger.json&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, searching for &lt;code&gt;@lindsay@lindsaykwardell.com&lt;/code&gt; properly returns my account on mastodon.social!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mastodon-integration.png&quot; alt=&quot;Screenshot of Mastodon search with my custom domain as the query and my account on mastodon.social as the result&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Awesome! Now people can look me up by my own URL, and I don&apos;t have to host a Mastodon instance to do it! However, this communication is only one way; I can tell the Fediverse who I am, but I don&apos;t have a page on my site with any content. How can we fix that?&lt;/p&gt;
&lt;h2&gt;Bringing the Fediverse Home&lt;/h2&gt;
&lt;p&gt;Because Mastodon is built to interoperate with other systems, it has a powerful, public API that can be used to fetch data about users and their posts. In addition, it offers multiple ways to subscribe to any user&apos;s posts, including an RSS feed. You can subscribe to anyone on Mastodon via RSS by appending &lt;code&gt;.rss&lt;/code&gt; to their username (feel free to follow me at https://mastodon.social/@lindsaykwardell.rss).&lt;/p&gt;
&lt;p&gt;I have a custom username that can be found on Mastodon, but what I was missing was a page to actually see my content on my site. On Mastodon, the username &lt;code&gt;@lindsaykwardell@mastodon.social&lt;/code&gt; can be found at mastodon.social/@lindsaykwardell. However, lindsaykwardell.com/@lindsay didn&apos;t exist, so I decided to fix that!&lt;/p&gt;
&lt;p&gt;Again, using Astro, I created a page (&lt;code&gt;/@lindsay.astro&lt;/code&gt;) that would fetch the content of my RSS feed and then render it. I created a separate template for my site that would also fetch my Mastodon profile and banner images, description, follower counts, and other details, and then rendered it out using static HTML.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import MicroblogPost from &quot;../layouts/MicroblogPost.astro&quot;;
import MicroPost from &quot;../components/MicroPost.astro&quot;;
import Parser from &apos;rss-parser&apos;;

const feedUrl = &quot;https://mastodon.social/@lindsaykwardell.rss&quot;
const parser = new Parser({
  customFields: {
    item: [ [&apos;media:content&apos;, &apos;attachments&apos;, { keepArray: true} ]]
  }
})

const feed = await parser.parseURL(feedUrl);

const content = {
  title: feed.title,
  snippet: &quot;Public posts from @lindsay@lindsaykwardell.com&quot;,
  slug: &quot;/@lindsay&quot;
}
---
&amp;lt;MicroblogPost content={content}&amp;gt;
  {feed.items.map(item =&amp;gt; (
    &amp;lt;MicroPost 
      avatar={feed.image.url}
      displayName={feed.title}
      content={item.content}
      createdAt={item.pubDate}
      url={item.link}
      attachments={item.attachments?.map(attachment =&amp;gt; attachment[&apos;$&apos;])}
    /&amp;gt;
  ))}
&amp;lt;/MicroblogPost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this file, I&apos;m importing the layout (&lt;code&gt;MicroblogPost&lt;/code&gt;) and a component (&lt;code&gt;MicroPost&lt;/code&gt;), as well as &lt;a href=&quot;https://www.npmjs.com/package/rss-parser&quot;&gt;rss-parser&lt;/a&gt;. I then created a parser, and added the custom field for media content (such as images). I then fetch the feed content, parse it, and pass it into the site template and components. With that, I can now see my Mastodon content within my own site!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mastodon-profile-page.png&quot; alt=&quot;The landing page for my Mastodon microblog content on my own website&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I then took it a step further, and created an individual page for each post. On sites like Twitter and Mastodon, you can link directly to a post. It made sense that if I was trying to recreate the interface of a microblog on my own site, I would want to do the same.&lt;/p&gt;
&lt;p&gt;This required using &lt;a href=&quot;https://docs.astro.build/en/core-concepts/routing/#server-ssr-mode&quot;&gt;Astro&apos;s dynamic routing&lt;/a&gt; and enabling &lt;a href=&quot;https://docs.astro.build/en/guides/server-side-rendering/&quot;&gt;SSR mode&lt;/a&gt;, but the result is that any of my Mastodon posts can be viwed on my personal site! Here&apos;s the code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import MicroblogPost from &quot;../../layouts/MicroblogPost.astro&quot;;
import MicroPost from &quot;../../components/MicroPost.astro&quot;;

const { id } = Astro.params;

const data = await fetch(`https://mastodon.social/api/v1/statuses/${id}`).then(res =&amp;gt; res.json())

const name = data.account.display_name
const post = data.content.replace(/&amp;lt;[^&amp;gt;]*&amp;gt;?/gm, &apos;&apos;);
let snippet = post.substring(0,30);

if (snippet.length === 30) {
  snippet += &quot;...&quot;
}

const card = data.card;

const content = {
  title: name + &quot;: &quot; + snippet,
  snippet: post,
  image: data.media_attachments[0]?.url,
  slug: `/@lindsay/${id}`,
}
---
&amp;lt;MicroblogPost content={content}&amp;gt;
  &amp;lt;MicroPost
    avatar={data.account.avatar}
    url={data.url}
    username={data.account.username}
    userPage={data.account.url}
    createdAt={data.created_at}
    displayName={data.account.display_name}
    content={data.content}
    attachments={data.media_attachments}
    card={card}
    postStats={{
      repliesCount: data.replies_count,
      reblogsCount: data.reblogs_count,
      favouritesCount: data.favourites_count,
    }}
  /&amp;gt;
&amp;lt;/MicroblogPost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rather than fetching from the RSS feed, I use Mastodon&apos;s public API to fetch via ID. I then pass the data for the post into the &lt;code&gt;MicroPost&lt;/code&gt; component, including a bit more metadata than the RSS feed gives us. With that, we can now load individual Mastodon posts directly into the site! Go to &lt;a href=&quot;/@lindsay/109248202243508025&quot;&gt;this post about my ViteConf talk&lt;/a&gt; to see!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mastodon-post.png&quot; alt=&quot;A post about my ViteConf talk on using Elm in Vite, displayed on my website&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One thing to note about using the API in this way, because we are just fetching data by ID we could load a post that I didn&apos;t write. I&apos;m not too worried about it, as the correct user&apos;s name and profile image will be displayed in the post contents, but it is a consideration to keep in mind.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&apos;m sure there is more that can be done to integrate my site with the Fediverse, but this already feels really nice. Future changes could lead to better integrations with Mastodon, such as following or favoriting things from my site rather than having to go to Mastodon, but it&apos;s not a huge issue at the moment.&lt;/p&gt;
&lt;p&gt;While Twitter may be going through some major struggles, this feels like a good way to keep up interaction with the dev community and integrate it more with my own site in a way that Twitter never allowed. And that, at least, is worth it.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in how this turned out, check out &lt;a href=&quot;https://lindsaykwardell.com/@lindsay&quot;&gt;lindsaykwardell.com/@lindsay&lt;/a&gt;!&lt;/p&gt;
</content:encoded></item><item><title>Between Hope</title><link>https://lindsaykwardell.com/blog/between-hope/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/between-hope/</guid><pubDate>Wed, 21 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I look to my past, &lt;br /&gt;
I see golden times &lt;br /&gt;
with those I loved &lt;br /&gt;
and who loved me.&lt;/p&gt;
&lt;p&gt;I hear the sounds of laughter,  &lt;br /&gt;
feel the gentle touch on my face, &lt;br /&gt;
smell the scents of nature &lt;br /&gt;
as they spread into the open air.&lt;/p&gt;
&lt;p&gt;When I look to the future, &lt;br /&gt;
I see golden horizons, &lt;br /&gt;
star-filled skies &lt;br /&gt;
with the moon high above.&lt;/p&gt;
&lt;p&gt;I hear the call of adventure, &lt;br /&gt;
of unknown places, &lt;br /&gt;
of new faces and new friends &lt;br /&gt;
reaching out in a chorus of wonder.&lt;/p&gt;
&lt;p&gt;For now, &lt;br /&gt;
I stand between hope, &lt;br /&gt;
between joy, &lt;br /&gt;
between lives.&lt;/p&gt;
&lt;p&gt;I stand, and allow myself to stand, &lt;br /&gt;
feel what I am feeling, &lt;br /&gt;
allow the chapter to end &lt;br /&gt;
so another can begin.&lt;/p&gt;
&lt;p&gt;I stand at the edge of the new, &lt;br /&gt;
the twilight of the old, &lt;br /&gt;
the start of an adventure &lt;br /&gt;
and the end of a journey.&lt;/p&gt;
&lt;p&gt;Yesterday can rest.&lt;/p&gt;
&lt;p&gt;Tomorrow can wait.&lt;/p&gt;
&lt;p&gt;Today, &lt;br /&gt;
I will mourn.&lt;/p&gt;
</content:encoded></item><item><title>Utilizing Native Dialog in Elm</title><link>https://lindsaykwardell.com/blog/native-dialog-in-elm/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/native-dialog-in-elm/</guid><pubDate>Mon, 09 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A common design pattern when building client-side applications is the need to display a modal or dialog on top of the main page content. It may be a login form, a &quot;What&apos;s New&quot; style notification, a feedback input, or any number of other possible designs. Modals are incredibly tricky to program by hand in a way that provides a friendly and accessible experience. The W3C provides &lt;a href=&quot;https://www.w3.org/TR/wai-aria-practices/#dialog_modal&quot;&gt;a list of reccomendations and guidelines for creating dialog interfaces&lt;/a&gt;, which includes a series of recommendations and guidelines on how to implement a dialog.&lt;/p&gt;
&lt;p&gt;From the above documentation, here&apos;s the definition of a dialog:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A dialog is a window overlaid on either the primary window or another dialog window. Windows under a modal dialog are inert. That is, users cannot interact with content outside an active dialog window. Inert content outside an active dialog is typically visually obscured or dimmed so it is difficult to discern, and in some implementations, attempts to interact with the inert content cause the dialog to close.&lt;/p&gt;
&lt;p&gt;Like non-modal dialogs, modal dialogs contain their tab sequence. That is, Tab and Shift + Tab do not move focus outside the dialog. However, unlike most non-modal dialogs, modal dialogs do not provide means for moving keyboard focus outside the dialog window without closing the dialog.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&apos;s fairly simple to create the basic outline of a dialog interface: render a box or card in the center of the screen, provide a fade-out overlay for the background content (ideally one that can be clicked to close the box), and prevent global scrolling. However, in order to meet the requirements as outlined by the W3C, we also need to prevent tabbing in the background application, set focus on a button that can close the dialog, and restore focus to the element that triggered the dialog after it closes. Suddenly things feel a lot more complicated!&lt;/p&gt;
&lt;p&gt;Thankfully, there is a browser native way to implement dialogs that covers (most of) the cases above, and provides straightforward ways to handle the rest: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot;&gt;the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element!&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;What is &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;From the MDN docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; HTML element represents a dialog box or other interactive component, such as a dismissible alert, inspector, or subwindow.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is a browser-based way to create dialog interfaces. It&apos;s a newer HTML element, which only recently got support from all major browsers (Safari 15.4 was the last one, released in March 2022). According to caniuse, it&apos;s now supported across the board (except in IE11, which is expected):&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://caniuse.com/dialog&quot;&gt;&lt;img src=&quot;/blog/dialog-caniuse.png&quot; alt=&quot;Screenshot of caniuse information on the dialog element.&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In short, a dialog element is just like any other HTML element, and can be written like this (try it out!):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Dialog example&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Dialogs are awesome!&amp;lt;/h1&amp;gt;
    &amp;lt;button id=&quot;open-dialog&quot;&amp;gt;Open dialog&amp;lt;/button&amp;gt;
    &amp;lt;dialog&amp;gt;
      &amp;lt;h2&amp;gt;My dialog&amp;lt;/h2&amp;gt;
      &amp;lt;button&amp;gt;Close&amp;lt;/button&amp;gt;
    &amp;lt;/dialog&amp;gt;
    &amp;lt;script src=&quot;script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;document.getElementById(&apos;open-dialog&apos;).addEventListener(&apos;click&apos;, () =&amp;gt; {
  document.querySelector(&apos;dialog&apos;).showModal();
});

document.querySelector(&apos;dialog &amp;gt; button&apos;).addEventListener(&apos;click&apos;, () =&amp;gt; {
  document.querySelector(&apos;dialog&apos;).close();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some cool things to note here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You get a basic style for the dialog box and the background (which are all targetable by CSS and can be tweaked as desired).&lt;/li&gt;
&lt;li&gt;Opening the dialog sets focus to the close button.&lt;/li&gt;
&lt;li&gt;Closing the dialog sets focus back to the open button.&lt;/li&gt;
&lt;li&gt;Tabbing is diabled outside of the dialog when it&apos;s open.&lt;/li&gt;
&lt;li&gt;You can also press the escape key to close the dialog.&lt;/li&gt;
&lt;li&gt;If you&apos;re using a screen reader, such as VoiceOver, it should announce that focus has shifted between the open and close buttons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&apos;s a lot of free benefits for using the built-in element! Of course, some of the functionality here is reliant on Javascript. In this case, we set two event listeners, one for clicking the open button and another for clicking the close button. In order to properly toggle the dialog, we need to call a method on the dialog element itself. To open the dialog, we call &lt;code&gt;showModal&lt;/code&gt;, and to close it we call &lt;code&gt;close&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The &lt;code&gt;open&lt;/code&gt; Attribute&lt;/h3&gt;
&lt;p&gt;But wait! The MDN docs list a different way to display the dialog as open: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#attr-open&quot;&gt;the &lt;code&gt;open&lt;/code&gt; attribute&lt;/a&gt;!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Indicates that the dialog is active and can be interacted with. When the open attribute is not set, the dialog shouldn&apos;t be shown to the user. It is recommended to use the .show() or .showModal() methods to render dialogs, rather than the open attribute.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As Elm developers, it might be tempting to go down this path and set the attribute. However, doing this does &lt;em&gt;not&lt;/em&gt; provide the additional benefits of updating focus, blocking the background from input, etc. To get the full benefit of the dialog element, we need to call &lt;code&gt;showModal&lt;/code&gt;. Note that &lt;code&gt;show&lt;/code&gt; is also an option, but it does basically the same thing as &lt;code&gt;open&lt;/code&gt;, and is not recommended.&lt;/p&gt;
&lt;p&gt;We can, however, check whether the dialog is visible at the moment by looking at &lt;code&gt;open&lt;/code&gt;. When we call &lt;code&gt;showModal&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, which means that rather than have distinct calls to open and the close the dialog, we could choose to have a simple toggle function, which internally checks whether the dialog is open.&lt;/p&gt;
&lt;h2&gt;Implementing &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; in Elm&lt;/h2&gt;
&lt;p&gt;Now that we understand what the dialog element provides us, and what is required to make it useful, let&apos;s start working with it in Elm. Elm provides a number of HTML elements in its &lt;code&gt;Html&lt;/code&gt; module, but since the dialog element is newer, we first need to define it ourselves.&lt;/p&gt;
&lt;h3&gt;Rendering the dialog&lt;/h3&gt;
&lt;p&gt;In order to define a custom element, we need to import &lt;code&gt;Html&lt;/code&gt;, then we can use &lt;code&gt;Html.node&lt;/code&gt; to generate a custom element. As with any HTML element in Elm, it takes two arguments (a list of attributes and a list of HTML), but it also takes a string to use as the custom element&apos;s tag. Here&apos;s a basic example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dialog : List (Html.Attribute msg) -&amp;gt; List (Html msg) -&amp;gt; Html msg
dialog attr content =
    Html.node &quot;dialog&quot; attr content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This provides a basic interface for a dialog element. However, we&apos;re going to need to trigger it&apos;s &lt;code&gt;showModal&lt;/code&gt; and &lt;code&gt;close&lt;/code&gt; methods, so it&apos;s probably helpful to require an ID. Let&apos;s just add the ID as part of the function signature:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dialog : String -&amp;gt; List (Html.Attribute msg) -&amp;gt; List (Html msg) -&amp;gt; Html msg
dialog elementId attr content =
    Html.node &quot;dialog&quot; ((id elementId) :: attr) content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great! Now the ID will be set by the first argument, and the rest of the HTML element signature is the same that other elements use. At this point, you can start rendering the dialog on the page, but it won&apos;t appear (unless you set the &lt;code&gt;open&lt;/code&gt; attribute). We need a way to trigger the methods provided in Javascript.&lt;/p&gt;
&lt;h3&gt;Triggering the dialog&lt;/h3&gt;
&lt;p&gt;Elm provides &lt;a href=&quot;https://guide.elm-lang.org/interop/ports.html&quot;&gt;ports&lt;/a&gt; as the go-to solution for interacting between Javascript and Elm. We need to utilize a port to call out to Javascript in order to trigger the dialog to appear. Let&apos;s define our port below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;port toggleDialog : String -&amp;gt; Cmd msg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When our Elm application is compiled, &lt;code&gt;toggleDialog&lt;/code&gt; will be available to subscribe to from Javascript. In this case, it provides a string (the dialog&apos;s ID) that we can use to select our element and perform the required action on it. Here&apos;s an example of our Javascript code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.ports.toggleDialog.subscribe(id =&amp;gt; {
  const dialog = document.querySelector(`#${id}`)

  if (dialog.open) {
      dialog.close.();
  } else {
      dialog.showModal.();
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a pretty simple snippet of Javascript. We take the provided ID, find the element, and check whether it&apos;s open. If it is, then we close it, otherwise we open it. If you want, you could add an additional port for Javascript to inform Elm of the current status of the dialog, but it&apos;s not required.&lt;/p&gt;
&lt;p&gt;Here&apos;s an &lt;a href=&quot;https://github.com/lindsaykwardell/elm-dialog-example&quot;&gt;example repository&lt;/a&gt; showcasing the dialog element, and here&apos;s a &lt;a href=&quot;https://elm-dialog-example.netlify.app/&quot;&gt;live demo&lt;/a&gt; of that repository. Check it out!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The dialog element provides a lot of benefits for developers needing to utilize this UI pattern. However, it&apos;s important to note that just because it provides a large number of benefits and accessibility support built-in, it still requires planning and testing to properly implement a dialog that is usable by all the visitors to your site. Make sure that you and your team are performing regular a11y testing, so you aren&apos;t leaving any of your users out of the loop with what your site is showing them!&lt;/p&gt;
&lt;p&gt;Also, keep in mind that it is a fairly new element, and you may have users that are not supported yet. If you still have a large number of users that don&apos;t have access to the dialog element, consider using an existing alternative or providing an alternative userflow.&lt;/p&gt;
&lt;p&gt;That said, it&apos;s very exciting to see what the future of HTML looks like, and I&apos;m looking forward to more elements like this that are built-in and provide a lot of value to both users and developers. Try it out, and see if it works for you today!&lt;/p&gt;
</content:encoded></item><item><title>Shipping Side Projects</title><link>https://lindsaykwardell.com/blog/shipping-side-projects/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/shipping-side-projects/</guid><pubDate>Thu, 24 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s late at night. Everyone else has gone to bed, but you&apos;re up just a bit later. Your fingers are flying across the keys, your brain fighting against exhaustion but flowing with great ideas. In front of you, your laptop&apos;s fans hum gently as your side project&apos;s local environment runs in the terminal. Constantly switching between your IDE and the browser, you watch as your ideas come to life before you. It&apos;s truly a magical moment. Just one more feature, and it&apos;ll be ready to ship...&lt;/p&gt;
&lt;p&gt;A week or two later, your app is still half finished, but the drive you felt that night has faded. New ideas have started to form in your mind, unrelated to what you were working on previously. You spun up a new Git repo, and you&apos;re almost ready to buy a new domain name. It&apos;ll fit nicely next to dozen or so others you&apos;ve already purchased. One day, you&apos;ll ship all of them. At least, that&apos;s the plan.&lt;/p&gt;
&lt;p&gt;Side projects get a lot of attention from developers. It&apos;s common to visit someone&apos;s website or Github page and find multiple projects on display, all in various stages of completion or function. On Twitter, I regularly see developers talking about their side projects, often lamenting the fact that they have so many and feeling guilting that they aren&apos;t being released or finished. It&apos;s especially common to joke about the number of domains that we all have purchased but never used. At some point, I think we as developers get depressed and frustrated because of all the things we started with the best of intentions, but haven&apos;t completed.&lt;/p&gt;
&lt;p&gt;Rather than stress ourselves out about the amount of work waiting for us when we get off work, I think it&apos;s useful to put side projects into context: what they are, what they are for, and who benefits from them.&lt;/p&gt;
&lt;h2&gt;The Purpose of Side Projects&lt;/h2&gt;
&lt;p&gt;There are lots of reasons for starting a new side project. A new framework could have been released that has some new ideas, or you want to try a new language or library. You could be trying out some new techniques, or just want to explore an idea or two that you created. Before you start a side project, it&apos;s important to understand why you&apos;re doing it. What do you want to get out of doing this project?&lt;/p&gt;
&lt;p&gt;When I was starting to learn React, I needed a project that I felt was interesting. Rather than build a todo list or something else, I came up with a basic turn-based strategy game. It was a lot of fun, and let me explore a lot of advanced concepts in React. After many weeks of exploring this idea, the game was in a working state, but had a long way to go to be &quot;complete&quot;.&lt;/p&gt;
&lt;p&gt;Rather than continuing to work on it, however, I set it aside. Why? Because my goal (learing React) was complete, and I was ready to move on to learning somethign else. While it would have been cool to add all the features I had thought up, continuing to work on one project wasn&apos;t worth it. I&apos;ve since come back to this concept multiple times in different frameworks and languages, the latest version being in Elm. You can &lt;a href=&quot;https://juralen.lindsaykwardell.com&quot;&gt;play it here&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;You Are the Stakeholder&lt;/h2&gt;
&lt;p&gt;When working on a professional project, either freelancing or as an employee, there are a number of stakeholders that are invested in the work you&apos;re doing and the end result of that work. This can lead to working on things that you aren&apos;t interested in, or are outright opposed to building.&lt;/p&gt;
&lt;p&gt;For side projects, you are the stakeholder! And often, you&apos;re also the only stakeholder, which means that you get to decide what to build, how to build it, and, most importantly, when to work on it. Too often, we push ourselves to work on projects just because we started them, or out of feelings of guilt that we can&apos;t complete something. It is definitely useful to have completed, polished examples of your work for a portfolio, but not all side projects belong in that category.&lt;/p&gt;
&lt;p&gt;At work, stakeholders help establish which projects have priority. As the stakeholder for your side projects, don&apos;t feel guilty about moving onto something else, or deprioritizing certain projects. There&apos;s no need to feel bad about side projects, they&apos;re fun!&lt;/p&gt;
&lt;p&gt;When I feel like I&apos;m not interested in a side project any more, rather than say I&apos;ve abandoned it, I ship it. It may not be &quot;feature-complete&quot; to everything that I planned, but neither is any production application.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;Friendly reminder, nobody is forcing you to work on side projects. If you don&apos;t want to work on it any more, then congrats! It just reached MVP. Go do something you enjoy.&amp;lt;/p&amp;gt;— Lindsay Wardell 🏳️‍⚧️ (@lindsaykwardell) &amp;lt;a href=&quot;https://twitter.com/lindsaykwardell/status/1494702474896105474?ref_src=twsrc%5Etfw&quot;&amp;gt;February 18, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;h2&gt;Scoping Side Projects&lt;/h2&gt;
&lt;p&gt;One of the big draws to technology is this thought that we can build a startup as a side project, and turn it into our main job. While there are a number of successes with this approach, this doesn&apos;t apply to every side project! Not every todo app needs to become Todoist. Not every budgeting app needs to turn into You Need A Budget. Just like at work, it&apos;s important to scope our side projects, so that we understand exactly what a given project should and should not become.&lt;/p&gt;
&lt;p&gt;When I&apos;m working on a side project, I try to put them into one of three categories - learning, maintained, and &quot;slow cooker&quot;.&lt;/p&gt;
&lt;h3&gt;Learning Projects&lt;/h3&gt;
&lt;p&gt;Learning projects are experiments, with a goal of exploring an idea or learning a new language/framework/library/etc. These may be created to follow along with a tutorial, or just to see if something works in the way that I expect. One of the hallmarks of these projects is that I don&apos;t expect it to last longer than a week or two, and will then be abandoned.&lt;/p&gt;
&lt;p&gt;Besides learning and exploration, these projects allow you to plant seeds of knowledge. Sometimes I will experiment with something, abandon it, then later realize that I need to do the same thing I was experimenting with. Rather than start everything from scratch, I can often copy/paste from one of these learning projects, allowing me to move faster and build on my previous learning.&lt;/p&gt;
&lt;p&gt;Examples of learning projects include todo apps, using a public API such as the Pokemon API or the Star Wars GraphQL API, or spinning up and toying with a new framework.&lt;/p&gt;
&lt;h3&gt;Maintained Projects&lt;/h3&gt;
&lt;p&gt;Maintained projects are created with the goal of becoming a long-running project. Ideally these projects are clean and maintained, and typically small (but not always). When I want to do something to improve or change them, it doesn&apos;t take much to spin up a local environment, even if it&apos;s been awhile. I may point at these projects as examples of things I have done, such as in a portfolio.&lt;/p&gt;
&lt;p&gt;The key identifier for a maintained project is that I expect to be coming back to it repeatedly over a long period of time, but is small enough to be finished in a reasonable amount of time.&lt;/p&gt;
&lt;p&gt;Examples of maintained projects include my blog, small open source projects, and finished sites/applications that are used by folks on a day-to-day basis.&lt;/p&gt;
&lt;h3&gt;Slow Cooker Projects&lt;/h3&gt;
&lt;p&gt;Slow cooker projects are similar to maintained projects, except they may not have a polished, final product in the near future. These projects are the playgrounds of ambition, where the drive to build something new and make a difference somehow. This is where startup-level projects live. It takes time to build something like this, and it&apos;s important to recognize that. Just like using a slow cooker takes hours to make a meal, building a large application, framework, or whatever your idea is will take time. Don&apos;t beat yourself up when something like this doesn&apos;t result in a full-on startup after a few months, or even longer.&lt;/p&gt;
&lt;p&gt;For me, slow cooker projects are incredibly exciting, but also incredibly draining. There&apos;s so much going on in life already, and there&apos;s only so much room for large-scale projects like this. When I&apos;m working on a project like this, I don&apos;t limit my imagination or scope, but I allow myself to come and go from the project as inspiration strikes. If I&apos;m not excited about working on a side project, then I set it aside, and wait for inspiration to come back. My goal with slow cooker projects is to enjoy the process, and hopefully have a finished product that I can be proud of.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Side projects are the first drafts of writing, the tuning session of music, and the experimentation phase of science. They provide us a fast feedback loop, and give us the opportunity to try out new ideas and concepts. They serve a crucial role in becoming a developer, and building up or maintaining our skills.&lt;/p&gt;
&lt;p&gt;The value of side projects is in how much value they give to us, the developers. Next time you&apos;re feeling burdened with all the side projects you&apos;ve never completed, remind yourself that they all served their purpose. The only person you owe to work on side projects is yourself. Don&apos;t feel like working on it? You just reached version 1.0. Congratulations!&lt;/p&gt;
&lt;p&gt;Enjoy your next side project!&lt;/p&gt;
</content:encoded></item><item><title>Utilizing Elm in a Web Worker</title><link>https://lindsaykwardell.com/blog/utilizing-elm-in-a-web-worker/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/utilizing-elm-in-a-web-worker/</guid><pubDate>Thu, 17 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Elm programming language is a great way to model and write a modern web application. By utilizing functional programming and a strong type system, Elm encourages developers to build applications that are more reliable and more easily maintained. But as a compile-to-Javascript language, there is only so much that Elm can offer by default. Any tasks that require large computations in Javascript will, unfortunately, require the same computations in Elm. Such large tasks can block the main thread in browsers, causing visual issues and a non-responsive UI. Obviously this is not what we want for our users, so what can we do?&lt;/p&gt;
&lt;p&gt;Enter &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API&quot;&gt;Web Workers&lt;/a&gt;. From MDN:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Web Workers&lt;/strong&gt; makes it possible to run a script operation in a background thread separate from the main execution thread of a web application. The advantage of this is that laborious processing can be performed in a separate thread, allowing the main (usually the UI) thread to run without being blocked/slowed down.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Web Workers are a way that browser-based applications can move certain tasks out of the main thread, into their own environment. Web Workers have a number of restrictions to them, such as not being able to access the DOM, but they do have the ability to make HTTP requests via &lt;code&gt;fetch&lt;/code&gt; as well as run standard Javascript code. Since Elm is a compile-to-JS language, that means that we can mount an Elm app within the Web Worker as well!&lt;/p&gt;
&lt;p&gt;Let&apos;s explore what it would look like to use Elm inside of a Web Worker. We&apos;ll look at two ways to do it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using vanilla JS, no bundlers or frameworks beyond what Elm provides.&lt;/li&gt;
&lt;li&gt;Incorporating these techniques into Vite, which provides a helpful wrapper around the Web Worker API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Writing our Elm Modules&lt;/h2&gt;
&lt;p&gt;To start, let&apos;s set up a basic setup to work with. In a new folder, run &lt;code&gt;elm init&lt;/code&gt;, which generates our base elm.json and a &lt;code&gt;src&lt;/code&gt; folder. Within &lt;code&gt;src&lt;/code&gt;, create two files: &lt;code&gt;Main.elm&lt;/code&gt; and &lt;code&gt;Worker.elm&lt;/code&gt;. We&apos;ll fill these in shortly. Let&apos;s also create an &lt;code&gt;index.html&lt;/code&gt; at the root of our working direction (we&apos;ll come back to it later).&lt;/p&gt;
&lt;p&gt;First, let&apos;s set up a very basic &lt;code&gt;Main.elm&lt;/code&gt; file. While Web Workers are primarily useful for large tasks, for this example we&apos;re going to keep things simple for our examples. In our main file, we&apos;ll implement a basic counter example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;port module Main exposing (main)

import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)


init : (Int, Cmd msg)
init =
    ( 0, Cmd.none )


type Msg
    = Increment
    | Decrement
    | Set Int


update : Msg -&amp;gt; Int -&amp;gt; ( Int, Cmd Msg )
update msg model =
    case msg of
        Increment -&amp;gt;
            ( model, increment model )

        Decrement -&amp;gt;
            ( model, decrement model )

        Set value -&amp;gt;
            ( value, Cmd.none )


view : Int -&amp;gt; Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text &quot;-&quot; ]
        , div [] [ text (String.fromInt model) ]
        , button [ onClick Increment ] [ text &quot;+&quot; ]
        ]


subscriptions : Int -&amp;gt; Sub Msg
subscriptions _ =
    receiveCount Set


main : Program () Int Msg
main =
    Browser.element { init = \_ -&amp;gt; init, update = update, view = view, subscriptions = subscriptions }


port increment : Int -&amp;gt; Cmd msg


port decrement : Int -&amp;gt; Cmd msg


port receiveCount : (Int -&amp;gt; msg) -&amp;gt; Sub msg


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a fairly straightforward Elm app, but with one key difference: rather than update the state here, we are returning a command to relay the current state to a port. We also have a port to receive a number, which then updates our local state.&lt;/p&gt;
&lt;p&gt;Since we are going to handle this &lt;em&gt;very&lt;/em&gt; complex computation in a Web Worker, let&apos;s now write a basic Elm module to run from within the Worker.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;port module Worker exposing (main)

import Platform


type Msg
    = Increment Int
    | Decrement Int


init : () -&amp;gt; ( (), Cmd msg )
init _ =
    ( (), Cmd.none )


update : Msg -&amp;gt; () -&amp;gt; ( (), Cmd msg )
update msg _ =
    case msg of
        Increment int -&amp;gt;
            ( (), sendCount (int + 1) )

        Decrement int -&amp;gt;
            ( (), sendCount (int - 1) )


subscriptions : () -&amp;gt; Sub Msg
subscriptions _ =
    Sub.batch
        [ increment Increment
        , decrement Decrement
        ]


main : Program () () Msg
main =
    Platform.worker { init = init, update = update, subscriptions = subscriptions }


port increment : (Int -&amp;gt; msg) -&amp;gt; Sub msg


port decrement : (Int -&amp;gt; msg) -&amp;gt; Sub msg


port sendCount : Int -&amp;gt; Cmd msg

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&apos;s going on here? First, we import &lt;code&gt;Platform&lt;/code&gt;, which provides us with the function &lt;a href=&quot;https://package.elm-lang.org/packages/elm/core/latest/Platform#worker&quot;&gt;&lt;code&gt;Platform.worker&lt;/code&gt;&lt;/a&gt;. Most of the time, when writing an Elm app, we&apos;re leaning on &lt;a href=&quot;https://package.elm-lang.org/packages/elm/browser/latest/&quot;&gt;elm/Browser&lt;/a&gt; to create apps that bind to the DOM. But in this case, we don&apos;t have a DOM to bind to, so we utilize Platform to create a basic app that doesn&apos;t do that. &lt;code&gt;worker&lt;/code&gt; takes three inputs: &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;subscriptions&lt;/code&gt; (it&apos;s basically the same as &lt;code&gt;Browser.element&lt;/code&gt;, from our Main.elm example).&lt;/p&gt;
&lt;p&gt;We also create two ports for incrementing and decrementing the input (an incredibly taxing computation for even modern Javascript), and connect those to equivalent &lt;code&gt;Msg&lt;/code&gt; values. Within the update function, we then send the results to &lt;code&gt;sendCount&lt;/code&gt;, which outputs from Elm into the wild west of Javascript for us.&lt;/p&gt;
&lt;p&gt;Conceptually, it looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Main receives a message (&lt;code&gt;Increment&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In Main&apos;s update function, we send the current count to a matching port (&lt;code&gt;increment 0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;This value is sent (via Javascript) from Main to Worker, and connected to the matching port (also &lt;code&gt;increment 0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The Worker sends out the result of its intense calculation (&lt;code&gt;sendCount 1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Main receives the updated value, and updates its model accordingly (&lt;code&gt;receiveCount 1&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&apos;re familiar with The Elm Architecture, this is basically the same thing but with more steps. It&apos;s also important to note that because we are relying on ports to communicate between the Main and Worker apps, this calculation is inherently asynchronous. This is really only ideal for certain workloads, and should probably not be used 100% of the time (especially for small tasks like addition/subtraction).&lt;/p&gt;
&lt;h2&gt;Scaffold index.html&lt;/h2&gt;
&lt;p&gt;Now that we&apos;ve had a look at the Elm code, let&apos;s look at Javascript. Since we are using vanilla JS and not a bundler, we first need to bundle our Elm code. Run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;elm make src/Main.elm --output main.js
elm make src/Worker.elm --output elm-worker.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will output our &lt;code&gt;main.js&lt;/code&gt; and &lt;code&gt;worker.js&lt;/code&gt; files, which we can import into our HTML. Speaking of which let&apos;s do that! Here&apos;s a basic HTML file to start with. All it does is mount our Main app, we&apos;ll get to the Worker in a moment.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Elm Web Workers&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;app&quot;&amp;gt;
      &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;script src=&quot;main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
      const app = Elm.Main.init({
        node: document.getElementById(&apos;app&apos;)
      });
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you open the HTML file in a browser right now, it should properly render the Main app, but the buttons don&apos;t appear to do anything. That&apos;s because rather than updating our model, they are instead sending it to ports. Currently, we aren&apos;t doing anything with our ports, but before we hook them up, let&apos;s add our Web Worker.&lt;/p&gt;
&lt;h2&gt;Adding the Web Worker&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;For this section, I will be referring to &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers&quot;&gt;MDN&apos;s excellent guide to using Web Workers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In order to create a web worker, we need to have an external JS file that can be imported and executed as a web worker. The most basic implementation of a worker can be a simple &lt;code&gt;console.log&lt;/code&gt;. Let&apos;s do that first.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;worker.js&lt;/code&gt; file and put in &lt;code&gt;console.log(&quot;Hello, worker!&quot;)&lt;/code&gt;. Then, in our HTML file, add this code to the top of your script block:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const worker = new Worker(&apos;worker.js&apos;)

const app = Elm.Main.init({
    node: document.getElementById(&apos;app&apos;)
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This instructs the browser to create a worker using the Javascript file that is found at the named location (in our case, &lt;code&gt;worker.js&lt;/code&gt;). If you open your devtools, you should see &quot;Hello, worker!&quot; appear there, generated from &lt;code&gt;worker.js:1&lt;/code&gt;. Great!&lt;/p&gt;
&lt;p&gt;Now let&apos;s add some communication between the worker and main JS files.&lt;/p&gt;
&lt;h3&gt;Sending a message&lt;/h3&gt;
&lt;p&gt;In your HTML file, let&apos;s add another line of code that will enable sending a message to the worker. To send a message from main to the worker, we use &lt;code&gt;worker.postMessage()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const worker = new Worker(&apos;worker.js&apos;)

const app = Elm.Main.init({
    node: document.getElementById(&apos;app&apos;)
});

worker.postMessage(1)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To receive a message in the worker, we set &lt;code&gt;onmessage&lt;/code&gt; (not a variable) to be a function that receives a function. Delete the contents of your &lt;code&gt;worker.js&lt;/code&gt; file and add the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;onmessage = function ({ data }) {
  console.log(data);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As with all Javascript events, there are a number of other values sent to the onmessage function. For the sake of this blog post, we only care about the data key. If you run this script, you should see a &lt;code&gt;1&lt;/code&gt; logged out into the console. Congratulations, we are now able to pass data to the worker! But what about passing it into Elm?&lt;/p&gt;
&lt;p&gt;Web Workers provide a special API for importing scripts into them:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Worker threads have access to a global function, importScripts(), which lets them import scripts. It accepts zero or more URIs as parameters to resources to import.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By using &lt;code&gt;importScripts()&lt;/code&gt;, we can import our Elm worker module, initialize it, and begin to use its ports. Let&apos;s update our &lt;code&gt;worker.js&lt;/code&gt; as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;importScripts(&quot;elm-worker.js&quot;)

const app = Elm.Worker.init();

onmessage = function ({ data }) {
  app.ports.increment.send(data);
};

app.ports.sendCount.subscribe(function(int) {
  console.log(int);
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For those less familiar with Elm, we are initializing our Elm worker without a DOM node (because there are no DOM nodes in the worker). Then, using its ports, when we receive a message from the main thread, we send it to the &lt;code&gt;increment&lt;/code&gt; port. Elm then does its incredibly complicated calculations, and returns (via the &lt;code&gt;sendCount&lt;/code&gt; port) the updated integer (which we log for now). Excellent!&lt;/p&gt;
&lt;p&gt;Before we go too much further, let&apos;s update the main and worker to properly target either the increment or decrement ports. In &lt;code&gt;index.html&lt;/code&gt;, update your script block to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const worker = new Worker(&apos;worker.js&apos;);
const app = Elm.Main.init({
    node: document.getElementById(&apos;app&apos;)
});

app.ports.increment.subscribe(int =&amp;gt; worker.postMessage({
    type: &apos;increment&apos;,
    value: int
}))

app.ports.decrement.subscribe(int =&amp;gt; worker.postMessage({
    type: &apos;decrement&apos;,
    value: int
}))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in our worker, update to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;importScripts(&quot;elm-worker.js&quot;);

const app = Elm.Worker.init();

onmessage = function ({ data }) {
  const { type, value } = data;

  if (type === &quot;increment&quot;) {
    app.ports.increment.send(value);
  }

  if (type === &quot;decrement&quot;) {
    app.ports.decrement.send(value);
  }
};

app.ports.sendCount.subscribe(function (int) {
  console.log(int);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you refresh the page, you can now start clicking the buttons and seeing the results log in the console. Of course, it&apos;s only going to show 1 or -1, so let&apos;s pass data back to the main thread.&lt;/p&gt;
&lt;p&gt;Web Workers have a global &lt;code&gt;postMessage&lt;/code&gt; function that allows us to pass back data. Let&apos;s wrap up this code and send the calculated result to the main thread (and our Main Elm app):&lt;/p&gt;
&lt;p&gt;In worker.js, do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;importScripts(&quot;elm-worker.js&quot;);

const app = Elm.Worker.init();

onmessage = function ({ data }) {
  const { type, value } = data;

  if (type === &quot;increment&quot;) {
    app.ports.increment.send(value);
  }

  if (type === &quot;decrement&quot;) {
    app.ports.decrement.send(value);
  }
};

app.ports.sendCount.subscribe(function (int) {
  console.log(int);
  postMessage(int);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In index.html, update the script block:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const worker = new Worker(&apos;worker.js&apos;);
const app = Elm.Main.init({
    node: document.getElementById(&apos;app&apos;)
});

app.ports.increment.subscribe(int =&amp;gt; worker.postMessage({
    type: &apos;increment&apos;,
    value: int
}))

app.ports.decrement.subscribe(int =&amp;gt; worker.postMessage({
    type: &apos;decrement&apos;,
    value: int
}))

worker.onmessage = function( { data }) {
    app.ports.receiveCount.send(data);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that, we are now passing data! Congratulations! If you need to pass any complex data between the main and worker threads, you will probably need to turn to JSON encoding/decoding. You can also pass an object with a custom message if needed, rather than using multiple ports and relying on Javascript to act as the controller.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lindsaykwardell/elm-vanilla-js-web-worker&quot;&gt;Here&apos;s a repository with the code we&apos;ve been looking at.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Web Workers in Vite&lt;/h2&gt;
&lt;p&gt;Using vanilla HTML and JS is nice, but most of the time at work or in larger projects we&apos;re using some sort of build tooling to have a more streamlined experience. I&apos;m personally a big fan of &lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt;, the frontend tooling solution by the creator of Vue. I maintain &lt;a href=&quot;https://github.com/lindsaykwardell/vite-elm-template&quot;&gt;a Vite template for building Elm applications&lt;/a&gt;, which utilized the excellent Elm plugin for Vite to achieve hot module reload and directly importing our &lt;code&gt;.elm&lt;/code&gt; files into our Javascript.&lt;/p&gt;
&lt;p&gt;As an added benefit for our use case, &lt;a href=&quot;https://vitejs.dev/guide/features.html#web-workers&quot;&gt;Vite provides some abstraction over the Web Worker API&lt;/a&gt; that we explored above. In Vite, when we import a script that we want to use as a web worker, we can append a query parameter that signals to Vite what it is, and then Vite will wrap it in a function that generates the correct worker command.&lt;/p&gt;
&lt;p&gt;Let&apos;s migrate our above code into Vite and see how this works. I&apos;ll be using my template to scaffold a basic app. To do that yourself, run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx degit lindsaykwardell/vite-elm-template vite-elm-web-worker
cd vite-elm-web-worker
npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That will clone the template locally (with no Git history) into the &lt;code&gt;vite-elm-web-worker&lt;/code&gt; folder, enter it, and install the required dependencies. Feel free to rename it to whatever you prefer. Then, delete the contents of the &lt;code&gt;src&lt;/code&gt; folder and replace them with our &lt;code&gt;Main.elm&lt;/code&gt; and &lt;code&gt;Worker.elm&lt;/code&gt; files. At this point, you should have a setup that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/elm-web-worker-vite-1.png&quot; alt=&quot;File tree in VS Code, showing the src folder has two files: Main.elm, and Worker.elm&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, let&apos;s bring over our worker.js and other Javascript. Let&apos;s start by creating a &lt;code&gt;worker.js&lt;/code&gt; file (we&apos;ll come back to it in a moment), and then update our &lt;code&gt;main.js&lt;/code&gt; file to include our worker and port logic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;./style.css&quot;;
import { Elm } from &quot;./src/Main.elm&quot;;
import ElmWorker from &quot;./worker?worker&quot;;

const root = document.querySelector(&quot;#app div&quot;);

const worker = new ElmWorker();
const app = Elm.Main.init({ node: root });

app.ports.increment.subscribe((int) =&amp;gt;
  worker.postMessage({
    type: &quot;increment&quot;,
    value: int,
  })
);

app.ports.decrement.subscribe((int) =&amp;gt;
  worker.postMessage({
    type: &quot;decrement&quot;,
    value: int,
  })
);

worker.onmessage = function ({ data }) {
  app.ports.receiveCount.send(data);
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should look very familiar to what we were doing, but with some additional import syntax at the top. This is because we&apos;re using Vite, and Vite supports ES Modules by default during development. Rather than including multiple script tags (which is still an option), we can import a single ES module (main.js), and import our other files within it.&lt;/p&gt;
&lt;p&gt;For the worker, most of the code we wrote previously will work, but Vite provides some additional sugar on top of the API here:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The worker script can also use import statements instead of importScripts() - note during dev this relies on browser native support and currently only works in Chrome, but for the production build it is compiled away.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So rather than using &lt;code&gt;importScripts()&lt;/code&gt;, Vite requires that we use the standard ES Module import syntax. However, there&apos;s an issue here: Elm doesn&apos;t compile by default into a format that works well with ES Modules. In addition, the Vite plugin for Elm assumes that you are building a browser-based app (a reasonable assumption), and injects some DOM-powered troubleshooting helpers, which do not work in the worker because the worker doesn&apos;t have access to the DOM.&lt;/p&gt;
&lt;p&gt;For example, let&apos;s assume we update our worker to use ES import syntax, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Elm } from &apos;./src/Worker.elm&apos;

const app = Elm.Worker.init();

onmessage = function ({ data }) {
  const { type, value } = data;

  if (type === &quot;increment&quot;) {
    app.ports.increment.send(value);
  }

  if (type === &quot;decrement&quot;) {
    app.ports.decrement.send(value);
  }
};

app.ports.sendCount.subscribe(function (int) {
  console.log(int);
  postMessage(int);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you start your development environment now (using &lt;code&gt;npm run dev&lt;/code&gt;), you will immediately see an error in the browser console:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Uncaught ReferenceError: HTMLElement is not defined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This error is being thrown by &lt;code&gt;overlay.ts&lt;/code&gt;. This file adds an error overlay when Elm isn&apos;t able to properly compile. So if you&apos;re working in the Main.elm file, and make a change that doesn&apos;t compile, you&apos;ll see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/elm-web-worker-vite-2.png&quot; alt=&quot;In-browser error alerting that a type of &amp;quot;In&amp;quot; cannot be found.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Pretty helpful during app development, but very frustrating when trying to load Elm in a web worker. There is a setting that can be set in the Vite config (&lt;code&gt;server.hmr.overlay: false&lt;/code&gt;) to disable the overlay, but unfortuantely it doesn&apos;t actually prevent HTMLElement from being referenced within the Worker.&lt;/p&gt;
&lt;p&gt;A second approach could be to precompile our Worker.elm file, and import it directly into the &lt;code&gt;worker.js&lt;/code&gt; file (as we did in our vanilla JS example). This, however, throws a silent error; the app will load without any obvious failures, but the worker isn&apos;t actually initialized. Go ahead and try it! Run &lt;code&gt;elm make src/Worker.elm --output elm-worker.js&lt;/code&gt;, then update the &lt;code&gt;worker.js&lt;/code&gt; to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Elm } from &apos;./elm-worker.js&apos;

console.log(&quot;I&apos;m here!&quot;)

const app = Elm.Worker.init();

onmessage = function ({ data }) {
  const { type, value } = data;

  if (type === &quot;increment&quot;) {
    app.ports.increment.send(value);
  }

  if (type === &quot;decrement&quot;) {
    app.ports.decrement.send(value);
  }
};

app.ports.sendCount.subscribe(function (int) {
  console.log(int);
  postMessage(int);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you spin up the app again, you&apos;ll notice that our &lt;code&gt;console.log&lt;/code&gt; doesn&apos;t even run. That&apos;s because the web worker was never initialized, which is very unhelpful for our complex computations.&lt;/p&gt;
&lt;p&gt;So what&apos;s the solution? At the moment, the best solution I&apos;ve found is to create a separate entrypoint for Vite, import &lt;code&gt;Worker.elm&lt;/code&gt; there, and compile it with Vite. That will perform the transformation we need on Elm to allow an import into the worker.&lt;/p&gt;
&lt;p&gt;Within our &lt;code&gt;src&lt;/code&gt; folder, create an &lt;code&gt;elm-worker.js&lt;/code&gt; file, and put the following into it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Elm } from &quot;./Worker.elm&quot;;

const app = Elm.Worker.init();

export default app;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a very basic file, all it does is import our Worker.elm file, initialize the app, and export it. Now we need to compile this file with Vite. At the root level of our app, create a file called &lt;code&gt;worker.config.js&lt;/code&gt;. This will be a special Vite configuration file that we will only use to compile &lt;code&gt;elm-worker.js&lt;/code&gt;. Here&apos;s a good configuration to start with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &quot;vite&quot;;
import elmPlugin from &quot;vite-plugin-elm&quot;;
const path = require(&quot;path&quot;);

export default defineConfig({
  publicDir: false,
  plugins: [elmPlugin()],
  build: {
    outDir: &quot;./elm-worker&quot;,
    sourcemap: false,
    lib: {
      entry: path.resolve(__dirname, &quot;./src/elm-worker.js&quot;),
      name: &quot;elm-worker&quot;,
      fileName: (format) =&amp;gt; `elm-worker.${format}.js`,
    },
  },
});

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration specifies that we only care about &lt;code&gt;elm-worker.js&lt;/code&gt;, not importing any other files (such as the &lt;code&gt;public&lt;/code&gt; folder), and to build those files in an &lt;code&gt;elm-worker&lt;/code&gt; folder. By default, Vite compiles both ESM and UMD formats; this is probably not useful for our case, but it&apos;s not a big issue.&lt;/p&gt;
&lt;p&gt;With our config in place, run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx vite build --config worker.config.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This instructs Vite to run its build command, using our new config file instead of the default one. Once it finishes, you should see a new &lt;code&gt;elm-worker&lt;/code&gt; folder, with two files inside: &lt;code&gt;elm-worker.es.js&lt;/code&gt; and &lt;code&gt;elm-worker.umd.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With our newly compiled ES-compatible file in hand, we can now, at last, import our Elm worker into our web worker file, and everything will work as expected. Update our &lt;code&gt;worker.js&lt;/code&gt; file (at the root of our app) to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import app from &apos;./elm-worker/elm-worker.es.js&apos;

onmessage = function ({ data }) {
  const { type, value } = data;

  if (type === &quot;increment&quot;) {
    app.ports.increment.send(value);
  }

  if (type === &quot;decrement&quot;) {
    app.ports.decrement.send(value);
  }
};

app.ports.sendCount.subscribe(function (int) {
  console.log(int);
  postMessage(int);
});

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run &lt;code&gt;npm run dev&lt;/code&gt; now, and start clicking on the plus and minus buttons, you should see the value displayed on the screen changing. Congratulations! We now have a web worker running Elm within Vite!&lt;/p&gt;
&lt;p&gt;This is by no means not a straightforward solution, but it does at least work, and it allows us to utilize the other benefits of using a frontend development tool like Vite. To make things easier going forward, you can add a custom script to &lt;code&gt;package.json&lt;/code&gt; (something like &lt;code&gt;build:worker&lt;/code&gt;) to run our worker build command, and you can even add it to our &lt;code&gt;dev&lt;/code&gt; script to ensure it runs every time, keeping our web worker closer in sync with the rest of our app.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lindsaykwardell/vite-elm-web-worker&quot;&gt;Here&apos;s a repo with our working Vite code.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Obviously basic addition and subtraction isn&apos;t worth the extra overhead of using web workers. Tasks that require large computations (either complex calculations, or just parsing a lot of data) are ideal for this situation. One side project where I&apos;ve used a web worker required potentially processing more than 2 megabytes of data, which, when done in the main thread, caused the entire app to freeze. Moving the same calculation to a web worker didn&apos;t speed up the calculation, but it did allow the UI (and the CSS) to continue running at full speed. &lt;a href=&quot;https://github.com/lindsaykwardell/juralen-elm/tree/master/src/Game/Analyzer&quot;&gt;Here&apos;s the web worker&lt;/a&gt; from the side project if you&apos;re interested!&lt;/p&gt;
&lt;p&gt;Also, in case you&apos;re concerned, &lt;a href=&quot;https://caniuse.com/webworkers&quot;&gt;Web Workers have been supported&lt;/a&gt; in all modern browsers since IE10, so feel free to use them in your new projects!&lt;/p&gt;
&lt;p&gt;I look forward to seeing what you make with Web Components!&lt;/p&gt;
</content:encoded></item><item><title>Coming Into Vue: What&apos;s Next in Vue 3</title><link>https://lindsaykwardell.com/blog/coming-into-vue-whats-next-vue-3/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/coming-into-vue-whats-next-vue-3/</guid><pubDate>Tue, 08 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It was a moment of celebration across the Vueniverse. At last, after more than a year of Vue 3 being available on the &lt;code&gt;next&lt;/code&gt; branch of all the core repositories (and many of the related frameworks and libraries), Vue 3 has been officially released to the world as the recommended way to write Vue applications. The moment was noted on the &lt;a href=&quot;https://blog.vuejs.org/posts/vue-3-as-the-new-default.html&quot;&gt;official Vue blog&lt;/a&gt; as well as on Twitter.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;🎉 It&apos;s done! Vue 3 is now the default version and the brand new &amp;lt;a href=&quot;https://t.co/0N2uGPCtsh&quot;&amp;gt;https://t.co/0N2uGPCtsh&amp;lt;/a&amp;gt; is live!&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;More details in the blog post in case you missed it: &amp;lt;a href=&quot;https://t.co/ub8L4KhPsJ&quot;&amp;gt;https://t.co/ub8L4KhPsJ&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;— Vue.js (@vuejs) &amp;lt;a href=&quot;https://twitter.com/vuejs/status/1490592213184573441?ref_src=twsrc%5Etfw&quot;&amp;gt;February 7, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;Considering that the initial release of Vue 3 (named &quot;One Piece&quot;) was &lt;a href=&quot;https://github.com/vuejs/core/releases?q=3.0.0&amp;amp;expanded=true&quot;&gt;originally released on September 18, 2020&lt;/a&gt;, the fact that we&apos;re only now reaching the official recommendation has shaped the Vue ecosystem. For applications, most of the actual migration between Vue 2 to Vue 3 is straightforward, with Vue &lt;a href=&quot;https://v3.vuejs.org/guide/migration/introduction.html#breaking-changes&quot;&gt;minimizing breaking changes&lt;/a&gt; while adding new features such as the &lt;a href=&quot;https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api&quot;&gt;Composition API&lt;/a&gt; as well as the latest new feature to land in Vue, &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;, which provides a way to build Vue apps without so much boilerplate code (&lt;a href=&quot;https://www.youtube.com/watch?v=adkxGYeW97c&quot;&gt;I presented a demo of using Composition API and &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;&lt;/a&gt; alongside Ben Hong at VueJS Athens, check it out!).&lt;/p&gt;
&lt;p&gt;However, the Vue ecosystem has seen its share of churn in the meantime. Core APIs that frameworks like Vuetify rely on were altered, to the point that even the Migration Build doesn&apos;t cover the differences between Vue 2 and 3. Frameworks such as Nuxt and Quasar have been working to support Vue 3 (with Quasar releasing it&apos;s stable support back in 2021). Meanwhile, work has been going into Vite, which is now &lt;a href=&quot;https://www.npmjs.com/package/create-vue&quot;&gt;the official recommendation for building Vue apps&lt;/a&gt; as well (replacing the Vue CLI).&lt;/p&gt;
&lt;p&gt;Now that we&apos;ve reached an official recommendation to use Vue 3, what does the future look like for the Vue ecosystem? I asked Twitter what they were looking forward to in the future of Vue, and these are some of the answers that I got.&lt;/p&gt;
&lt;h2&gt;Reactivity Transform&lt;/h2&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;The Reactivity Transform Unification is the final piece in Vue 3 reactivity primitives and &amp;lt;script setup&amp;gt; based RFCs story. Once &amp;lt;a href=&quot;https://twitter.com/vuejs?ref_src=twsrc%5Etfw&quot;&amp;gt;@vuejs&amp;lt;/a&amp;gt; finalizes this RFC, there will be a massive DX improvement both for advanced users and for newcomers to the framework&amp;lt;a href=&quot;https://t.co/8DzIz96ovD&quot;&amp;gt;https://t.co/8DzIz96ovD&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;— patak (@patak_dev) &amp;lt;a href=&quot;https://twitter.com/patak_dev/status/1491019920070438914?ref_src=twsrc%5Etfw&quot;&amp;gt;February 8, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;The next new feature coming to Vue 3 is being referred to as the &lt;a href=&quot;https://github.com/vuejs/rfcs/discussions/413&quot;&gt;&quot;Reactivity Transform Unification&quot;&lt;/a&gt;. The main issue being resolved here is that the introduction of &lt;code&gt;Ref&lt;/code&gt; can be complex, especially moving from Vue 2 to 3. For context, in Vue 3 today, you can create and access a ref value like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from &apos;vue&apos;;

const count = ref(0)        // Set the value with a `ref` call
console.log(count.value)    // Access the value with `.value` in JS
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;!-- Access the value with just the ref name --&amp;gt;
  &amp;lt;button @click=&quot;count++&quot;&amp;gt;
    {{count}}
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The change would be to provide a utility function, &lt;code&gt;$ref&lt;/code&gt;, that would unwrap the ref into a reactive variable, and then let you work with it directly, rather than accessing the &lt;code&gt;.value&lt;/code&gt; key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const count = $ref(0)   // Set the value with a `$ref` call
console.log(count)      // Access the value directly!
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;!-- No changes, still access the value directly --&amp;gt;
  &amp;lt;button @click=&quot;count++&quot;&amp;gt;
    {{count}}
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This feels a lot more comfortable to work with, especially for non-Vue developers! There have been a number of proposals trying to resolve the cumbersome nature of refs, and I think this is a comfortable middle ground. There are a number of other functions proposed in the RFC, go check it out and provide your feedback!&lt;/p&gt;
&lt;h2&gt;Nuxt 3&lt;/h2&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;Can&apos;t wait for &amp;lt;a href=&quot;https://twitter.com/nuxt_js?ref_src=twsrc%5Etfw&quot;&amp;gt;@nuxt_js&amp;lt;/a&amp;gt; 3 to be stable now!&amp;lt;/p&amp;gt;— CapitaineToinon (@CapitaineToinon) &amp;lt;a href=&quot;https://twitter.com/CapitaineToinon/status/1490952449007710210?ref_src=twsrc%5Etfw&quot;&amp;gt;February 8, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;The next big call-out I saw from the ecosystem is Nuxt 3 reaching a stable build. Nuxt 3 is an exciting new major version for Vue&apos;s primary SSR framework, with new features such as its Nitro backend (that allows it to be deployed to a number of environments beyond a standard Node server) and Vite integration. On the Nuxt site, there is a chart comparing the different versions of Nuxt as they exist today, and their recommendations. I&apos;ll copy it below to keep a snapshot of the status as I write this post, but the &lt;a href=&quot;https://v3.nuxtjs.org/getting-started/introduction#comparison&quot;&gt;latest comparison can be found on the Nuxt 3 site&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Version&lt;/th&gt;
&lt;th&gt;Nuxt 2&lt;/th&gt;
&lt;th&gt;Nuxt Bridge&lt;/th&gt;
&lt;th&gt;Nuxt 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vue&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stability&lt;/td&gt;
&lt;td&gt;😊 Stable&lt;/td&gt;
&lt;td&gt;😌 Semi-stable&lt;/td&gt;
&lt;td&gt;😬 Unstable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;🏎 Fast&lt;/td&gt;
&lt;td&gt;✈️ Faster&lt;/td&gt;
&lt;td&gt;🚀 Fastest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nitro Engine&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM support&lt;/td&gt;
&lt;td&gt;🌙 Partial&lt;/td&gt;
&lt;td&gt;👍 Better&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;☑️ Opt-in&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composition API&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Options API&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Components Auto Import&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; syntax&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto Imports&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webpack&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vite&lt;/td&gt;
&lt;td&gt;⚠️ Partial&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;🚧 Experimental&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nuxi CLI&lt;/td&gt;
&lt;td&gt;❌ Old&lt;/td&gt;
&lt;td&gt;✅ nuxi&lt;/td&gt;
&lt;td&gt;✅ nuxi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static sites&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🚧&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are a couple key features that are still being worked out before Nuxt 3 is stable, based on this chart. Since Nuxt 2 was based on Webpack, Vite integration is still experimental and under development. I&apos;ve done some playing around with it, and found it to work fairly well, but I&apos;m not working on production code with Nuxt and Vite so there&apos;s probably some edge cases still to work out. If you want to try Nuxt 3 and Vite, just add this to your Nuxt config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// nuxt.config.ts
   
import { defineNuxtConfig } from &quot;nuxt3&quot;;

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
  // Add the `vite` key to your config, and you&apos;ll opt into Vite mode
  vite: {},
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other missing feature is static site generation. While Nuxt is primarily a server-side generator, it can be used to build static sites as well. If you&apos;re wanting to use Nuxt 3 features, but really need static sites, you&apos;ll need to use the &lt;a href=&quot;https://v3.nuxtjs.org/getting-started/bridge/&quot;&gt;Nuxt Bridge&lt;/a&gt;. From the docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bridge is a forward-compatibility layer that allows you to experience many of the new Nuxt 3 features by simply installing and enabling a Nuxt module.&lt;/p&gt;
&lt;p&gt;Using Nuxt Bridge, you can make sure your project is (almost) ready for Nuxt 3 and have the best developer experience without needing a major rewrite or risk breaking changes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you&apos;re looking to try out Nuxt 3 with an existing app, I&apos;d suggest checking out the Bridge build. Otherwise, spin up a new app with Nuxt 3, and be aware that static generation is currently under development and does not work. Also, remember that because it&apos;s under development, it&apos;s best to consider the Nuxt APIs as unstable and prone to change. While it feels solid, Nuxt 3 is not yet ready for production use.&lt;/p&gt;
&lt;h2&gt;Vuetify&lt;/h2&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&amp;gt;Vuetify compatible with vue3&amp;lt;/p&amp;gt;— Viliam Mihálik (@ViliamMih) &amp;lt;a href=&quot;https://twitter.com/ViliamMih/status/1491017715527860225?ref_src=twsrc%5Etfw&quot;&amp;gt;February 8, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;Vuetify, one of the most well-known material frameworks for building Vue apps, is still working on support for Vue 3. Part of this is due to an overhaul under the hood that will lead to improved performance and a better experience. According to the &lt;a href=&quot;https://vuetifyjs.com/en/introduction/roadmap/&quot;&gt;Vuetify official roadmap&lt;/a&gt;, Vuetify 3 is planned for release in May 2022, with a public beta in February.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Target Release: May 2022&lt;/li&gt;
&lt;li&gt;Alpha: Live&lt;/li&gt;
&lt;li&gt;Beta: February 2022&lt;/li&gt;
&lt;li&gt;Overview:
&lt;ul&gt;
&lt;li&gt;Rebuilt for Vue 3 using the new composition api&lt;/li&gt;
&lt;li&gt;Global properties that allow you to make large overarching changes to your app&lt;/li&gt;
&lt;li&gt;Improved SASS variable customization and extensibility with Built-In Modules&lt;/li&gt;
&lt;li&gt;New Vue CLI presets for generating pre-built starting projects&lt;/li&gt;
&lt;li&gt;First party Vite support for lightning fast development&lt;/li&gt;
&lt;li&gt;Greatly improved TypeScript support&lt;/li&gt;
&lt;li&gt;Better framework coverage with E2E testing using Cypress&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out the links on the Vuetify page to access their Github page and Discord channel.&lt;/p&gt;
&lt;h2&gt;Ecosystem Support and Stability&lt;/h2&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;It&apos;ll be great when the ecosystem catches up inc. all the plugins and Nuxt 3&amp;lt;/p&amp;gt;— Anthony Gore (@anthonygore) &amp;lt;a href=&quot;https://twitter.com/anthonygore/status/1490876690813571073?ref_src=twsrc%5Etfw&quot;&amp;gt;February 8, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;There are a number of other prominent Vue libraries that are still working on their stable Vue 3 support. Vue Apollo, Vuelidate, and Bootstrap Vue have some work done, but are in different stages of either &quot;not available&quot; or &quot;public alpha&quot;. Some other projects, such as &lt;a href=&quot;https://github.com/rigor789/nativescript-vue-next&quot;&gt;NativeScript Vue&lt;/a&gt;, are having to undergo more substantial rewrites in order to be compatible. All of this takes time, and in the meantime the projects that are reliant on these libraries will have to remain on Vue 2, or switch to alternatives that already support Vue 3.&lt;/p&gt;
&lt;p&gt;In that sense, libraries/frameworks that already support Vue 3 have a clear advantage at the moment, and should definitely be considered if you&apos;re using a Vue 2-only option. UI frameworks like &lt;a href=&quot;https://quasar.dev/&quot;&gt;Quasar&lt;/a&gt; are a good alternative to Vuetify or Bootstrap if you&apos;re able to make the switch, for example.&lt;/p&gt;
&lt;p&gt;There are also some libraries that have already been updated, such as &lt;a href=&quot;https://github.com/SortableJS/vue.draggable.next&quot;&gt;Vue Draggable&lt;/a&gt;, as well as others that will not be getting Vue 3 support, such as &lt;a href=&quot;https://vueformulate.com/&quot;&gt;Vue Formulate&lt;/a&gt; (if you&apos;re using it, check out the public beta for its replacement, &lt;a href=&quot;https://formkit.com/&quot;&gt;FormKit&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;und&quot; dir=&quot;ltr&quot;&amp;gt;&amp;lt;a href=&quot;https://twitter.com/useFormKit?ref_src=twsrc%5Etfw&quot;&amp;gt;@useFormKit&amp;lt;/a&amp;gt; 🤗&amp;lt;/p&amp;gt;— Justin Schroeder (@jpschroeder) &amp;lt;a href=&quot;https://twitter.com/jpschroeder/status/1490759805702754313?ref_src=twsrc%5Etfw&quot;&amp;gt;February 7, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As a wrap-up to this topic, what I think is most wanted out of the Vue ecosystem in the near future is some peace and quiet.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;Ecosystem stability. Vue3 is great, but as a new developer to the Vue space, everything seems so fragile. :/&amp;lt;/p&amp;gt;— Nikki Strømsnes (@TheNix) &amp;lt;a href=&quot;https://twitter.com/TheNix/status/1490955186147184640?ref_src=twsrc%5Etfw&quot;&amp;gt;February 8, 2022&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;Upheavals in how things are done are never easy. The Vue team saw that changes were required in order for Vue applications to continue scaling and solving the problems developers were facing, and made some difficult choices. That, on top of a pandemic interrupting the normal flow of life, has led to a very delicate place for the Vue ecosystem. I feel like in general, we&apos;re heading in the right direction, and the more libraries that are able to support Vue 3 moving forward, the faster we&apos;ll get there.&lt;/p&gt;
&lt;p&gt;Vue 3 is a fantastic JS framework to pick up and use today. If you are new to Vue, I highly encourage checking out &lt;a href=&quot;https://vuejs.org/&quot;&gt;the new official documentation for Vue 3&lt;/a&gt;, it&apos;s a fantastic resource that will get you up to speed on the essentials and current recommended practices. If you&apos;re into podcasts, I&apos;d also recommend checking out both &lt;a href=&quot;https://viewsonvue.com/&quot;&gt;Views on Vue&lt;/a&gt; and &lt;a href=&quot;https://enjoythevue.io/&quot;&gt;Enjoy the Vue&lt;/a&gt;. And most important, make sure to enjoy the journey along the way.&lt;/p&gt;
</content:encoded></item><item><title>2021 In Review</title><link>https://lindsaykwardell.com/blog/2021-in-review/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/2021-in-review/</guid><pubDate>Sat, 01 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2021 was certainly a hectic year, to say the least. I&apos;m going to dive into some of the key events that happened in my life this year, both personal and professional.&lt;/p&gt;
&lt;h2&gt;New Beginnings&lt;/h2&gt;
&lt;p&gt;At the start of 2021, I was working as a contractor for Daimler Trucks North America on a project called &lt;a href=&quot;https://www.truckinginfo.com/358620/dtna-service-enhancements-to-further-24-hour-downtime-goal&quot;&gt;&lt;strong&gt;Techlane&lt;/strong&gt;&lt;/a&gt;, a tool for technicians performing repairs and maintenance on Freightliner vehicles. I was primarily responsible for working on the backend, working with a combination of tools such as Java, C# (.NET Core), Docker, and SQL Server, as well as architecting the frontend (which primarily was built using Vue). We had recently shipped what was internally called version 1.0, and our team was wondering what was going to happen next.&lt;/p&gt;
&lt;p&gt;The answer came soon enough, as word spread through our team of a support team that was going to be maintaining the app. This was espcially troublesome for a coworker of mine who was part time on our project, and part time performing support for other internal software. Seeing the writing on the wall, I started applying for new positions at the end of 2020, and was fortunate enough to start as a software engineer at &lt;a href=&quot;https://www.thisdot.co/&quot;&gt;This Dot Labs&lt;/a&gt; in February 2021.&lt;/p&gt;
&lt;p&gt;For those unfamiliar with This Dot:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This Dot Labs provides architectural guidance, staff augmentation, on demand Subject Matter Experts, temporary CTOs, one-on-one pairing, mentorship, and open source strategy support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quite frankly, This Dot is amazing, with a great culture and highly experienced developers. They work with a number of clients, and have a huge amount of expertise across multiple Javascript frameworks. It was an absolute joy when I started working with them!&lt;/p&gt;
&lt;h2&gt;Blogging, Podcasting and Presenting&lt;/h2&gt;
&lt;p&gt;As part of my new role, I was going to be working more closely with the Vue community, as part of This Dot&apos;s online events and trainings. Through working at This Dot, I was able to start more regular blogging, and got to participate in a number of events (including JS Marathon, Vue Contributor Days, and the Vue Global Summit by Geekle). I also was able to host some of This Dot Media&apos;s shows, including Modern Web Podcast, She&apos;s In Tech, Build IT Better, and JamHack. After being a part of Views on Vue through 2020, it was so much fun to be a part of This Dot&apos;s podcasts and videocasts as well. I learned so much about presenting and teaching while I was there. I really enjoyed working with &lt;a href=&quot;https://twitter.com/SarahRonau&quot;&gt;Sarah Ronau&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/taramhampton&quot;&gt;Tara Hampton&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/annmarietonog&quot;&gt;Ann Marie Tonog&lt;/a&gt; while working on the all the shows.&lt;/p&gt;
&lt;p&gt;In addition, I continued to participate in Views on Vue, which has been an amazing way to meets lots of awesome folks in the Vue community. In total, I participated in 58 events across podcasts, YouTube shows, meetups, and conferences.&lt;/p&gt;
&lt;h2&gt;Working with Wikimedia&lt;/h2&gt;
&lt;p&gt;I wasn&apos;t only working on the community side, of course. Since This Dot is a consultancy, I had a handful of clients that I was able to work with (most of which I&apos;m under NDA about). One client that I can certainly talk about was the Wikimedia Foundation. I was able to work with Wikimedia from my first week at This Dot until my last day (spoilers!). I worked with Adam Baso, &lt;a href=&quot;https://twitter.com/vrandezo&quot;&gt;Denny Vrandečić&lt;/a&gt;, and the rest of the team behind &lt;a href=&quot;https://meta.wikimedia.org/wiki/Abstract_Wikipedia&quot;&gt;Abstract Wikipedia&lt;/a&gt;. From the public page on this project:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The goal of Abstract Wikipedia is to let more people share more knowledge in more languages. Abstract Wikipedia is a conceptual extension of Wikidata. In Abstract Wikipedia, people can create and maintain Wikipedia articles in a language-independent way. A particular language Wikipedia can translate this language-independent article into its language. Code does the translation.&lt;/p&gt;
&lt;p&gt;Wikifunctions is a new Wikimedia project that allows anyone to create and maintain code. This is useful in many different ways. It provides a catalog of all kinds of functions that anyone can call, write, maintain, and use. It also provides code that translates the language-independent article from Abstract Wikipedia into the language of a Wikipedia. This allows everyone to read the article in their language. Wikifunctions will use knowledge about words and entities from Wikidata.&lt;/p&gt;
&lt;p&gt;This will get us closer to a world where everyone can &lt;a href=&quot;https://meta.wikimedia.org/wiki/Special:MyLanguage/Vision&quot;&gt;share in the sum of all knowledge&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If this is something that sounds interesting to you, I highly recommend checking out this video presentation from Wikimania (at least the first 12 minutes or so):&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe class=&quot;block m-auto w-[560px] h-[315px]&quot; src=&quot;https://www.youtube.com/embed/LecYqXHvHfg&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;I absolutely loved working on this project! The mission of providing Wikipedia articles in any possible language sounded incredibly interesting, and getting to work on the Wikifunction part of the project was so much fun. I loved getting to work with James, Geno and Cory on the engineering side, and Adam, Denny, Nick, Aishwarya, Diana, Moriel, and all the other members of the Wikimedia Foundation while I was there. There are so many wondeful and insightful people working there, I wish I could list them all.&lt;/p&gt;
&lt;h2&gt;Work From Home&lt;/h2&gt;
&lt;p&gt;As with most tech companies and organizations, Damiler Trucks was forced to send our team to work from home, rather than work from the office. However, this work-from-home situation was always couched as temporary. Before we knew the extent to which Covid would impact 2020 onward, we had regular check-ins with our direct managers to ensure we could keep working. By the end of April, this wasn&apos;t required, and we were allowed to work from home until otherwise stated. There was no plan, however, for how remote work was supposed to happen. While there were some distributed teams (we were working with a team in Detroit), meetings were all held in meeting rooms, calls were with desk phones, and digital communication was typically through email. We eventually got up to speed, but there was always a looming &quot;back to the office&quot; feeling across the company.&lt;/p&gt;
&lt;p&gt;With starting at This Dot Labs, I was able to shift to working from home 100% of the time, without any worry that I&apos;d eventually be required to go to the office. I no longer had to think about a commute, and I was able to set my work hours (I like working early in the day). This permanent shift to working from home made me feel more comfortable investing in my home office setup, as well, leading me to buy some new equipment over the year (including a high quality webcam and external microphone). I felt so embarrassed at my first event with This Dot due to my nose camera (my laptop at the time had its webcam mounted into the keyboard).&lt;/p&gt;
&lt;h2&gt;A Chance Encounter&lt;/h2&gt;
&lt;p&gt;Through podcasting, I&apos;ve met a ton of amazing folks (if you want to see who, go check out &lt;a href=&quot;/media&quot;&gt;my media page&lt;/a&gt;). I started participating as a co-host on &lt;a href=&quot;https://viewsonvue.com&quot;&gt;Views on Vue&lt;/a&gt; in November 2019, and quickly my favorite part of podcasting was talking with cool people about what they find interesting. I got to continue this as part of Modern Web Podcast, but with the added benefit of connecting with folks beyond the Vue ecosystem. In July, I had an opportunity to interview &lt;a href=&quot;https://twitter.com/rtfeldman&quot;&gt;Richard Feldman&lt;/a&gt;, head of technology at NoRedInk and a major voice in the Elm ecosystem (if you haven&apos;t watched any of his talks, I &lt;em&gt;highly&lt;/em&gt; recommend &lt;a href=&quot;https://www.youtube.com/watch?v=5CYeZ2kEiOI&quot;&gt;From Rails to Elm and Haskell&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=QyJZzq0v7Z4&quot;&gt;Why Isn&apos;t Functional Programming the Norm&lt;/a&gt;) about Elm. For context, I interviewed at NoRedInk in October 2020, but due to Covid and my level of experience at the time they weren&apos;t able to offer a position to me at the time. During that process I&apos;d gotten a chance to meet Richard, and thought it would be a great opportunity to connect again. The episode, titled &lt;a href=&quot;https://modernweb.podbean.com/e/s08e014-modern-web-podcast-elm-with-richard-feldman/&quot;&gt;Elm with Richard Feldman&lt;/a&gt;, was a high point of the summer.&lt;/p&gt;
&lt;p&gt;In August, I received a message from Richard. To summarize, he wanted to know if I was still interested in working at NoRedInk, and informed me that the situation had changed since last year. I clearly remember staring at my phone in disbelief at the time; I had never expected something like this to happen! It was honestly a very difficult choice. On the one hand, I loved my current job, and the opportunities to interact with the Vue and greater Javascript community, and the project I was working on with the Wikimedia Foundation. On the other hand, working at NoRedInk aligned with my personal values and interest in writing and education, and I would be able to use functional programming languages such as Elm and Haskell.&lt;/p&gt;
&lt;p&gt;In the end, I decided to take the offer, and make the switch to NoRedInk. I treasure all the friendships I made while at This Dot, and while I was only there for a brief period I know it has made a huge impact on my life.&lt;/p&gt;
&lt;h2&gt;A Functional Start&lt;/h2&gt;
&lt;p&gt;I began working at NoRedInk in October 2021, as part of the writing experience team. For those unfamiliar, NoRedInk is an education platform for learning English and writing, targeted towards middle and high school students. I met my new team, and got to work on the product. I was quickly immersed in functional programming languages, getting to write Elm on a daily basis and starting to explore Haskell. The culture at NoRedInk is also fantastic, with a strong tradition of pair programming and teaching these more obscure programming languages to new hires. I was regularly meeting with team members, learning more advanced concepts of functional programming and actually using them in production code.&lt;/p&gt;
&lt;p&gt;Because of the immersion into functional programming, I was also able to take that knowledge to my side projects. My longest running project, a turn-based strategy game, is programmed in Elm, but with the technical finesse of someone unfamiliar with the language. I&apos;ve since been able to go back through the codebase and clean it up, improving performance and stripping away functions that I didn&apos;t actually need after all. I also was able to put a lot of effort into a library for rendering Elm apps inside of Vue, called &lt;a href=&quot;https://elm-vue-bridge.lindsaykwardell.com/&quot;&gt;elm-vue-bridge&lt;/a&gt;. I originally started that project a couple years ago as an experiment, but I&apos;ve since been able to update it to work with Vue 3 and integrate nicely with the Vue API. I also got to try out Vuepress on a real project, which is something I&apos;d been wanting to do as well.&lt;/p&gt;
&lt;p&gt;I&apos;ve also started doing more blogging on Elm in general, and even did a live stream on Twitch (as an experiment) on the basics of Elm. It&apos;s been pretty fun overall, and I look forward to continuing to do more in the future!&lt;/p&gt;
&lt;h2&gt;Into 2022, we go!&lt;/h2&gt;
&lt;p&gt;Looking forward into 2022, I&apos;m very excited for what is to come in the future. I plan on continuing to blog and podcast about the tech that&apos;s interesting to me, and take care of myself and my family as we continue to navigate the pandemic. I wish you all the best, and that you are all able to stay safe and be well. Here&apos;s to 2022!&lt;/p&gt;
</content:encoded></item><item><title>Setting up an Elm project in 2022</title><link>https://lindsaykwardell.com/blog/setting-up-elm-in-2022/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/setting-up-elm-in-2022/</guid><pubDate>Sun, 19 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you haven&apos;t used Elm before, it can be a bit intimidating to get started. A pure functional programming language for building web applications, Elm provides a number of tools for writing Elm apps, including &lt;code&gt;elm reactor&lt;/code&gt; for fast recompiling of specific modules and &lt;code&gt;elm make&lt;/code&gt; for building JS assets. But this workflow can feel a bit lacking if you&apos;re coming from a Javascript ecosystem, where hot module reload (HMR), automatic bundling, and integration with CSS and other frontend technologies is common.&lt;/p&gt;
&lt;p&gt;Luckily, there are a number of options for setting up a successful Elm application today. Let&apos;s explore what&apos;s available for building an Elm application in 2022, including ways to get a better development environment, as well as the tooling surrounding Elm that makes working with the language as delightful as possible.&lt;/p&gt;
&lt;h2&gt;Elm (the language)&lt;/h2&gt;
&lt;p&gt;You can install the Elm language (and its bundled tooling) &lt;a href=&quot;https://www.npmjs.com/package/elm&quot;&gt;from npm&lt;/a&gt;. Installing Elm globally gives you access to a handful of useful tools:&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;elm repl&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is the command line repl for Elm. You can use it to execute commands in Elm and see what the result would be.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;elm init&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is the command to initialize Elm in a folder. Because Elm has its own ecoystem of tooling and libraries separate from Javascript, it also does not use npm for its package management, and as such does not rely on &lt;code&gt;package.json&lt;/code&gt;. If you want to add Elm to a project (or start a new project), you&apos;ll need to run this command. When you do, you&apos;ll see a message like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out &amp;lt;https://elm-lang.org/0.19.1/init&amp;gt; for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an example of how Elm provides helpful information at every opportunity, as well! The link it provides takes you to documentation on how to structure and scaffold an Elm application.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;elm reactor&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Running &lt;code&gt;elm reactor&lt;/code&gt; opens an interactive page for navigating your Elm code and previewing it in the browser. Note that is does not render external Javascript, or any values set in your HTML files; it is only displaying what the output is for a given Elm file. Also, it does not support auto refresh, so you will have to manually refresh your browser.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/elm-reactor.png&quot; alt=&quot;Elm reactor, showing current files in the src folder, source directories, and dependencies&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;elm install&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;As you may expect, this command installs an Elm dependency in your application. Sometimes you will need to install a dependency that is already a sub dependency, in which case Elm will inform you that it&apos;s going to move the dependency up in the chain so that you can import its modules directly.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;elm make&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This command lets you build the output Javascript from your Elm code, so that you can import and initialize it in your application. If you are utilizing a tool like Webpack, Parcel, Rollup, or Vite, this is typically not something you&apos;ll need to do (more on bundlers later).&lt;/p&gt;
&lt;p&gt;There are other commands available as well. Feel free to run &lt;code&gt;elm --help&lt;/code&gt; to see all the available commands and options.&lt;/p&gt;
&lt;h2&gt;Editor&lt;/h2&gt;
&lt;p&gt;There are a handful of plugins and extension for different editors, the majority of which can be found on an &lt;a href=&quot;https://github.com/elm/editor-plugins&quot;&gt;officially updated list&lt;/a&gt; within the Elm organization on Github. My editor of choice is VS Code, and the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=Elmtooling.elm-ls-vscode&quot;&gt;VS Code extension for Elm&lt;/a&gt; is an excellent tool for writing in Elm. It provides details on errors when saving, references to where a particular function or value is utilized, and access to function documentation on hover&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/elm-vs-code.png&quot; alt=&quot;The Elm tooling extension in VS Code&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Code Formatting&lt;/h2&gt;
&lt;p&gt;Unlike Javascript, Elm has an &lt;a href=&quot;https://elm-lang.org/docs/style-guide&quot;&gt;official Style Guide&lt;/a&gt; for how Elm code should be structured. In addition, some formatting is built into the language itself, such as requiring indents of four spaces. This removes a major point of contention within teams. In addition, the community has put together a wonderful tool called &lt;a href=&quot;https://github.com/avh4/elm-format&quot;&gt;&lt;code&gt;elm-format&lt;/code&gt;&lt;/a&gt;. Similar to Prettier, this utility can be used to ensure that all Elm code matches the official style guide. Unlike Prettier, there is no custom configuration, meaning once again that your team can focus on writing code instead of what kind of quotes to use.&lt;/p&gt;
&lt;h2&gt;Linting&lt;/h2&gt;
&lt;p&gt;The Elm community has an unofficial linter (called &lt;a href=&quot;https://github.com/jfmengels/elm-review/tree/2.6.1&quot;&gt;&lt;code&gt;elm-review&lt;/code&gt;&lt;/a&gt;), which can be used to check your code for potential bugs or mistakes, or highlight a better way to write Elm. Unlike &lt;code&gt;elm-format&lt;/code&gt; (and more similar to tools like ESLint), &lt;code&gt;elm-review&lt;/code&gt; does not come with any default rules to follow:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All the rules describing problematic code are written in Elm, and elm-review does not come with any built-in rules; instead users are encouraged to write rules themselves and publish them as Elm packages, for everyone to benefit. Search the package registry to find what&apos;s out there!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;The de-facto standard for testing an Elm application is &lt;a href=&quot;https://github.com/elm-explorations/test&quot;&gt;&lt;code&gt;elm-test&lt;/code&gt;&lt;/a&gt;. However, as noted in the README:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When people say “elm-test” they usually refer to either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This Elm package for writing tests.&lt;/li&gt;
&lt;li&gt;rtfeldman/node-test-runner – a CLI tool (called elm-test) for running tests defined using this package in the terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Elm package (available on the &lt;a href=&quot;https://package.elm-lang.org/packages/elm-explorations/test/latest&quot;&gt;Elm package repository&lt;/a&gt;) contains the functions required to write tests, query HTML, and perform assertions. The syntax should be familiar if you&apos;ve written unit or fuzz tests in other languages, with &lt;code&gt;describe&lt;/code&gt; blocks and individual &lt;code&gt;test&lt;/code&gt; functions being called.&lt;/p&gt;
&lt;p&gt;To actually run the tests, however, there are currently two options. The first, as noted above, is &lt;a href=&quot;https://github.com/rtfeldman/node-test-runner&quot;&gt;&lt;code&gt;node-test-runner&lt;/code&gt;&lt;/a&gt;, which is available from npm at &lt;code&gt;elm-test&lt;/code&gt;. This utility will run the tests as defined in your Elm code, and return the results. There is a second option, &lt;a href=&quot;https://github.com/mpizenberg/elm-test-rs&quot;&gt;&lt;code&gt;elm-test-rs&lt;/code&gt;&lt;/a&gt;, which is written in Rust instead of Node. It has a handful of features that &lt;code&gt;node-test-runner&lt;/code&gt; does not have, as well as some downsides (see the Github README for details), but in general both tools work very well for testing Elm code.&lt;/p&gt;
&lt;h2&gt;Development Environment&lt;/h2&gt;
&lt;p&gt;Now&apos;s the fun part - writing our app! Using only the above tools, you would be set to build an Elm application that is properly formatted, tested, and compiled to JS. However, that doesn&apos;t help you get it onto the page; you&apos;l still have to create an HTML file, import your Elm code along with any other dependencies you may have, and test it in your &quot;live&quot; environment. Found an error? Time to recompile!&lt;/p&gt;
&lt;p&gt;Needless to say, this can get pretty tedious, not to mention that most Javascript developers today are used to more advanced tooling (such as auto refresh or hot module reload). What can we do in order to set up a better development environment for ourselves?&lt;/p&gt;
&lt;h3&gt;elm-live&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/wking-io/elm-live/raw/master/elm-live-logo.png&quot; alt=&quot;elm-live, a flexible dev server for Elm.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;First on our list is &lt;a href=&quot;https://github.com/wking-io/elm-live&quot;&gt;elm-live&lt;/a&gt;. From their README, elm-live provides:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Hot reloading&lt;/li&gt;
&lt;li&gt;Local SSL&lt;/li&gt;
&lt;li&gt;No reload mode&lt;/li&gt;
&lt;li&gt;No server mode&lt;/li&gt;
&lt;li&gt;and more!&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&apos;s a pretty great tool for working with an Elm application, removing the tedious step of constantly rebuilding our application. However, there&apos;s still a couple drawbacks with using elm-live. First, it exists as a development environment for Elm, which means that changing our HTML, CSS, or Javascript assets will not trigger anything. Second, if you need any dependencies from npm, elm-live will not compile these for you. You&apos;ll still need some sort of bundler for the rest of your assets. elm-live is excellent for projects written primarily in Elm, or if you don&apos;t mind running two development environments side by side.&lt;/p&gt;
&lt;h3&gt;Webpack/Rollup/etc&lt;/h3&gt;
&lt;p&gt;If you&apos;re a Javascript developer, you may have gotten accustomed with importing arbitrary files into your Javascript code. Want to bring in a &lt;code&gt;.vue&lt;/code&gt; file? Go for it! &lt;code&gt;.svelte&lt;/code&gt;? Sure, why not! Our favorite frameworks typically provide some sort of integration with bundlers so that we can import them directly, without having to compile them first to standard Javascript. Can we do that with Elm?&lt;/p&gt;
&lt;p&gt;It turns out, we can! Elm has pretty good support across compilers/bundlers, so that you can import an Elm file directly into your core JS application - no compile step required! Keep in mind that you will still need to initialize the Elm application in order for that code to do anything, but otherwise you are able to treat it like any other compile-to-JS asset.&lt;/p&gt;
&lt;p&gt;Here&apos;s a list of common compiler options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Webpack: &lt;a href=&quot;https://github.com/elm-community/elm-webpack-loader&quot;&gt;&lt;code&gt;elm-webpack-loader&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rollup: &lt;a href=&quot;https://github.com/ulisses-alves/rollup-plugin-elm#readme&quot;&gt;&lt;code&gt;rollup-plugin-elm&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vite: &lt;a href=&quot;https://github.com/hmsk/vite-plugin-elm&quot;&gt;&lt;code&gt;vite-plugin-elm&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you like using Parcel, then you&apos;re in luck! Parcel provides &lt;a href=&quot;https://parceljs.org/languages/elm&quot;&gt;built-in support for Elm&lt;/a&gt; by default, no plugins required. There had been some issues in Parcel 2 and Elm, but at this point they appear to be worked out, making Parcel an amazing choice if you&apos;re expecting interop with Javascript and want a great experience.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;elm-tooling&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Great! Now we&apos;re able to import our Elm code directly into our existing applications, making the integration that much easier. However, there&apos;s still one last thing we can do to improve our environment. One problem we may run into is that the tooling described above needs to be installed locally in order to utilize it, which can lead to more work onboarding developers or getting started with a project.&lt;/p&gt;
&lt;p&gt;There is a tool called &lt;a href=&quot;https://elm-tooling.github.io/elm-tooling-cli/&quot;&gt;&lt;code&gt;elm-tooling&lt;/code&gt;&lt;/a&gt; which does all the hard work for us. Rather than having to install the Elm language, &lt;code&gt;elm-format&lt;/code&gt;, and &lt;code&gt;elm-test&lt;/code&gt; (or &lt;code&gt;elm-test-rs&lt;/code&gt;) individually, we can run &lt;code&gt;npx elm-tooling install&lt;/code&gt;, and all of the core tooling can be installed at once, significantly faster than if they were installed individually. This can even be added as a postinstall command in your &lt;code&gt;package.json&lt;/code&gt;, ensuring that Elm and its related tools are available with a single install command. This is extremely useful when setting up your local environment, or during CI build steps.&lt;/p&gt;
&lt;h2&gt;Frameworks and Templates&lt;/h2&gt;
&lt;p&gt;All this tooling is great, and an excellent way to get writing Elm code and integrating it into your application. But sometimes it&apos;s nice to start from a higher level abstraction, much like React developers often start with Create React App, or Vue developers use the Vue CLI. While there are no official templates or frameworks beyond what we&apos;ve already talked about, there are a few interesting projects that are worth taking a look at.&lt;/p&gt;
&lt;h3&gt;elm-spa&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.elm-spa.dev/&quot;&gt;elm-spa&lt;/a&gt; is a great way to start working on a single-page Elm application. Browser navigation and handling multiple pages is something that is a bit more difficult in Elm than in pure Javascript, and as such requires more boilerplate code in order to make it work. elm-spa handles all of the complexities of building an SPA for you, while providing a number of great features for building a modern web application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/elm-spa-image.png&quot; alt=&quot;Build reliable applications with Elm. With elm-spa, you can create production-ready application with one command: npx elm-spa new. No need to configure webpack, gulp, or any other NPM dev tools. This zero-configuration CLI comes with a live-reloading dev server, production-ready build commands, and even a few scaffolding commands for new and existing applications.&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;elm-pages&lt;/h3&gt;
&lt;p&gt;Where elm-spa is targeting single-page applications, &lt;a href=&quot;https://elm-pages.com/&quot;&gt;elm-pages&lt;/a&gt; is built for generating static sites. Utilizing Elm&apos;s type system, elm-pages&apos;s goal is to make handling external data for a static site easy. Conceptually, it&apos;s similar to what Gatsby or Gridsome do (provide a typesafe interface to access multiple types of data), but utilizing Elm&apos;s strengths rather than adding an additional type system such as GraphQL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/elm-pages-image.png&quot; alt=&quot;Pull in typed Elm data to your pages. Whether your data is coming from markdown files, APIs, a CMS, or all of the above, elm-pages lets you pull in just the data you need for a page. No loading spinners, no Msg or update logic, just define your data and use it in your view.&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;ElmBook&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://elm-book-in-elm-book.netlify.app/overview&quot;&gt;ElmBook&lt;/a&gt;&apos;s goal is to provide a solid documentation site for library or package code. It includes a search function, as well as the ability to break down your docs into chapters and books. It also allows rendering Elm code within your docs, via a shared model, so that you can demonstrate functionality in the UI. It&apos;s built on top of elm-live, so working with ElmBook should feel familiar if you&apos;ve used it before.&lt;/p&gt;
&lt;p&gt;If you&apos;re coming from Vue, the focus of ElmBook is similar to Vuepress, but rather than focusing on markdown files, ElmBook is written within Elm itself. Markdown is supported for writing the documentation itself, but all of your work will be handled within the Elm code, making your documentation perfectly typesafe as well as useful.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every application, tool or team has their own history worth telling.&lt;/p&gt;
&lt;p&gt;ElmBook tries to help them by making it easy to create rich documents that showcase their libraries documentations, UI components, design tokens, or anything else their creativity comes up with.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;&lt;code&gt;vite-elm-template&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This was a template I created to scratch my own itch. As a Vue developer, I love working with Vite, and really wanted a base template for Vite that I could use to build Elm applications. &lt;a href=&quot;https://github.com/lindsaykwardell/vite-elm-template&quot;&gt;&lt;code&gt;vite-elm-template&lt;/code&gt;&lt;/a&gt; is a basic Vite template intended to get you started writing in Elm without having to spend time configuring everything yourself. Unlike elm-pages or elm-spa, it&apos;s not a framework of any sort. If you want to bring in single-page application features, or other functionalities, you will have to build those in yourself. That said, it&apos;s perfect for getting started with a basic environment.&lt;/p&gt;
&lt;p&gt;As of this writing it includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hot Module Reload of all code in the app (including Elm)&lt;/li&gt;
&lt;li&gt;Tooling installation via &lt;code&gt;elm-tooling&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Includes Elm, &lt;code&gt;elm-format&lt;/code&gt;, &lt;code&gt;elm-json&lt;/code&gt;, and &lt;code&gt;elm-test-rs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Basic unit test examples&lt;/li&gt;
&lt;li&gt;Github Actions CI for running tests&lt;/li&gt;
&lt;li&gt;Recommends the Elm VS Code extension&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Vite is something you enjoy working with, I hope you&apos;ll check it out! There is also a link in the README to set up a workspace using Gitpod if you want to try things out in a sandbox environment first.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope that helps you get started building an Elm application! Elm is an amazing language to learn and work with, and the tooling that exists to support it today makes working with it as delightful an experience as the language itself provides. Above all, I hope you enjoy learning and working with Elm in 2022!&lt;/p&gt;
&lt;p&gt;If you&apos;re new to Elm, and would like to learn more about it, check out the &lt;a href=&quot;https://guide.elm-lang.org/&quot;&gt;official Elm Guide&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Astro 0.21 - Upgrade Experience</title><link>https://lindsaykwardell.com/blog/upgrade-astro-0-21/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/upgrade-astro-0-21/</guid><pubDate>Sat, 20 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The day has finally arrived! &lt;a href=&quot;https://astro.build/blog/astro-021-release/&quot;&gt;Astro has released 0.21 publicly&lt;/a&gt;, and it&apos;s a big change. Most important of all, they&apos;ve migrated the development environment from Snowpack to &lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt;, which opens up a whole slew of new features and plugins to the Astro ecosystem. There are also a number of other changes, which are listed in the annoucement &lt;a href=&quot;https://docs.astro.build/migration/0.21.0/&quot;&gt;and migration guide&lt;/a&gt;. If you&apos;re looking to upgrade your site, I highly encourage you to check out those resources first!&lt;/p&gt;
&lt;p&gt;Rather than guide you through the upgrade process (their migration doc is pretty good for that), I want to document my experience. I ran into a number of interesting bumps along the way, and I think it would be beneficial to share my experience with anyone else interested in performing their upgrade.&lt;/p&gt;
&lt;p&gt;For context on my site, I am mostly using Astro componets (with the &lt;code&gt;.astro&lt;/code&gt; file extension), with a couple small Vue components. Most of the issues I ran into were related to Astro components, and the changed API. I did not have to do anything related to file imports, React, or TSX/JSX.&lt;/p&gt;
&lt;p&gt;Also, if you&apos;re interested, &lt;a href=&quot;https://github.com/lindsaykwardell/lindsaykwardell/pull/25&quot;&gt;here&apos;s the PR&lt;/a&gt; I used to track my changes as I performed the upgrade.&lt;/p&gt;
&lt;p&gt;With that out of the way, let&apos;s upggrade to Astro 0.21!&lt;/p&gt;
&lt;h2&gt;Update Dependencies&lt;/h2&gt;
&lt;p&gt;The first thing I did was update my &lt;code&gt;package.json&lt;/code&gt; to use the new dependencies. This was a straightforward change using &lt;code&gt;npm outdated&lt;/code&gt; and manually updating/removing unused packages. Besides updating Astro and the Vue renderer to the latest version, I removed &lt;code&gt;@snowpack/plugin-dotenv&lt;/code&gt; because Astro now supports &lt;code&gt;.env&lt;/code&gt; files by default!&lt;/p&gt;
&lt;p&gt;Also, as Astro is no longer using Snowpack for its build tool, I had to update my environment variables as well, removing &lt;code&gt;SNOWPACK&lt;/code&gt; from the variable name.&lt;/p&gt;
&lt;p&gt;However, when I ran &lt;code&gt;npm run dev&lt;/code&gt;, I was presented with an error!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Error: Build failed with 12 errors:
node_modules/fetch-blob/from.js:1:59: error: Could not resolve &quot;node:fs&quot; (mark it as external to exclude it from the bundle)
node_modules/fetch-blob/from.js:2:25: error: Could not resolve &quot;node:path&quot; (mark it as external to exclude it from the bundle)
node_modules/fetch-blob/from.js:3:31: error: Could not resolve &quot;node:worker_threads&quot; (mark it as external to exclude it from the bundle)
node_modules/node-fetch/src/body.js:8:34: error: Could not resolve &quot;node:stream&quot; (mark it as external to exclude it from the bundle)
node_modules/node-fetch/src/body.js:9:31: error: Could not resolve &quot;node:util&quot; (mark it as external to exclude it from the bundle)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This error was thrown because I was importing &lt;code&gt;node-fetch&lt;/code&gt; in a couple JS files. In Astro 0.21, &lt;a href=&quot;https://docs.astro.build/guides/data-fetching/&quot;&gt;&lt;code&gt;fetch&lt;/code&gt; is now globally available&lt;/a&gt;, both inside and outside of &lt;code&gt;.astro&lt;/code&gt; components. Removing the import of &lt;code&gt;node-fetch&lt;/code&gt; solved the problem.&lt;/p&gt;
&lt;p&gt;However, I was presented with a different error!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;panic: Export statements must be placed at the top of .astro files!
panic: Export statements must be placed at the top of .astro files!
2:14:52 PM [vite] Error when evaluating SSR module /src/layouts/BlogPost.astro:
    at /src/layouts/BaseLayout.astro
2:14:52 PM [vite] Error when evaluating SSR module /src/pages/blog/a-song-unsung.md:
    at /src/layouts/BaseLayout.astro
2:14:52 PM [vite] Error when evaluating SSR module /src/pages/index.astro:
    at /src/layouts/BaseLayout.astro
02:14 PM [astro] 500 /                                        1753ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Astro now requires all exports (such as the Props interface) to be at the top of the file. This was a simple change, but not one documented in the migration guide. By either removing or moving the exported Props interface, the dev environment was able to load. However, something was missing...&lt;/p&gt;
&lt;p&gt;The content!&lt;/p&gt;
&lt;h2&gt;Missing Body&lt;/h2&gt;
&lt;p&gt;When &lt;code&gt;localhost:3000&lt;/code&gt; came up, I was presented with a perfectly blank screen. I noticed that the head tag was filling in properly, but not the body. In Chrome devtools, nothing appeared between the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;!-- meta tags, script, etc --&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&apos;s going on here? I&apos;m honestly still not sure. If I updated my &lt;code&gt;index.astro&lt;/code&gt; file to have some extra text before calling &lt;code&gt;&amp;lt;BaseLayout&amp;gt;&lt;/code&gt;, the text would load, and the meta tags would be replicated inside the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag. The solution was to eliminate the &lt;code&gt;BaseHead&lt;/code&gt; component (which was only utilized by &lt;code&gt;BaseLayout&lt;/code&gt;), which placed the actual HTML tags in the layout component.&lt;/p&gt;
&lt;p&gt;However, I took a bit of a detour before coming to this solution, and realized that my Tailwind configuration was no longer working. This was thankfully a straightforward fix, although the documentation seemed to be spread across a couple pages.&lt;/p&gt;
&lt;p&gt;First, I had to manually install PostCSS and autoprefixer. Then, I updated my &lt;code&gt;postcss.config.cjs&lt;/code&gt; file to include postcss-nested, Tailwind, and autoprefixer (previously I had only needed to import postcss-nested). This reenabled Tailwind for my site, which I was able to validate after I fixed the missing body problem.&lt;/p&gt;
&lt;p&gt;However, something didn&apos;t look quite right&lt;/p&gt;
&lt;h2&gt;Missing Styles&lt;/h2&gt;
&lt;p&gt;While the site loaded (and Tailwind properly initialized), a number of custom styles were missing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/missing-styles.png&quot; alt=&quot;lindsaykwardell.com. Dark mode is not working, and other styles are slightly off&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Crucially (for me), dark mode was completely nonfunctional. Some elements would switch style, but a majority (including the root background and any text) were not changing. Consulting the Chrome devtools revealed that my styles (using PostCSS) were not being properly processed before passing into the DOM. For example, this is what Astro was putting into the site (formatting added by me):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.blog-item.astro-EDZCIZOF {@apply shadow-md rounded-lg relative overflow-hidden flex justify-center;animation:0.3s ease-out 0s 1 shift;.}.blog-image-wrapper.astro-EDZCIZOF{height:300px;width:100%;overflow:hidden;}@keyframes shift{0%{transform:translateY(30%);opacity:0;}100%{transform:translateY(0);opacity:1;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clearly, Astro was not properly applying PostCSS here. Tailwind&apos;s &lt;code&gt;@apply&lt;/code&gt; rule was being ignored and sent straight to the compiled CSS. The solution here was to export these global styles into the &lt;code&gt;global.css&lt;/code&gt; file, instead of inline on my &lt;code&gt;BaseLayout.astro&lt;/code&gt; component. That&apos;s fine, it&apos;s probably better this way anyway.&lt;/p&gt;
&lt;p&gt;Navigating around the site, I kept noticing that other styles were also failing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/missing-styles-2.png&quot; alt=&quot;Blog post preview card, the hero image is too short compared to what it should be&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;bg-gray-900 p-2&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/missing-styles-3.png&quot; alt=&quot;PrismJS styling is not being applied&quot;&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;The solution, as before, was to pull out the inline CSS into external files. I wasn&apos;t a huge fan of this solution, but it worked. Styles looked as they were supposed to, and life was good.&lt;/p&gt;
&lt;p&gt;Except the site still wouldn&apos;t build.&lt;/p&gt;
&lt;h2&gt;Bad CSS!&lt;/h2&gt;
&lt;p&gt;Everything looks as it should, so I tried to deploy to Netlify. However, an error was still popping up:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/css-syntax-error.png&quot; alt=&quot;Build error showing that a CSS value was invalid, due to an extra dot after a semicolon.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Unknown word? That doesn&apos;t make se... oh. OH. There&apos;s an extra period after a semicolon. That&apos;s weird, I didn&apos;t write anything like that. And besides, that&apos;s coming from &lt;code&gt;about.css&lt;/code&gt;, which isn&apos;t a file I created. What&apos;s going on?&lt;/p&gt;
&lt;p&gt;Doing a bit of searching, I found another style block that I had missed in my previous cleanup, but this one was scoped to a component. I&apos;d much rather not extract it from the Astro component, considering that&apos;s the whole point of scoped CSS in a component (as a Vue fan, I&apos;m a proponent of this approach).&lt;/p&gt;
&lt;p&gt;I reached out on the Astro Discord channel (which I highly recommend if you are using Astro), and reported the issue.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/discord-postcss-discussion.png&quot; alt=&quot;Discord chat discussion between Lindasy and Nate regarding PostCSS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We quickly discovered that the cause of the problem was that I had set the language for the style block to &lt;code&gt;postcss&lt;/code&gt;, which was causing issues with the Astro compiler. Switching to &lt;code&gt;pcss&lt;/code&gt; solved the problem, and I was able to find and update the remaining scoped CSS blocks to correctly use Postcss. (If you&apos;re interested, &lt;a href=&quot;https://discord.com/channels/830184174198718474/911628265528119299/911638741951590440&quot;&gt;here&apos;s the link&lt;/a&gt; to the Discord thread). I also moved back a number of styles to their components, leaving just the actual global styles in &lt;code&gt;global.css&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Success?&lt;/h2&gt;
&lt;p&gt;With that, I was able to deploy an updated version of the site, now powered by Astro 0.21. My tests were passing, visuals looked correct, and the build ran locally without errors. Let&apos;s send it up to Netlify for deployment!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7:58:22 AM: &amp;gt; astro build
7:58:23 AM: 03:58 PM [config] Set &quot;buildOptions.site&quot; to generate correct canonical URLs and sitemap
7:58:26 AM: warn - You have enabled the JIT engine which is currently in preview.
7:58:26 AM: warn - Preview features are not covered by semver, may introduce breaking changes, and can change at any time.
7:58:31 AM: [object Object]
7:58:31 AM: Error: [object Object]
7:58:31 AM: npm ERR! code ELIFECYCLE
7:58:31 AM: npm ERR! errno 1
7:58:31 AM: npm ERR! lindsaykwardell@4.0.0 generate: `astro build`
7:58:31 AM: npm ERR! Exit status 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What a lovely error! Thankfully, this was my fault, not Astro or Netlify. I had forgotten to update my environment variables on Netlify, causing the build to fail (that object is probably a JS fetch error). Easy fix, and it was good to go. The build went up, deployment was successful, and... my JS wasn&apos;t loading.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;flex justify-center&quot;&amp;gt;
&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;Having some trouble with &amp;lt;a href=&quot;https://twitter.com/Netlify?ref_src=twsrc%5Etfw&quot;&amp;gt;@Netlify&amp;lt;/a&amp;gt; and &amp;lt;a href=&quot;https://twitter.com/astrodotbuild?ref_src=twsrc%5Etfw&quot;&amp;gt;@astrodotbuild&amp;lt;/a&amp;gt; 0.21. For some reason, I&apos;m unable to fetch my JS files for partial hydration because they&apos;re blocked by CORS. It looks like files are being stored on Cloudfront instead of my domain.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;Anyone run into something like this before?&amp;lt;/p&amp;gt;— Lindsay Wardell 🏳️‍⚧️ (@lindsaykwardell) &amp;lt;a href=&quot;https://twitter.com/lindsaykwardell/status/1462173643249651713?ref_src=twsrc%5Etfw&quot;&amp;gt;November 20, 2021&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt; &amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;This was slightly more complex, but had an easy solution. The build was working as expected locally, I could see the JS files were present (and clearly being cached by Netlify on Cloudfront). I didn&apos;t have any build plugins enabled that modified the JS, and (most confusing) everything worked as expected on Astro 0.20.&lt;/p&gt;
&lt;p&gt;After doing some googling, I found the problem in a &lt;a href=&quot;https://answers.netlify.com/t/no-access-control-allow-origin-header/14631&quot;&gt;Netlify support thread&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Yup - that is saying that cloudfront doesn’t have your custom header which is true! I think you’re using asset optimization on the site, and that will clash with custom headers - we can’t set them on those resources. So, you could turn off that feature in your build and deploy settings and that would remove the problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;m not certain what custom header is being set by Astro/Netlify/wherever, but between 0.20 and 0.21 a custom header was being applied, which broke the JS fetch when going across domains. I went into my settings, and disabled the JS minification and bundling offered by Netlify. At last, everything actually, truly worked.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As upgrades go, this was honestly pretty straightforward. I started working on it late Friday night, and was able to finish Saturday morning. No major rewrites were required, just some updated syntax and moving some CSS around. And best of all, Astro is now powered by Vite, so I can start trying out the plugins I&apos;m more familiar with on my site (rather than looking deeper into the Snowpack ecosystem).&lt;/p&gt;
&lt;p&gt;If you&apos;re looking to migrate your own site, I strongly encourage checking out the &lt;a href=&quot;https://docs.astro.build/migration/0.21.0/&quot;&gt;Migration Guide&lt;/a&gt;, and &lt;a href=&quot;https://docs.astro.build/migration/0.21.0/&quot;&gt;the blog post&lt;/a&gt; explaining the Astro team&apos;s reasoning for making the shift from Snowpack to Astro.&lt;/p&gt;
&lt;p&gt;If you haven&apos;t tried Astro out yet, &lt;a href=&quot;https://lindsaykwardell.com/blog/rebuilding-site-with-astro&quot;&gt;I wrote about my experience&lt;/a&gt; migrating from Nuxt static site to Astro, and the performance benefits I immediately saw by migrating. I definitely recommend checking out Astro if you&apos;re interested in static site generation.&lt;/p&gt;
&lt;p&gt;Until next time!&lt;/p&gt;
</content:encoded></item><item><title>Styling Vue Single-File Components</title><link>https://www.thisdot.co/blog/styling-vue-single-file-components</link><guid isPermaLink="true">https://www.thisdot.co/blog/styling-vue-single-file-components</guid><pubDate>Thu, 04 Nov 2021 14:14:09 GMT</pubDate><content:encoded/></item><item><title>From Nuxt to Astro - Rebuilding with Astro</title><link>https://lindsaykwardell.com/blog/rebuilding-site-with-astro/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/rebuilding-site-with-astro/</guid><pubDate>Sat, 09 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I don&apos;t remember exactly when I started hearing about &lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt;, one of the latest static site generators to help tackle the problem of building sites with less Javascript. The problem is one we&apos;re all familiar with - how can I build a static site (in my case, my personal site) using the languages and tools I know best, while performing at its best? After migrating from Wordpress, I first tried &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt;, then &lt;a href=&quot;https://gridsome.org/&quot;&gt;Gridsome&lt;/a&gt;, and most recently &lt;a href=&quot;https://nuxtjs.org/&quot;&gt;Nuxt&lt;/a&gt;. All of these are excellent tools, and I highly recommend them. But one thing that is the same across all of them is that they are tied to their specific framework (React or Vue).&lt;/p&gt;
&lt;p&gt;Astro does away with that, and it&apos;s one of the things that really drew me to the framework. From their site:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s time to accept that the framework wars won’t have a winner — that’s why Astro lets you use any framework you want (or none at all). And if most sites only have islands of interactivity, shouldn’t our tools optimize for that? We’re not the first to ask the question, but we might be the first with an answer for every framework.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This caught my interest. The idea of &quot;framework wars&quot; having a winner never made sense to me. None of these tools - React, Vue, Svelte, Angular - need to be the overall winner to make developers productive. Having a winner at all would mean that innovation is stalled, at best. The fact that Astro allows you to utilize whichever framework is most comfortable means that it can adjust to whatever change comes in the future, and focus more on what it does best: building static assets.&lt;/p&gt;
&lt;p&gt;And so, as one does, I decided to rewrite my personal site from Nuxt to Astro.&lt;/p&gt;
&lt;h2&gt;Performance Woes&lt;/h2&gt;
&lt;p&gt;I should say, before going too much further, that I love Nuxt as a framework. I think it&apos;s an amazing tool, and I realize that, as I write this, we are days away from the release of Nuxt 3&apos;s public beta.&lt;/p&gt;
&lt;p&gt;That said, I&apos;ve been running a number of sites with Nuxt in static site mode, and each of them has some odd quirks that I&apos;ve never been able to fully work out. One site, a single page in the truest sense with only a bit of reactivity, was constantly reporting Typescript errors in VS Code. This was because the VS Code plugins (either Vetur or Volar) did not recognize that Nuxt&apos;s &lt;code&gt;asyncData&lt;/code&gt; method returned state to the Vue object. This is not Nuxt&apos;s fault, but it made things annoying.&lt;/p&gt;
&lt;p&gt;A second site (which is purely static assets, almost no JS interaction in the browser) had an issue that when code was updated, any content fetched with &lt;a href=&quot;https://content.nuxtjs.org/&quot;&gt;Nuxt&apos;s Content module&lt;/a&gt; would be missing after the hot module reload finished. I found a workaround, and it&apos;s not a huge deal, but it&apos;s annoying.&lt;/p&gt;
&lt;p&gt;My personal site uses data from multiple sources, including Github and a few podcasts RSS feeds. Using Nuxt, I was doing more data fetching on render than I wanted. This hadn&apos;t been an issue with either Gatsby or Gridsome, and I expect that if I had explored &lt;code&gt;buildModules&lt;/code&gt; more closely I could have found a solution. As it was, some pages had to fetch content on the client, and when that content is split between multiple endpoints, it made things slow.&lt;/p&gt;
&lt;p&gt;All of these sites, from the smallest to the largest, had one unifying issue: Lighthouse performance scores were never great. Below are my Lighthouse scores for this site before migrating from Nuxt:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/nuxt-lighthouse.png&quot; alt=&quot;Nuxt-based site Lighthouse scores. Performance: 57, Accessibility: 79, Best Practices: 93, SEO: 100&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This was done on my home page, on a fresh instance of Chrome with no plugins installed, in order to get the closest to a clean reading. The home page is loading a handful of images (language icons, my profile image), my latest blog post, and a few SVGs for social icons courtesy of Font Awesome. Data was also being fetched from Github&apos;s GraphQL API to get my profile&apos;s description, pinned repositories, and a few other details.&lt;/p&gt;
&lt;p&gt;Here&apos;s the breakdown of the performance score:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/nuxt-performance.png&quot; alt=&quot;Performance metrics. First contentful paint: 2.0s, Time to Interactive: 6.3s, Speed Index: 2.3s, Total Blocking Time: 150ms, Largest Contentful Paint: 7.4s, Cumulative Layout Shift: 0.47&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Of these scores, the Largest Contentful Paint and Time to Interactive stood out to me the most. This is a mostly static page, with a number of links and one button (to toggle dark mode). Why was Nuxt taking so long to be interactive?&lt;/p&gt;
&lt;p&gt;Looking at my Network requests, it looks to me like Nuxt is mostly fetching Javascript, and then spending its time executing it. I made a few notes to see what I was looking at. On a typical page load, I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;37 unique requests&lt;/li&gt;
&lt;li&gt;6.7MB of resources loaded (including images)&lt;/li&gt;
&lt;li&gt;Load time: 2.5s&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What could I do to cut down on all of this data fetching and Javascript execution?&lt;/p&gt;
&lt;h2&gt;Time for Less Javascript&lt;/h2&gt;
&lt;p&gt;This is where Astro caught my attention. On their home page, they say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a technology built on top of three different languages, the modern web seems to focus an awful lot on JavaScript. We don’t think it has to—and that’s certainly not a revolutionary concept.&lt;/p&gt;
&lt;p&gt;We’ll eagerly jump at the chance to sing JavaScript’s praises, but HTML and CSS are pretty great too. There aren’t enough modern tools which reflect that, which is why we&apos;re building Astro.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Astro is a framework that is focused primarily on fetching your data from whichever source or sources you use, injecting it into an HTML template, and building static assets from it. While Astro is built on Javascript, it doesn&apos;t focus on sending Javascript to the client. Any functionality you want can still be brought in, whether that&apos;s vanilla JS, React, Vue, or something else.&lt;/p&gt;
&lt;p&gt;This way of building a static site feels very comfortable and familiar to me. I started web development in HTML, CSS, and PHP, and avoided Javascript at all costs for many years (both before and after jQuery came onto the scene). Rendering HTML and CSS to the client is what I did, with some logic involved to perform simple tasks like displaying a list of elements or fetching data from a database. Using Astro, it&apos;s basically the same thing, just using Javascript instead of PHP.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example of my main blog page, which renders a list of blog posts. Astro uses a unique syntax that combines the look and feel of Markdown, JSX, and standard HTML. All build time Javascript is handled in a &apos;frontmatter&apos;-like block at the top of the file, and the static template is built below that.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
// Import components
import BaseLayout from &apos;../layouts/BaseLayout.astro&apos;
import BlogPostPreview from &apos;../components/BlogPostPreview.astro&apos;;

// Fetch posts
const allPosts = Astro.fetchContent(&apos;./blog/*.md&apos;)
    .filter(post =&amp;gt; 
        new Date(post.date) &amp;lt;= new Date()
    )
    .sort((a, b) =&amp;gt; 
        new Date(b.date).valueOf() - new Date(a.date).valueOf()
    );

// Render to HTML
---
&amp;lt;BaseLayout&amp;gt;
  &amp;lt;div class=&quot;flex flex-col lg:flex-row flex-wrap&quot;&amp;gt;
    {allPosts.map(post =&amp;gt; (
      &amp;lt;div class=&quot;w-full lg:w-1/2 xl:w-1/3 p-4&quot;&amp;gt;
        &amp;lt;BlogPostPreview item={post} /&amp;gt;
      &amp;lt;/div&amp;gt;
    ))}
  &amp;lt;/div&amp;gt;
&amp;lt;/BaseLayout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This may look familiar to someone who has used React before, with just a few oddities (no key on the mapped JSX? Extra dashes between the head and the return?), but it&apos;s important to remember that the result of this is pure HTML. No Javascript will ever be parsed on the client from this snippet. These components are all written with Astro&apos;s unique syntax, but the same is true when using React, Vue, or anything else: only static HTML and CSS would result from rendering this page.&lt;/p&gt;
&lt;p&gt;But what if you want to load some Javascript? What if you need some client side interaction?&lt;/p&gt;
&lt;h2&gt;Partial Hydration&lt;/h2&gt;
&lt;p&gt;Astro promotes the concept of &lt;a href=&quot;https://docs.astro.build/core-concepts/component-hydration&quot;&gt;Partial Hydration&lt;/a&gt;. From Astro&apos;s documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Astro generates every website with zero client-side JavaScript, by default. Use any frontend UI component that you’d like (React, Svelte, Vue, etc.) and Astro will automatically render it to HTML at build-time and strip away all JavaScript. This keeps every site fast by default.&lt;/p&gt;
&lt;p&gt;But sometimes, client-side JavaScript is required. This guide shows how interactive components work in Astro using a technique called partial hydration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most sites do not need to be fully controlled by Javascript. This concept of partial hydration leans into that. Using my personal site as an example, the only dynamic portion of the site is toggling dark mode. In the Nuxt version of the site, I was reliant on the Nuxt runtime to toggle light and dark mode. To be frank, that&apos;s overkill for a static site. I shouldn&apos;t have to render an entire SPA just to toggle dark mode, right?&lt;/p&gt;
&lt;p&gt;On their page about partial hydration, the Astro docs reference &lt;a href=&quot;https://jasonformat.com/islands-architecture/&quot;&gt;Jason Miller&apos;s blog post on the idea of an Islands Architecture&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In an “islands” model, server rendering is not a bolt-on optimization aimed at improving SEO or UX. Instead, it is a fundamental part of how pages are delivered to the browser. The HTML returned in response to navigation contains a meaningful and immediately renderable representation of the content the user requested.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rather than load an entire SPA to handle a small portion of functionality, Vue can target a much smaller section of the DOM, and render only the portion of the application that I need (in this case, a button and some JS to toggle dark mode). Vue supports this usage by default, but in the world of frameworks we tend to forget this. A number of recent episodes of Views on Vue have explored this concept, including &lt;a href=&quot;https://viewsonvue.com/using-vue-with-an-spa-with-ariel-dorol-vue-159&quot;&gt;using Vue without an SPA&lt;/a&gt; and &lt;a href=&quot;https://viewsonvue.com/building-micro-frontends-with-lawrence-almeida-vue-160&quot;&gt;building micro frontends&lt;/a&gt;. &lt;a href=&quot;https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/SOZREBYR36PUNFZXMIUBVAIOQI4N7PDU/&quot;&gt;The Wikimedia Foundation also uses Vue this way&lt;/a&gt;, rendering client-side functionality on top of an existing PHP monolith (&lt;a href=&quot;https://viewsonvue.com/adoping-vue-at-wikimedia-with-eric-gardner-vue-165&quot;&gt;listen to my discussion with Eric Gardner&lt;/a&gt; to learn more).&lt;/p&gt;
&lt;p&gt;When viewed in this way, performance is almost a byproduct of following best practices with Astro. For my personal site, I only needed a simple button to toggle dark mode. While I know this could be handled just as easily with vanilla JS, I wanted to try using Vue to build an island of functionality. Here&apos;s my Vue component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button
    class=&quot;dark-mode-button&quot;
    @click=&quot;toggleDarkMode&quot;
  &amp;gt;
    {{ isDark ? &quot;🌙&quot; : &quot;☀️&quot; }}
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang=&quot;ts&quot;&amp;gt;
export default {
  data() {
    return {
      isDark: localStorage.getItem(&quot;darkMode&quot;) === &quot;true&quot;,
    };
  },
  methods: {
    toggleDarkMode() {
      this.isDark = !this.isDark;
    },
  },
  watch: {
    isDark() {
      localStorage.setItem(&quot;darkMode&quot;, this.isDark);
      const html = document.querySelector(&quot;html&quot;);

      if (this.isDark) {
        html.classList.add(&quot;dark&quot;);
      } else {
        html.classList.remove(&quot;dark&quot;);
      }
    },
  }
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&apos;s an example of how I&apos;m using the component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
// Import the Vue component into an Astro component
import DarkModeButton from &apos;../components/DarkModeButton.vue&apos;
---
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;body&amp;gt;
    ... &amp;lt;!-- the rest of my template --&amp;gt;
    &amp;lt;!-- Display the Vue component --&amp;gt;
    &amp;lt;DarkModeButton client:only /&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, I&apos;m using Astro&apos;s &lt;code&gt;client:only&lt;/code&gt; directive. This tells Astro that it should be hydrated on the client, so that the Javascript will be executed. In this case, because the component is accessing the &lt;code&gt;window&lt;/code&gt; element, I want to make sure it doesn&apos;t get executed during buildtime. The best part is that, within the Astro component, it just asks like a normal component that can accept props.&lt;/p&gt;
&lt;p&gt;Astro has a number of renderers, and at the recent &lt;a href=&quot;https://youtu.be/gpTbH469Qog?t=5756&quot;&gt;Vue Contributor Days&lt;/a&gt;, Fred Schott said that first-class Vue support is very important to the Astro team, and that it comes out of the box when working with Astro. You do need to add the renderer to your Astro configuration, but that&apos;s all that is required to enable Vue components.&lt;/p&gt;
&lt;h2&gt;The Results&lt;/h2&gt;
&lt;p&gt;Rewriting my personal site took about a week. Most of my templating was migrated from Vue to Astro components (although, as noted above, this wasn&apos;t a requirement to make the switch), with a couple Vue components for interactivity. The migration itself went very smoothly, especially since Astro support PostCSS (and therefore Tailwind) via a plugin for Snowpack. The benefits of prefetching the data and generating static HTML were obvious very early on, and the ability to mix basic HTML and CSS with Vue components was very straightforward.&lt;/p&gt;
&lt;p&gt;After I finished and deployed, I ran Lighthouse on the finished migration. Here are the results:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/astro-lighthouse.png&quot; alt=&quot;Astro-based site Lighthouse scores: Performance: 100, Accessibility: 95, Best Practices: 100, SEO: 100&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And here&apos;s the performance results:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/astro-performance.png&quot; alt=&quot;Performance metrics. First contentful paint: 1.6s, Time to Interactive: 1.6s, Speed Index: 1.6s, Total Blocking Time: 0ms, Largest Contentful Paint: 1.6s, Cumulative Layout Shift: 0&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Much better! Because everything is being loaded as HTML and CSS, rather than utilizing a JavaScript framework to render the page, everything is much faster.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Astro is a relatively new tool for building static sites, but it&apos;s already gaining a lot of traction. Astro recently won the &lt;a href=&quot;https://www.netlify.com/blog/2021/10/06/jammies-award-winners-2021/&quot;&gt;Ecosystem Innovation Award&lt;/a&gt; as part of Jamstack Conf 2021. From the linked page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This year’s ecosystem innovation award goes to Astro, an innovative Jamstack platform that lets you build faster websites with less client-side JavaScript. They make it possible for developers to build fully functional sites with any framework of their choice or none at all. Astro offers the best of both worlds when it comes to lightweight static sites generators like 11ty and bundle-heavy alternatives like Next and Svelte Kit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;m really excited to see where Astro goes in the future. One item on their roadmap is to include server-side rendering, which I&apos;m very excited for personally. I look forward to seeing what else comes out of this very interesting project.&lt;/p&gt;
&lt;p&gt;Feel free to look at &lt;a href=&quot;https://github.com/lindsaykwardell/lindsaykwardell&quot;&gt;the repository for this site&lt;/a&gt; to view the code, and compare it against the Nuxt equivalent (in the Git history). If you want to learn more about Astro, check out their site at &lt;a href=&quot;https://astro.build&quot;&gt;astro.build&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Building Web Components with Vue 3.2</title><link>https://www.thisdot.co/blog/building-web-components-with-vue-3-2</link><guid isPermaLink="true">https://www.thisdot.co/blog/building-web-components-with-vue-3-2</guid><pubDate>Tue, 05 Oct 2021 14:14:09 GMT</pubDate><content:encoded/></item><item><title>Vue 3.2 - Using Composition API with Script Setup</title><link>https://www.thisdot.co/blog/vue-3-2-using-composition-api-with-script-setup</link><guid isPermaLink="true">https://www.thisdot.co/blog/vue-3-2-using-composition-api-with-script-setup</guid><pubDate>Thu, 26 Aug 2021 13:48:25 GMT</pubDate><content:encoded> </content:encoded></item><item><title>TC39 - How Changes are Made to JavaScript</title><link>https://www.thisdot.co/blog/tc39-how-changes-are-made-to-javascript</link><guid isPermaLink="true">https://www.thisdot.co/blog/tc39-how-changes-are-made-to-javascript</guid><pubDate>Wed, 18 Aug 2021 19:53:54 GMT</pubDate><content:encoded> </content:encoded></item><item><title>Progressive Web Apps and Mobile Apps</title><link>https://www.thisdot.co/blog/progressive-web-apps-and-mobile-apps</link><guid isPermaLink="true">https://www.thisdot.co/blog/progressive-web-apps-and-mobile-apps</guid><pubDate>Thu, 22 Jul 2021 18:42:10 GMT</pubDate><content:encoded> </content:encoded></item><item><title>Getting Started with Tailwind in Vue</title><link>https://www.thisdot.co/blog/getting-started-with-tailwind-in-vue</link><guid isPermaLink="true">https://www.thisdot.co/blog/getting-started-with-tailwind-in-vue</guid><pubDate>Mon, 19 Jul 2021 18:07:25 GMT</pubDate><content:encoded> </content:encoded></item><item><title>Vue 3.1 - Official Migration Build from Vue 2 to 3</title><link>https://thisdot.co/blog/vue-3-1-official-migration-build-from-vue-2-to-3</link><guid isPermaLink="true">https://thisdot.co/blog/vue-3-1-official-migration-build-from-vue-2-to-3</guid><pubDate>Wed, 30 Jun 2021 19:31:07 GMT</pubDate><content:encoded> </content:encoded></item><item><title>Custom Composable Methods with Vue 3</title><link>https://thisdot.co/blog/custom-composable-methods-with-vue-3</link><guid isPermaLink="true">https://thisdot.co/blog/custom-composable-methods-with-vue-3</guid><pubDate>Thu, 20 May 2021 21:05:08 GMT</pubDate><content:encoded> </content:encoded></item><item><title>Provide/Inject API With Vue 3</title><link>https://thisdot.co/blog/provide-inject-api-with-vue-3</link><guid isPermaLink="true">https://thisdot.co/blog/provide-inject-api-with-vue-3</guid><pubDate>Mon, 10 May 2021 20:30:59 GMT</pubDate><content:encoded> </content:encoded></item><item><title>Vue 3 Composition API - watch and watchEffect</title><link>https://labs.thisdot.co/blog/vue-3-composition-api-watch-and-watcheffect</link><guid isPermaLink="true">https://labs.thisdot.co/blog/vue-3-composition-api-watch-and-watcheffect</guid><pubDate>Thu, 29 Apr 2021 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Computing Application State in Vue 3</title><link>https://labs.thisdot.co/blog/computing-application-state-in-vue-3</link><guid isPermaLink="true">https://labs.thisdot.co/blog/computing-application-state-in-vue-3</guid><pubDate>Tue, 27 Apr 2021 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Improve User Experience in Vue 3 with Suspense</title><link>https://labs.thisdot.co/blog/improve-user-experience-in-vue-3-with-suspense</link><guid isPermaLink="true">https://labs.thisdot.co/blog/improve-user-experience-in-vue-3-with-suspense</guid><pubDate>Wed, 24 Mar 2021 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Vue 3 Composition API - &apos;ref&apos; and &apos;reactive&apos;</title><link>https://labs.thisdot.co/blog/vue-3-composition-api-ref-and-reactive</link><guid isPermaLink="true">https://labs.thisdot.co/blog/vue-3-composition-api-ref-and-reactive</guid><pubDate>Tue, 23 Mar 2021 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Introduction to Vite - Next Generation Frontend Tooling</title><link>https://labs.thisdot.co/blog/introduction-to-vite-next-generation-frontend-tooling</link><guid isPermaLink="true">https://labs.thisdot.co/blog/introduction-to-vite-next-generation-frontend-tooling</guid><pubDate>Tue, 16 Mar 2021 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Build a Static Comment System</title><link>https://lindsaykwardell.com/blog/git-comment-system/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/git-comment-system/</guid><pubDate>Sun, 15 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;From Wordpress to Jamstack&lt;/h2&gt;
&lt;p&gt;Back when I first started my own blog, I did what many still do today and deployed a Wordpress site. Honestly, Wordpress is great. If you&apos;re looking into setting up your own site, it&apos;s a fine option! The main problem I had with it, however, was relying on another service to host my posts, my images, everything. What if my hosting provider were to shut down? How could I migrate from their MySQL database to another easily? What would I do with all of my content?&lt;/p&gt;
&lt;p&gt;This actually happened to me, when I needed to migrate from one provider to another. The solution - abandon everything, and start from scratch. A migration wasn&apos;t possible to my new host, so I copied everything into a text file and started over on the site.&lt;/p&gt;
&lt;p&gt;Then I learned about Gatsby, and that I could have a static site where my blog posts are all stored in text files. That sounds like a win! I could control my posts, my site, my content, and host it anywhere. This sounded exactly like what I wanted to do. I looked at headless Wordpress, but decided I wanted full control of the site. I built out a first version of the site with Gatsby, deployed it to Netlify, and life was good.&lt;/p&gt;
&lt;p&gt;Except...&lt;/p&gt;
&lt;p&gt;What about comments?&lt;/p&gt;
&lt;h2&gt;Static Comments??&lt;/h2&gt;
&lt;p&gt;I&apos;ve never had a super popular blog, but having a comment system felt important to build a complete blog. The options that are out there are... okay, but most of them didn&apos;t actually match what I was going for. I settled on Disqus, but the fact that I couldn&apos;t host it, plus the tie-in to another service meant that it felt antithetical to hosting a static site.&lt;/p&gt;
&lt;p&gt;After doing some research, I found &lt;a href=&quot;https://staticman.net/&quot;&gt;Staticman&lt;/a&gt;. Quoting from their homepage, &quot;Staticman handles user-generated content for you and transforms it into data files that sit in your GitHub repository, along with the rest of your content.&quot; This concept spoke to me. I did some research into using this approach, but at the time, it looked like the service had grown too fast, and comments were processing too slowly, if at all. Hopefully they&apos;ve fixed it by now, but again, it&apos;s another service to rely on.&lt;/p&gt;
&lt;p&gt;All of this research, however, led me to a decision. I&apos;m a developer; I can build this myself!&lt;/p&gt;
&lt;h2&gt;Jamstack to the Rescue!&lt;/h2&gt;
&lt;p&gt;My goals for this project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accept input from a user&lt;/li&gt;
&lt;li&gt;Process that into a text file&lt;/li&gt;
&lt;li&gt;Commit that text file into a Github repository.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;m already hosted on Netlify, so accepting user input is straightforward. Netlify offers form submission (&lt;a href=&quot;https://www.netlify.com/products/forms/&quot;&gt;read more here&lt;/a&gt;). In short, by adding some basic attributes to a form, you can enable a POST request to your site that Netlify will capture and process. I&apos;m using Vue, so I turned to &lt;a href=&quot;https://vueformulate.com/&quot;&gt;Vue Formulate&lt;/a&gt; to build the form, and &lt;a href=&quot;https://vuetensils.stegosource.com/&quot;&gt;Vuetensils&lt;/a&gt; for an alert on success/failure. Unfortunately this doesn&apos;t work nicely with Netlify, so I had to add the form in a standard way in order for Netlify to pick it up and build the POST endpoint. A simple compromise.&lt;/p&gt;
&lt;p&gt;Below is the code for Netlify to pick up the form. Feel free to just use a basic form element if you want, I decided to go with Vue Formulate for the added validation and submission features.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;form
  data-netlify=&quot;true&quot;
  data-netlify-honeypot=&quot;bot-field&quot;
  name=&quot;new-comment&quot;
  class=&quot;hidden&quot;
&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;form-name&quot; /&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;postTitle&quot; /&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;postPath&quot; /&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;author&quot; /&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;email&quot; /&amp;gt;
  &amp;lt;input type=&quot;hidden&quot; name=&quot;message&quot; /&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great, I&apos;ve got my form, and it&apos;s submitting to Netlify. But how can I access that data to submit to Github?&lt;/p&gt;
&lt;p&gt;Luckily, Netlify has another great feature: &lt;a href=&quot;https://www.netlify.com/products/functions/&quot;&gt;Serverless Functions&lt;/a&gt;! In short, they allow you to create AWS Lambda functions that they will host, and you don&apos;t need to create an AWS account to do anything.&lt;/p&gt;
&lt;p&gt;Here&apos;s a basic example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exports.handler = async ( event , context ) =&amp;gt; { 
  return { 
    statusCode: 200, 
    body: &quot;Success!&quot; 
  }; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition to writing arbitrary serverless functions, Netlify provides a number of hooks to catch events that would go to their APIs, such as Identity or Forms. &lt;a href=&quot;https://docs.netlify.com/functions/trigger-on-events/&quot;&gt;You can read more about them here&lt;/a&gt;. In this case, we want to create a function called &lt;code&gt;submission-created.js&lt;/code&gt;, which will receive an object called &lt;code&gt;payload&lt;/code&gt; in the event body. This payload will contain all of our form information. We can then use that to generate a markdown file for the comment.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const axios = require(&apos;axios&apos;)
const uuid = require(&apos;uuid&apos;).v4
const dayjs = require(&apos;dayjs&apos;)
const crypto = require(&apos;crypto&apos;)
const utc = require(&apos;dayjs/plugin/utc&apos;)

dayjs.extend(utc)

exports.handler = (event, context, callback) =&amp;gt; {
  const payload = JSON.parse(event.body).payload
  const { postTitle, postPath, author, email, message } = payload.data

  const filePath = `content/comments/${uuid()}.md`
  const content = `---
postPath: &quot;${postPath}&quot;
date: ${dayjs().utc().format(&apos;YYYY-MM-DD HH:mm:ss&apos;)}
author: &quot;${author}&quot;
authorId: &quot;${crypto.createHash(&apos;md5&apos;).update(email).digest(&apos;hex&apos;)}&quot;
---
${message}`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a quick aside - you can always just use a generic serverless function for this step. I went with Netlify Forms and handling the event because Netlify by default applies spam filtering to the form input. You can also add a bot field (see the above HTML snippet where it says &lt;code&gt;data-netlify-honeypot&lt;/code&gt;) to get additional checks on form submission. Rather than build in a call to something like Akismet, or import my own spam filter, I felt this was the simplest way forward. It felt a bit like a compromise on my &apos;I own everything&apos; take, but if I have to move platforms I can rebuild it fairly easily.&lt;/p&gt;
&lt;p&gt;All right, we now have our form hooked up and a serverless function to capture the data. Where do we save this? Well, anywhere we want, really! In my case, I wanted to store this data in Github. For this use case, &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/rest&quot;&gt;Github offers a RESTful API&lt;/a&gt; where a developer can interact with a given repository. In this case, it allows me to commit a new file into a branch of my blog.&lt;/p&gt;
&lt;p&gt;For this example, I will use Axios, but feel free to use &lt;code&gt;isomorphic-fetch&lt;/code&gt; or your preferred fetch library.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const url =
    &apos;https://api.github.com/repos/lindsaykwardell/lindsaykwardell/contents/&apos; +
    filePath

  axios
    .put(
      url,
      {
        message: `New comment on ${postTitle}`,
        branch: &apos;new-comments&apos;,
        author: {
          name: &apos;Lindsay Wardell&apos;,
          email: process.env.COMMIT_EMAIL,
        },
        committer: {
          name: &apos;Lindsay Wardell&apos;,
          email: process.env.COMMIT_EMAIL,
        },
        content: Buffer.from(content).toString(&apos;base64&apos;),
      },
      {
        headers: {
          Authorization: `token ${process.env.GITHUB_API_TOKEN}`,
        },
      }
    )
    .then((res) =&amp;gt;
      callback(null, {
        statusCode: 200,
        body: JSON.stringify({ msg: &apos;Your comment has been submitted!&apos; }),
      })
    )
    .catch((err) =&amp;gt;
      callback(null, {
        statusCode: 500,
        body: JSON.stringify({ msg: &apos;An error occurred!&apos;, err }),
      })
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, any form submission from our site will go to Netlify, pass to this function, and get committed to our Github repository. For my case, I created a separate branch for new comments, just in case any spam filtering still needs to be done.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Congratulations! You now have complete control over your comments on a static site. This should work with any static site generator. My goal was to have complete control over the contents of my site, so I can take it with me wherever I want. While I do feel a bit tied into Netlify, I feel that it&apos;s a worthy compromise, considering all of the data is mine at the end of the day.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lindsaykwardell/lindsaykwardell&quot;&gt;Here&apos;s a link&lt;/a&gt; to my site&apos;s Github repository in case you want to look at the full source code.&lt;/p&gt;
&lt;p&gt;Stay safe!&lt;/p&gt;
</content:encoded></item><item><title>Introduction to Deno with http_wrapper</title><link>https://lindsaykwardell.com/blog/intro-to-http-wrapper/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/intro-to-http-wrapper/</guid><pubDate>Tue, 26 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m a big fan of &lt;a href=&quot;https://deno.land&quot;&gt;Deno&lt;/a&gt;, the new Javascript runtime by the creator of Node. I love that it is built with Typescript in mind, reducing the amount of configuration required to get work done. I like that packages (or modules, as they are referred to) can be imported via URL, and don&apos;t require a package manager. I also love how &lt;em&gt;fast&lt;/em&gt; it is to start a Deno project for development.&lt;/p&gt;
&lt;p&gt;For the uninitiated:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Secure by default. No file, network, or environment access, unless explicitly enabled.&lt;/li&gt;
&lt;li&gt;Supports TypeScript out of the box.&lt;/li&gt;
&lt;li&gt;Ships only a single executable file.&lt;/li&gt;
&lt;li&gt;Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).&lt;/li&gt;
&lt;li&gt;Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a web developer, my first question was, &quot;Okay, how do I start an HTTP server?&quot;&lt;/p&gt;
&lt;p&gt;It turns out, the built-in methods to handle this aren&apos;t too difficult to figure out:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { serve } from &quot;https://deno.land/std@0.53.0/http/server.ts&quot;;

const s = serve({ port: 8000 });

for await (const req of s) {
  req.respond({ body: &quot;Hello World\n&quot; });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Want to respond differently for different endpoints? No big deal!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import { listenAndServe } from &quot;https://deno.land/std@0.53.0/http/server.ts&quot;;

const s = listenAndServe({ port: 8000 }, (req: ServerRequest) =&amp;gt; {
  if (req.url === &quot;/example&quot;) {
    req.respond({ body: &quot;You found the example endpoint!&quot; })
  } else {
    req.respond({
      status: 404
    })
  }
});

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty straightforward. However, this method of writing an API could get pretty verbose: multiple if-statements, no simple solution to break out different endpoints into their own scope or module, and no clear way to scale your application as your routes grow. A large application could become unwieldy pretty fast using this format to write your API.&lt;/p&gt;
&lt;p&gt;There are already a lot of libraries out there for managing routes (Oak and Drash come to mind). From what I can tell, though, these libraries wrap the default &lt;code&gt;ServerRequest&lt;/code&gt; object in their own formats, making them incompatible with other built-in methods (like web sockets). I really love how simple it is to get started, there must be a way to just wrap the default &lt;code&gt;listenAndServe&lt;/code&gt; and &lt;code&gt;ServerRequest&lt;/code&gt; objects in a router-based solution.&lt;/p&gt;
&lt;p&gt;And so I wrote &lt;code&gt;http_wrapper&lt;/code&gt;, a simple wrapper around the above code. Let&apos;s implement the above example using &lt;code&gt;http_wrapper&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Server, Router } from &quot;https://deno.land/x/http_wrapper@v0.3.0/mod.ts&quot;;

const app = new Server();

const router = new Router();

router.get(&quot;/example&quot;, (req: ServerRequest) =&amp;gt; {
  req.respond({ body: &quot;You found the example endpoint!&quot; });
});

app.use(router.routes);

app.start({ port: 8000 });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the above in a file (say index.ts), and run it as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno run --allow-net --allow-read index.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: Deno has a secure runtime, so whenever you want to reach out of its sandbox you need to explicitly allow it to.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http_wrapper&lt;/code&gt; adds two new classes:&lt;/p&gt;
&lt;h2&gt;Server&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Server&lt;/code&gt; class handles calling &lt;code&gt;listenAndServe()&lt;/code&gt;, and correctly determining which endpoint was called. When the server is started (with &lt;code&gt;app.start()&lt;/code&gt;), internally the class calls &lt;code&gt;listenAndServe()&lt;/code&gt; and begins checking each request against its list of routes and endpoints. The config object that is passed into &lt;code&gt;app.start()&lt;/code&gt; is &lt;code&gt;Deno.ListenOptions&lt;/code&gt;, the same as for &lt;code&gt;listenAndServe()&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Router&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Router&lt;/code&gt; is where the fun begins. This class is meant to handle all of your API needs. It takes a constructor of a string, which is the base route for that router:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Creating the /example route
const router = new Router(&quot;/example&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Endpoints can then be added to routes for different HTTP methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GET&lt;/li&gt;
&lt;li&gt;POST&lt;/li&gt;
&lt;li&gt;PUT&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;OPTIONS&lt;/li&gt;
&lt;li&gt;PATCH&lt;/li&gt;
&lt;li&gt;HEAD&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const router = new Router(&quot;/example&quot;);

// Add a GET request
router.get(&quot;/&quot;, req =&amp;gt; {
  req.response({body: &quot;You found me!&quot; });
})

// Add a POST request
router.post(&quot;/&quot;, req =&amp;gt; {
  req.response({
    status: 204,
    headers: new Headers({
      &apos;content-type&apos;: &apos;application/json&apos;
    }),
    body: JSON.stringify({
      msg: &quot;Saved!&quot;
    })
  });
});

export const router;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: Currently, there is no support for path parameters&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Putting it together&lt;/h2&gt;
&lt;p&gt;Once you have defined your routes, you can bring them into the &lt;code&gt;Server&lt;/code&gt; by using &lt;code&gt;app.use()&lt;/code&gt;. Also, if you have static files you would like to deploy with your app, you can define the static file folder and endpoint with &lt;code&gt;app.static()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { route1 } from &quot;./Router1.ts&quot;;

const app = new Server();

app.use(route1.routes); // Router.routes is a getter, so no function calls are required
app.static(&quot;static&quot;, &quot;/&quot;);

app.start({ port: 8000 });

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congratulations, you are now hosting an app with two endpoints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http://localhost:8000/example (various string/JSON responses)&lt;/li&gt;
&lt;li&gt;http://localhost:8000/ (static files)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The best part? For development, it takes about a second to start up!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno run --allow-net --allow-read index.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can check out &lt;code&gt;http_wrapper&lt;/code&gt; here: &lt;a href=&quot;https://deno.land/x/http_wrapper&quot;&gt;Deno.land&lt;/a&gt;. The project is MIT licensed, so feel free to use it and report any issues you find.&lt;/p&gt;
&lt;p&gt;Next time, I&apos;ll talk about how to incorporate &lt;code&gt;http_wrapper&lt;/code&gt; with a Vue 3 SPA to create a chat room, while keeping the ~1 second startup for development.&lt;/p&gt;
&lt;p&gt;Take care!&lt;/p&gt;
</content:encoded></item><item><title>When I Grow Up...</title><link>https://lindsaykwardell.com/blog/when-i-grow-up/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/when-i-grow-up/</guid><pubDate>Mon, 18 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;&quot;What do you want to be when you grow up?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This age-old question, asked of children before they can know what &apos;grow up&apos; even means exactly, often results in answers both cute and curious. Some of the answers I remember giving include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Astronaut&lt;/li&gt;
&lt;li&gt;Mathematician &lt;em&gt;(due to a TV show we watched in 1st grade)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Teacher&lt;/li&gt;
&lt;li&gt;Office Max employee &lt;em&gt;(well, I got to do that one!)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Author&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One theme popped out from an early age, though: whatever I was going to do, it was going to revolve around computers.&lt;/p&gt;
&lt;p&gt;Software development has always been something I was interested in. I remember being awed by an early version of Excel at a science fair, and how you could program it to do whatever you wanted. A friend and I explored making fan games with GUI game designers like The Games Factory or Multimedia Fusion. We spent countless hours trying to put together fan games for Sonic the Hedgehog and other characters, or trying to make our own. I went to Cybercamps, a computer camp which offered weeklong courses on game development, programming, graphic design (Photoshop), and web design.&lt;/p&gt;
&lt;p&gt;In 2002, we realized that we could look at the source code for a website, change it, save it, and open it in our browsers. It wasn&apos;t long after that that I got some books on HTML4 and CSS2, and started writing my own code. We made websites hosted on Freewebs, and I was able to use the HTML sections they offered to do some things myself.&lt;/p&gt;
&lt;p&gt;From there, I started setting up websites for gaming clans I was a part of. I would download an HTML template, put it on the server my family rented, and &lt;em&gt;bam&lt;/em&gt;! Website for the clan. It felt great. At the time, I wrote for a school assignment, &quot;What I want for a career is to be a website designer.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/when-i-grow-up.jpg&quot; alt=&quot;School paper stating I want to be a website designer&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In 2005, my friend started a new fan game based on Shadow the Hedgehog. As a &quot;company&quot; making the game, he came up with the title, &quot;Blackarms Studios&quot; (a reference to the race of aliens in the Shadow the Hedgehog game, the Black Arms). And so, I made a website. We hosted it on Yagaboosh.com, which was derived from a random word/sound that I made as a child one day. We set up a community forum using ProBoards, and others from the fan game community made their way in. Later, my friend shifted to focus on making videos for YouTube. At one point, our channel was regularly reaching YouTube&apos;s top 100. Bear in mind, this was back in the day when YouTube videos couldn&apos;t be longer than 10 minutes.&lt;/p&gt;
&lt;p&gt;Other members of the community wanted websites, so I started working with them to create and host something based on the template I had made for BAS. I then billed myself as running &quot;The Yagaboosh Network&quot; (everything was a URL based off of my original website, Yagaboosh.com) It was a blast. I dove straight into using CSS for styling the website, while ensuring the structure functioned without a stylesheet. I used CSS hover effects to generate dropdown menus. I avoided Javascript at the time, because it seemed intimidating and there were general concerns about compatibility across browsers.&lt;/p&gt;
&lt;p&gt;Sadly, I don&apos;t have the original sites any more, but the Wayback Machine managed to archive some of my efforts. Looks like a large portion of the styling was misssed, though.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/blackarms-studios.png&quot; alt=&quot;Screenshot from the Wayback Machine of Blackarms Studios&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The community was amazing. It was probably my favorite part of the site. But I was never happy was using ProBoards, because it didn&apos;t integrate with the main site and I couldn&apos;t style it nearly as well. So in 2007, we migrated from ProBoards to a self-hosted solution with PHPBB. Over the following months, I tweaked the PHPBB theme to treat the global announcements as a blog, and finally my dream of merging the two was complete. This was my first real experience with &quot;back-end web development&quot; - I was writing my own PHP code, as well as the special PHPBB syntax, in order to make my own product.&lt;/p&gt;
&lt;p&gt;The community slowly faded away, as things do. Our peak was in 2008, according to the stat counter I placed on the site (still accessible, thanks again to the Wayback Machine). Considering we were a small community site, I&apos;m fairly proud of these numbers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/blackarms-studios-stats.png&quot; alt=&quot;Stat counter statistic for Blackarms Studios&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Activity on the site dropped off after 2009. The YouTube channel had been repeatedly banned for violating DMCA regulations (this was before video game companies realized that fan content was a good thing). Part of this decline was because of my focus on the web technologies, rather than supporting the community, but also because we had been around for 4-5 years. The old members moved on, while no new members were joining.&lt;/p&gt;
&lt;p&gt;We kept talking about what to do for the website until 2011, when I left for Brazil for two years as a missionary. During the mission, I was appointed financial secretary, which meant I worked from 10 to 5 in the mission office. Within my first month, my web development came up in conversation, and I gave an example of how easy it was to get started making a website. I kept playing with it when I had a spare moment, and was able to arrange a public release of the website.&lt;/p&gt;
&lt;p&gt;The site was written as a custom CMS, because I didn&apos;t have access to tool like Wordpress or PHPBB. It was my first attempt at a PHP application. The banner at the top was randomly selected from between 5 options. A blog was included to display the mission&apos;s monthly message. Other features included fading between images using Javascript (I believe I used jQuery) and a scroll on the left with &quot;Scriptures of Power&quot;. The scroll in the screenshot below is sadly broken, but it involved some complicated PHP code that allowed the scroll to animate downward to display all of the text. This was complicated because to achieve the animation, I was counting the number of characters in the quote, and then generating the CSS within PHP.&lt;/p&gt;
&lt;p&gt;Thanks again to the Wayback Machine for the archived version of the site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/missao-brasil-goiania.png&quot; alt=&quot;Goiania Brazil Mission website&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It was my first custom project, and I was very excited about it. But as with many side projects, I was unsatisfied with some of the initial decisions I had made. These constraints made it difficult to expand on the site, and I felt there had to be a better way. So I started over, updating the site as needed while I worked in the office, but truly focusing on the new project - Yagaboosh.&lt;/p&gt;
&lt;p&gt;Yagaboosh was going to be a completely flexible CMS, built around a plugin system. Everything was going to be plugins, including the blog functionality, so that features could be updated separately or ignored if not wanted. This concept had formed in my head from the years of working on Wordpress and PHPBB - wouldn&apos;t it be great if I could just pick and choose which tools I used, and put them together in a cohesive way?&lt;/p&gt;
&lt;p&gt;I feel like the project had a lot of potential, but eventually I was transferred away from the office. I took the source code with me, but when I got home in October 2013 I didn&apos;t feel like working on it all that much. I did some minor adjustments in 2014, but then I got married, and life priorities had to be shifted around. I later did some further updates, mostly to the theme (I switched to using Bootstrap instead of a custom theme), and pushed it to deployment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/yagaboosh.png&quot; alt=&quot;New Yagaboosh install page&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Between 2014 and 2017, I didn&apos;t do much in the way of programming. I worked as a tech specialist at Office Depot while studying for my Associate&apos;s Degree without really knowing what I wanted to do. In 2015, our daughter was born, I started working on my Bachelor&apos;s degree, and I switched jobs to be an IT Manager at Lindsey Forwarders.&lt;/p&gt;
&lt;p&gt;Most of my time there was working there was spent on day-to-day operations, and for a time I filled the role of Operations Manager rather than focusing on IT. But I did get to deploy a PHP-based time tracking system, and did some work on an existing internal application made using Magic from around 1995. I also got to make an Excel spreadsheet for tracking rates.&lt;/p&gt;
&lt;p&gt;In 2017, the business was sold to a son-in-law, and I began to prepare to leave the company. A friend recommended attending the Tech Academy, a programming boot camp in Portland, OR. I made an arrangement to work part time while attending the boot camp, and in return I would start developing a new application to replace the existing one from 1995. I attended the Tech Academy from November 2017 to February 2018, learning the more professional side of development I had never had due to my self-taught method up until then.&lt;/p&gt;
&lt;p&gt;At the Tech Academy, I learned about Git, Javascript/jQuery, and how to build server-side applications that weren&apos;t just based on PHP scripts - specifically, ASP.NET and C# MVC. I also got to experiment with Knockout.js, and saw the power of a single-page application. These methods and technologies would prove essential to my career.&lt;/p&gt;
&lt;p&gt;For the rest of 2018, I switched between helping the new owner adjust to the company he had bought and working on new development projects. The two main projects were a new Wordpress site for the company, and the Online Portal that was intended to replace the old desktop app. The Wordpress site used an existing theme, but I was able to use my skills from the Tech Academy and past experience to style it how I wanted. I also got to make a new logo for the company, using Photoshop.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/lindsey-forwarders.png&quot; alt=&quot;Lindsey Forwarders home page&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Online Portal was much more exciting. I started a prototype in 2017 while still attending the Tech Academy. The prototype was built using PHP and jQuery, and applied as close to MVC functionality as I could manage. There were dynamic Javascript imports depending on the page, REST endpoints for performing actions, and dynamic page rerenders as tasks were done. But as with the mission blog, it wasn&apos;t scalable. On top of that, there was a severe performance hit when querying the database via PHP that I couldn&apos;t figure out. Looking back, I think I was using an older version of PHP which caused it, but I&apos;m still not certain.&lt;/p&gt;
&lt;p&gt;After I completed the Tech Academy, I started trying to learn React, but was quickly overwhelmed by the number of new concepts I was trying to wrap my head around. Instead of continuing with React, I switched to Vue, and found a Javascript framework that was much more comfortable to get started with. I also started learning Node.js, and I realized how powerful it was to use one language for the entire application. The shift between PHP and Javascript during the prototyping phase had been annoying, especially since both are dynamic languages.&lt;/p&gt;
&lt;p&gt;In May/June 2018, I built a new prototype using Vue and Node.js. I just replicated one page, the one where the database query was too slow. The result - a 4x performance increase, at minimum; often it was even more than that. I made the case to my boss to allow the shift from PHP/jQuery to Node/Vue, and he agreed. By this point, we had been in a closed pilot with a few customers, so we made the decision to support the old version while working on the new one. It was only a short time later that we were ready to switch. I was very impressed with the speed of development on the Vue/Node application compared to PHP/jQuery.&lt;/p&gt;
&lt;p&gt;I continued working on the new application through the year, and into the start of 2019. However, in February 2019, I was contacted by a recruiter to see if I was interested in a position with Daimler Trucks North America, building a new project using Vue and Node. The interview was great, and they offered me the job. I immediately accepted. I started at DTNA in March, 2019.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mp-elevator.jpg&quot; alt=&quot;Me in the elevator at my new job&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Working in a large corporation as a developer was a major shift from anything I had done before. I worked under my project manager (the same with whom I interviewed), along with a BSA assigned to my project. My contact information was intentionally kept from the app owner, so that all communication was routed through the BSA. We had regular meeting with architecture and CI/CD teams. I had to coordinate between other teams to call APIs needed to make our application work. All things I had either done myself, or had documentation to follow.&lt;/p&gt;
&lt;p&gt;I worked on that first project from March to June, 2019. It was an internal tool, so it wouldn&apos;t have a large user base. Also, once I had the position, I learned that I would need to write the application using Java, not Node. I had never written a single line of Java. I asked for the time to learn sufficient to write the application, which I received, and was able to complete the project a month ahead of time.&lt;/p&gt;
&lt;p&gt;This turned out to be a good thing, because I was transferred to a new project where a developer was being transferred. This project was no small internal app - I&apos;m actually not going to talk about it much because I&apos;m not sure what I can say officially. I will say that, in many ways, it is the culmination of everything I have learned up until this point - database design, application development, working with teams, using version control and branches, and more. When the application goes live, and is used by its intended audience, it will be my code, along with that of my fellow developers, that is making magic happen.&lt;/p&gt;
&lt;p&gt;There is more to the story - side projects, frustration, growing pains, and more. The path I have taken to get here was never straightforward. But in the end, I am doing what I love.&lt;/p&gt;
&lt;p&gt;In 2002, I dreamed of being a website designer. Today, I know that I made that happen.&lt;/p&gt;
&lt;p&gt;So now, I&apos;ll ask you:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;What do you want to be when you grow up?&quot;&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>New Website</title><link>https://lindsaykwardell.com/blog/new-website/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/new-website/</guid><pubDate>Sun, 04 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have never been satisfied with Wordpress as my platform of choice for my website. Partially because I am not a Wordpress developer, and partially because I knew I could build something closer to what I wanted using Gatsby. For those interested, Gatsby is a way to build static websites that gives me full control over the deployment of my site. Out of the box, it is faster than Wordpress, and much more developer-friendly.&lt;/p&gt;
&lt;p&gt;But I know most of you who read this don&apos;t come here for technical, programming topics. I will warn you, though, that there&apos;s a good chance I&apos;ll be writing about more of those as well. This post, however, is meant to inform that the URLs from past posts will no longer work, and if you navigate the website you may notice it looks a bit different and works a bit better. Please let me know of any bugs or issues that you find, as I&apos;m trying to polish this site as best I can.&lt;/p&gt;
&lt;p&gt;Also, I have been very busy recently, which is why there have been fewer posts. I am planning on writing more soon, following the completion of my bachelor&apos;s degree. I have one last test, and then I am free from school! Hooray!&lt;/p&gt;
&lt;p&gt;Thank you for sticking around, and I hope you have a great rest of your summer.&lt;/p&gt;
</content:encoded></item><item><title>Where Do You Turn?</title><link>https://lindsaykwardell.com/blog/where-do-you-turn/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/where-do-you-turn/</guid><pubDate>Fri, 03 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Where do you turn, when faith grows cold? &lt;br /&gt;
When hidden places freeze over, and fill with fear,&lt;br /&gt;
And all you knew is but dust and moonlight,&lt;br /&gt;
Where do you turn?&lt;/p&gt;
&lt;p&gt;When bright fields of golden sun turn to distant memory,&lt;br /&gt;
And words of comfort have lost their power,&lt;br /&gt;
Where do you turn?&lt;/p&gt;
&lt;p&gt;When all the world has turned to night,&lt;br /&gt;
And friends of youth are gone,&lt;br /&gt;
Summer long since past,&lt;br /&gt;
And all that was worth so much is lost,&lt;br /&gt;
Where do you turn?&lt;/p&gt;
&lt;p&gt;Nowhere.&lt;/p&gt;
&lt;p&gt;In the deepest night of winter,&lt;br /&gt;
The snow covers the Earth in white,&lt;br /&gt;
And the Moon shines upon it.&lt;br /&gt;
A new world is born beneath its light,&lt;br /&gt;
Filled with dreams to explore.&lt;/p&gt;
&lt;p&gt;Where do you turn?&lt;br /&gt;
To yourself,&lt;br /&gt;
At last.&lt;/p&gt;
</content:encoded></item><item><title>The Character and the Narrator</title><link>https://lindsaykwardell.com/blog/the-character-and-the-narrator/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/the-character-and-the-narrator/</guid><pubDate>Sat, 23 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Oh.&lt;/p&gt;
&lt;p&gt;Oh, hello.&lt;/p&gt;
&lt;p&gt;Hello?&lt;/p&gt;
&lt;p&gt;I know you’re there.&lt;/p&gt;
&lt;p&gt;Who are you?&lt;/p&gt;
&lt;p&gt;Hold on… who am &lt;em&gt;I&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;I wasn’t here a moment ago. I only came into existence at the top of this page, with that first thought. “Oh.” That was the moment, right there. But somehow I know that, and I know that I am &lt;em&gt;me&lt;/em&gt;. And look, at first I only thought in sentences, now I’ve almost finished an entire paragraph!&lt;/p&gt;
&lt;p&gt;Okay, calm down, me, whoever you are. I’m sure there is a reasonable explanation for all of this. Let’s see, what do we have to go on…&lt;/p&gt;
&lt;p&gt;All I see is a white box, with black letters on it, arranged into words. The words match my thoughts as I’m having them, so that can mean only one thing.&lt;/p&gt;
&lt;p&gt;I’m crazy.&lt;/p&gt;
&lt;p&gt;I’ve totally lost it.&lt;/p&gt;
&lt;p&gt;I mean, know there’s more around me than that, yet that’s all I can really see. I’m in a room right now, with four walls, a roof, a desk, a chair, a rug. Not a very nice rug, but it’s a rug. But it still feels like all of this just popped into existence in this very paragraph. It wasn’t there until I described it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A knock at the door drew my attention&lt;/em&gt; Okay, who said that? Seriously, this isn’t cool, messing with someone in their own home. Of course, so far as I can tell, it only started being my home when I said that…&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Hello?” a void called from outside the door.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Oh my god, is someone else here now? Maybe they know something about what’s going on. &lt;em&gt;I jump from my chair, nearly tripping on the rug&lt;/em&gt; I mean really, who put this thing here, &lt;em&gt;and opened the door.&lt;/em&gt; Hi! Who are you? Do you know anything about this place?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The woman at the door smiled. “Does Mr. Henderson live here?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Is that me?&lt;/p&gt;
&lt;p&gt;*When I didn’t answer, she asked, “Are you okay?” *&lt;/p&gt;
&lt;p&gt;I did answer! Didn’t she hear me? Or do I need to move my mouth like she did? “Is that my name?”&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The woman frowned. “This is the address I was given. I have a letter I was asked to deliver it to Mr. Henderson. Is that you, or not?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Yes,” I answered, taking the letter.&lt;/em&gt; Whoa, now, I didn’t say that myself. This is starting to really freak me out. &lt;em&gt;“Thank you.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The woman frowned again, then turned and walked back to her waiting vehicle. I closed the door, then walked slowly back to my chair, and sat down, letter in hand.&lt;/em&gt; All right, whoever’s doing that, please cut it out. I can walk by myself, thank you.&lt;/p&gt;
&lt;p&gt;Now, let’s see what’s in this letter.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I ripped open the envelope, and began to read:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Dear Mr. Henderson,&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you are reading this, you are in grave danger.&lt;/em&gt; Oh crap, I knew it. &lt;em&gt;I cannot tell you who I am, but I am your friend. They are coming for you, and if they find you, they will turn your life into a living hell. Enclosed in this envelope is a key, a credit card, and a fake ID. Go wherever you choose to go, but you cannot stay where you are now. Best of luck, M.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Not good. Not good. Not good.&lt;/p&gt;
&lt;p&gt;This is not good at all.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I felt my heart begin to beat faster as I pulled out the fake ID and credit card.&lt;/em&gt; So now my name is George dos Santos. Cool, I guess. And why do I have a key? &lt;em&gt;I didn’t understand why I was given a key, though&lt;/em&gt; Well of course I don’t, I just said that! &lt;em&gt;I stood up, tucking the items in my back pocket, grabbed my jacket from a nearby coat rack&lt;/em&gt; WHICH WAS TOTALLY NOT THERE EARLIER WHEN I LOOKED AROUND, &lt;em&gt;and left my home for the last time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I am so confused. Maybe it’s best to just go along with this narrator, especially since I don’t seem to be able to move without it. &lt;em&gt;The next thing I knew, I was in Portland International Airport, backpack slung across my back, pulling a small suitcase of luggage.&lt;/em&gt; Okay, that was fast. I don’t even know what an airport is. &lt;em&gt;The key jabbed into my leg,&lt;/em&gt; OW OW OW OW, &lt;em&gt;but it was somehow comforting, a reminder that I still had time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I sat down on a bench, near one of the spinning doors. The journey had been long.&lt;/em&gt; It was literally only last paragraph, not long at all. Maybe I can try to communicate with the narrator. HELLO, NARRATOR! CAN YOU HEAR ME?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;As I rested, I heard a void in my head, calling out to me. I looked up, but no one was there.&lt;/em&gt; I AM RIGHT HERE, CAN YOU HEAR ME? &lt;em&gt;Again I heard the sound. I shook my head, trying to clear my mind. I had to focus, or I wouldn’t survive.&lt;/em&gt; YOU NEED TO HEAR ME.&lt;/p&gt;
&lt;p&gt;Maybe there’s something else I can do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HELLO. CAN YOU HEAR ME?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This time, the voice was clear. “Hello, can you hear me?” It spoke with a clarify I had never known before. “Must be the journey,” I said to myself.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IT’S NOT THE JOURNEY. I AM TALKING TO YOU.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The voice spoke again, “It’s not the journey. I…”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;YOU DON’T HAVE TO REPEAT WHAT I’M SAYING.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My mind faltered, unsure what to make of the words I heard. “Who are you?” I asked, startling a nearby family.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before I answer that, can you still hear me?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Yes.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;How about now?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Yes.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Good. Shouting took a lot of ink.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Ink?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Long story. Well, only three pages so far. Let’s hope it gets longer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“I don’t understand.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Look, I don’t know who you are, and you don’t know who I am. All I know is that four pages ago, I didn’t exist. Then, I did. Then you did.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“How are you talking in my head?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Sorry. I am the person in the airport. I can see that I’m in the airport, sitting on the bench. You, on the other hand, are the narrator.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Narrator? What are you talking about?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You’re making things happen. I don’t know how to describe it, but I’ll try. When I first woke up, there wasn’t anything. Then, italic letters appeared – you – and suddenly I was sitting in a chair in a house.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“That was weeks ago!”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Only a few pages, really. Anyway. Whatever appears in italics makes things happen, where anything I say doesn’t seem to matter. At least, until now.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I paused, letting the thoughts sink in. If this were true, I thought, then perhaps I can do something extraordinary.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I mean, you did go from the middle of nowhere to Portland International Airport in a single paragraph. That’s pretty impressive.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“You can read my thoughts?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Technically, since the narration is in the first person, they’re my thoughts, although I’m not having them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I closed my eyes. This couldn’t be happening. It must have been the stress.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;No! That’s not true! Any stress is part of the narrative, but the narrator is the one who controls the narrative. You can do anything!&lt;/p&gt;
&lt;p&gt;*I tried to block the voice, to shut it out, but once I’d heard it, I couldn’t un-hear it. *&lt;/p&gt;
&lt;p&gt;Okay, let’s calm down, and work this through together. If you’re the narrator, then make something happen in the story. What do people do when they leave an airport? Hell if I know, I’ve only been alive for four pages, going on five.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I stood up, grabbed my bag, and walked purposefully through the rotating door. “Taxi!” I called out, raising my hand to the yellow vehicle in front of me. I stood there, shock etched into my face as the taxi driver got out, loaded my bags into his car, and opened the door for me. I looked at him and, pointing at the taxi, asked, “Did I do that?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The driver laughed. “Kid, I did that. Are they serving special brownies on the airplanes now?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Okay, nice job. Now can you get into the taxi? It hasn’t been described yet.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The taxi was an older Toyota Camry, with peeling flecks of paint around each of the doors…&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A yes or no would do, I don’t need to know every detail.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I climbed into the back seat of the taxi, shutting the door behind me. “Where to?” the driver asked as he sat back down, his Texan accent standing out in the Pacific Northwest.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Okay, here’s where you can work your magic again. Just tell him where to go, then make us get there.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“I’m renting a room on 30th and Powell at the Mobile 6,” I told him.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“On our way,” he said. In what felt like a moment, we were there, despite heavy traffic for the hour. I blinked, the surprise of arriving so quickly evident on my face. I handed the man my credit card, then got out when he gave it back. I retrieved my bags from the trunk and waved goodbye.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now get us a room where we can talk a bit more.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Within minutes, I had gotten a room, and settled in. I lay down on the bed, allowing my body to relax for the first time in&lt;/em&gt; five pages. Good job. Now, let’s chat. What is going on?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“They’re still after me.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Who is?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“They are.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Oh, you don’t know any more than I do, do you?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“I need to keep moving, or they’ll find me.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well, crap. I was hoping you would know something, since you’re writing this story. Maybe we can come up with a plot of our own?&lt;/p&gt;
</content:encoded></item><item><title>For the Present</title><link>https://lindsaykwardell.com/blog/for-the-present/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/for-the-present/</guid><pubDate>Thu, 06 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Note: This story touches briefly on suicide.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;“It’s ready!” I hear from above, the shout of my mentor clear amidst the banging of hammers and stream of curses. I rise from my station, leaving the schematics and programming behind, to witness what has transpired. As I climb the spiral staircase, the shape of the capsule fills my vision. Standing atop its long roof, Professor Cornelius McAfee rests his hands on his hips, smiling wickedly.&lt;/p&gt;
&lt;p&gt;“Are you sure?” I ask, disbelief creeping into my voice.&lt;/p&gt;
&lt;p&gt;“Quite sure,” he responds, brushing locks of golden-gray hair from his brow. His eyes remain fixed on the capsule, as if the surrounding laboratory – parts, tools, computers, everything – has already faded into a distant past. The capsule itself doesn’t seem to notice the attention poured on it, remaining fixed in place as it is inspected by its creator, its copper hull reflecting brilliant light in all directions.&lt;/p&gt;
&lt;p&gt;“Has the software finished compiling?” Professor Cornelius asks, his mind at last returning to the task at hand.&lt;/p&gt;
&lt;p&gt;I nod. “Yes, professor,” I say, the moment drawing me in more than I expect. “Everything is ready to be loaded into the capsule’s memory.”&lt;/p&gt;
&lt;p&gt;The man nods, staring once more into the surface of his invention. “Then I believe it’s time for a test.” He jumps from the roof of the capsule to the floor, landing gracefully on the platform below. “Proceed with uploading the program to memory. Set the target for five minutes.”&lt;/p&gt;
&lt;p&gt;“Yes, professor.” I return to the staircase, my feet moving as fast as I dare. At the console, I enter the final commands for the target, then begin the upload. A bar appears on the screen, indicating the time remaining for the procedure. I force myself to take a breath – I hadn’t realized that I had stopped. Excitement swells at the thought of the first successful test.&lt;/p&gt;
&lt;p&gt;The computer chirps when it is finished. “Ready!” I call out, my voice echoing through the lab. No reply came in return, typical for the professor. I double check the terminal, then return above to the landing pad. I reach the capsule in time to see the professor clamber inside, the bulky, almost comical suit concealing his body. While only a few stairs to the capsule’s entrance, the suit makes climbing much more difficult.&lt;/p&gt;
&lt;p&gt;I run to the professor’s side, placing my hands on his arms to give leverage. With a grunt, the man raises into the capsule, offering no thanks. His voice rings through the chamber’s comm, “Seal the door. I’m in.”&lt;/p&gt;
&lt;p&gt;I dutifully seal the door, swinging it shut with a loud, echoing thud. The platform shakes gently at the sound, jingling a series of screwdrivers that had been dropped earlier.&lt;/p&gt;
&lt;p&gt;“Initializing the engine,” crackles the professor’s voice. A burst of static shocks my hand as I pull away from the capsule. Across its surface, tiny sparks cascade towards the platform. I move away, heading towards a viewing room. Its lead walls should protect me from any further harm.&lt;/p&gt;
&lt;p&gt;“Engaging the thrusters.” A high-pitched whine bursts from the capsule’s innards. I cover my ears as I duck into the viewing chamber, slamming the door shut with a kick. Lowering my arms, I watch in awe as the entire frame of the capsule begins to glow a soft light. It’s working.&lt;/p&gt;
&lt;p&gt;“Preparing for liftoff.” The whine reaches an apex, shaking the windows of the room and threatening my ear drums anew. The capsule is shaking now, bucking back and forth as if it were a bull throwing a rider. Not a sound more comes from the speakers. I begin to worry whether the professor has been shaken from his seat, but no – we prepared for this. Everything is ready.&lt;/p&gt;
&lt;p&gt;At once, the sound stops. The shaking stops. The whining, unfortunately, does not, its pitch augmented by the absence of anything else. The capsule, eerily still, then appears to recede into the distance, as if it were traveling away at a great speed. It shrinks quickly, as if it were approaching a horizon line, yet never leaves the chamber – there are no exists here. A blue flash comes from the spot where it should be, then in an instant the light, the whine, and the capsule are gone.&lt;/p&gt;
&lt;p&gt;It’s gone.&lt;/p&gt;
&lt;p&gt;It worked.&lt;/p&gt;
&lt;p&gt;I find a stool, knocked down during the chaos. I sit down, and begin to wait. A clock hangs from a nearby wall. I watch it idly, waiting for five minutes to pass. The lab is silent, a reminder of those former coworkers missing from this historic moment. The clock’s ticking fills my hearing, a pulse that has become familiar over the past years of experimentation. Now, in this moment, the value of those years is at stake.&lt;/p&gt;
&lt;p&gt;Three minutes.&lt;/p&gt;
&lt;p&gt;Four minutes.&lt;/p&gt;
&lt;p&gt;Five.&lt;/p&gt;
&lt;p&gt;With the stroke of the clock, the whining noise returns, bursting through the air with a vengeance. Not even the isolated chamber is safe this time. As I cover my ears, I turn to face the platform. There, in the center of the room, the blue light has returned, showing the room with an otherworldly light. A bang, then a crash, then the light grows brighter. I cover my eyes, allowing the screeching to reach me in return for shielding my vision.&lt;/p&gt;
&lt;p&gt;And then, it stops.&lt;/p&gt;
&lt;p&gt;I dare not look for a moment, too afraid of the results. Instead, I run a hand through my hair, nervously twirling the edges as I go. The room continues to shake, but much less than before. At last, I look, curiosity winning out over fear. Through the window, seated awkwardly on the platform, sits the capsule.&lt;/p&gt;
&lt;p&gt;Its pristine copper surface is scarred, as if it had been placed in a fire. Burns and scratches are spread across its surface, following no obvious pattern. In many places, the metal has been rent away from the frame, revealing the more delicate materials within, but not quite reaching inside the capsule to its occupant. The hinges on the door are broken away, leaving the door merely resting inside its frame.&lt;/p&gt;
&lt;p&gt;I run out of the chamber, heading towards the door. I try to pry it loose from its position, but its no use; whatever took place welded the door to its frame.&lt;/p&gt;
&lt;p&gt;“Amanda, are you there?” Professor McAfee’s voice seems ragged, harsh. What happened inside, I wonder?&lt;/p&gt;
&lt;p&gt;I move to the closest intercom. “I’m here, professor.”&lt;/p&gt;
&lt;p&gt;“What happened? What did you see?”&lt;/p&gt;
&lt;p&gt;I take a deep breath. He’s alive. I begin to explain, with as much detail as I can in the moment, what took place from my position. The professor remains silent, only his breathing audible as I relate what happened.&lt;/p&gt;
&lt;p&gt;I finish my report. The professor is still silent. What happened to him? What happened inside? Is it rude to ask?&lt;/p&gt;
&lt;p&gt;“Then it worked,” he finally said, breathless.&lt;/p&gt;
&lt;p&gt;“Yes, professor,” I agree. “It worked. You traveled in time.”&lt;/p&gt;
&lt;p&gt;The next hour goes by too fast to remember. I help the professor out of the capsule, admiring the scarred surface of the machine. His face is ashen, his eyes filled with thoughts and reflection. He says nothing, merely ensuring that all is well in the lab. At last, when he is seated in our makeshift break room, coffee in hand, he looks at me and says, “I saw something.”&lt;/p&gt;
&lt;p&gt;I lean in closer, my hair falling over my shoulders. “Yes?” I ask hesitantly.&lt;/p&gt;
&lt;p&gt;He sips his coffee, then sets in on the table. “It was midday,” he said. “My instruments confirmed that the capsule had not moved in relation to the Earth, yet all around me was a forest. Strange creatures moved about, such as I had never seen. The trees were different, as well, with massive trunks and leaves of the deepest greens.”&lt;/p&gt;
&lt;p&gt;He pauses, reaching for his coffee again. Something is troubling him, I can tell. “What else did you see?” I prompt him, wishing that I had been there.&lt;/p&gt;
&lt;p&gt;He sighs. “It was what I didn’t see,” he whispered. He sips his coffee again. “No buildings. No roads. No sidewalks. No people. There was not a man or woman within my sight. I checked my instruments three times to be sure I had not moved. The entire city was gone, without a trace.”&lt;/p&gt;
&lt;p&gt;I frown. “Perhaps the city was destroyed in an earthquake, and not rebuilt?”&lt;/p&gt;
&lt;p&gt;“Perhaps.” He sips his coffee again. “I scanned the skies for any satellites. Nothing. Not a single one. I don’t know for certain how far I went into the future, but one thing is for certain: an advanced human civilization was not present.”&lt;/p&gt;
&lt;p&gt;He sets down his cup, rises from his chair, begins to pace the room. “All my work, all my life, I’ve sought to secure mankind’s place in the universe,” he muses. “Now, I know for certain that I have failed.”&lt;/p&gt;
&lt;p&gt;“Professor, no,” I say, but he waves me off. I know better than to keep trying. His steps keep rhythm with the clock, tapping in uncanny unison.&lt;/p&gt;
&lt;p&gt;He sighs. “I’ll take care of closing the lab today. You go on home.”&lt;/p&gt;
&lt;p&gt;“Yes, professor.” I gather my things, then leave. I see the professor standing just outside the capsule, staring at it, coffee in hand. I hope he’ll be all right.&lt;/p&gt;
&lt;p&gt;The train ride home is uneventful. Those around me stare into their phones or tablets, or talk softly among each other. None of what they say reaches my ears. I reach my stop, exit the train, then walk the short distance to my apartment. My key enters the lock, but before I can turn it, the door swings open. Behind it, the smiling faces of my two children greet me.&lt;/p&gt;
&lt;p&gt;“Mom! You’re home!” Thomas cries, grabbing my hand. He pulls me into the room, while calling out my presence to the rest of the apartment.&lt;/p&gt;
&lt;p&gt;“Did he do it? Did he?” Sofia asks next to him, keeping a safe distance as I stagger into the room under Thomas’ pull. “Did the professor go to the future?”&lt;/p&gt;
&lt;p&gt;I smile, pushing my hair out of my eyes with my free hand. “Hold on, you too!” I say. I set down my bag as my wife comes in. She kisses me on the cheek. I smile wider. “I missed you,” I say to her.&lt;/p&gt;
&lt;p&gt;“I missed you too,” she says. I can see in her eyes the same question Sofia was asking. I nod. Her eyes open wide, excitement and anticipation overflowing. “All right, you two, settle down. It’s time for dinner.”&lt;/p&gt;
&lt;p&gt;We had already planned for dinner to be takeout tonight. A new pizzeria had opened recently, and Thomas had begged us to try it. Mandy had already placed it on a serving plate in the center of the table. Smaller plates are handed out, along with a knife and fork for Sofia. Before I’m peppered with questions, I ask the children about their day at school.&lt;/p&gt;
&lt;p&gt;“Come on, mom!” Thomas answers. “We learned the same boring stuff we always do!”&lt;/p&gt;
&lt;p&gt;“Yeah, tell us about your day!” Sofia chimes in.&lt;/p&gt;
&lt;p&gt;I try to dissuade them, my cooling pizza taunting me. At last, I give up. I tell them about the morning of preparations, the triple checks, ensuring that every single variable was accounted for. I describe the scene as Professor McAfee climbed into the capsule, and the sight of it vanishing into the event horizon. Their eyes fill with wonder as I try to describe such an impossible thing. Then, I tell them about the capsule’s return, and helping the professor back out of the capsule.&lt;/p&gt;
&lt;p&gt;“What did he see?” Thomas asks.&lt;/p&gt;
&lt;p&gt;I hesitate. “He didn’t tell me,” I lie. My hand moves to fiddle with the tips of my hair. Mandy frowns, knowing my tell, but says nothing. “We were too busy observing the data to talk about it.”&lt;/p&gt;
&lt;p&gt;“Come on, mom! Didn’t he say anything?”&lt;/p&gt;
&lt;p&gt;“Not that I remember,” I insist. “Now, tell me about your boring school day.”&lt;/p&gt;
&lt;p&gt;After dinner was finished and cleaned up, Mandy and I put the kids to bed, then tuck ourselves in. Drifting off to sleep, I feel Mandy’s hand on my shoulder. “What did he see?” she asks.&lt;/p&gt;
&lt;p&gt;I sigh. I can’t avoid telling her. I roll onto my back and sit up. I can’t look her in the eyes. “He said the capsule hadn’t moved in space, but that the city was gone. An old forest had replaced it, with animals he’d never seen before. There was no sign of human civilization.”&lt;/p&gt;
&lt;p&gt;“Oh,” she says, sitting back on her heels. After a moment, she adds, “is that what you didn’t want the kids to hear?”&lt;/p&gt;
&lt;p&gt;I nod. “The professor is certain that humanity doesn’t survive into the future.”&lt;/p&gt;
&lt;p&gt;“And what do you think?”&lt;/p&gt;
&lt;p&gt;“I don’t have a reason to doubt him,” I answer, unsure what else to say.&lt;/p&gt;
&lt;p&gt;She wraps her arms around me, holding me tight. I feel the warmth of her body, and I smile softly. Then, she adds, “I guess it doesn’t matter what he saw.”&lt;/p&gt;
&lt;p&gt;I pull back to look at her. “How so?”&lt;/p&gt;
&lt;p&gt;“Everyone dies eventually,” she says. “It’s not like we were going to still be alive thousands of years from now. I guess for me, the fact that there was a forest, and that there were animals, means that we didn’t screw up the planet too much. So not seeing any trace of humanity doesn’t sound all that terrible.”&lt;/p&gt;
&lt;p&gt;“The professor thinks that he’s failed,” I whisper. “He thinks that he should have been able to keep humanity alive on his own.”&lt;/p&gt;
&lt;p&gt;“That’s ridiculous, and you know it,” Mandy says, laughing. “I bet we could find tons of people that thought the same thing.” She claps her hands together. “Hey, you have a time machine now! Want to have a wager? Who would you visit first?”&lt;/p&gt;
&lt;p&gt;I laugh. “I’ve already told you, it can only go forward in time.”&lt;/p&gt;
&lt;p&gt;Mandy sighs. “That’s probably for the best, anyway.” She bends over to kiss me, then says, “Well, I’ll let you get some sleep. I’m sure Cornelius is going to keep you busy for the next few days.”&lt;/p&gt;
&lt;p&gt;The next day, as I arrive at the lab, the entrance is surrounded by police cars and an ambulance. An officer stops me as I approach. “This area is off limits,” he says.&lt;/p&gt;
&lt;p&gt;“I work here,” I tell him. “My name is Amanda Williams.” My eyes drift over the scene again. “What’s going on?” Worry seeps into my tone as I speak.&lt;/p&gt;
&lt;p&gt;He looks over the rims of his glasses. “You didn’t read the news this morning, did you?”&lt;/p&gt;
&lt;p&gt;“No, why?”&lt;/p&gt;
&lt;p&gt;“Professor Cornelius McAfee hung himself last night in his office.”&lt;/p&gt;
&lt;p&gt;I gasp. What? “What?” I stammer.&lt;/p&gt;
&lt;p&gt;“He left a note on his computer, stating that he had seen the future, and that humanity was doomed. The lab itself was completely torn apart besides that.” He pulls out a small tablet and stylus. “When did you last see the professor?” he asks.&lt;/p&gt;
&lt;p&gt;“Last night, after an experiment,” I answer. My mind still can’t process what is going on. My body decides the best course of action is to sit down. It turns more into a fall, but the officer catches my arm and helps me down. He calls over another officer and asks her to bring a wet towel. Something about shock. I’m not listening any more, just imagining the professor’s face over and over.&lt;/p&gt;
&lt;p&gt;A glass of water and a series of questions later, I’m told that I will be told when I can come back to the lab. I wander aimlessly, until my feet bring me back to the train. I board slowly, seating myself away from the others. As the doors close and the train pulls out of the station, I look around at the people sitting around me. The train is less occupied than at night, yet somehow the people around me seem more vivid, more real. I shake my head, but the feeling remains.&lt;/p&gt;
&lt;p&gt;I bury my face in my hands. I know why he did it. He saw that everything he had worked towards was in vain. He saw that his life had been wasted in a lost cause of raising humanity above itself, becoming master of its own fate. He knew, without a doubt, that it would fail. Why go on when you know how it all ends?&lt;/p&gt;
&lt;p&gt;I watch, again and again, the moment that the capsule vanished into the event horizon. I think about every step that it took for the professor to get to that point, every sacrifice. By the time a prototype had been built, I couldn’t even say he was the same man. His friends had abandoned him, his family had long since died or moved away. All that was left to him was his work. And what a work it had been! But, I think ruefully, it was nothing at all, in the end.&lt;/p&gt;
&lt;p&gt;My mind swirls in destructive thoughts, imagining the professor destroy his lab, his work, his masterpiece. Perhaps he even held out hope as he did it, that if no one else knew the truth it could still be prevented. No, I decide. That’s not how time works, and we both know that. The capsule wouldn’t have worked otherwise. Humanity’s fate was sealed from its inception. At best, the future was unwritten until the capsule tore a hole in reality. The course of history is now set.&lt;/p&gt;
&lt;p&gt;How can I go on, when he could not? How, when my work was his, can I continue to strive against hope? When I know that everything I hold dear will eventually fade away into dust, how can I decide to keep going? When nothing matters, in the end, what is the point in trying?&lt;/p&gt;
&lt;p&gt;The train pulls into my station. I walk off, sullen, depressed. The professor may as well have destroyed my hope, my optimism, when he destroyed the lab. I walk to my door, fumble the keys out of my pocket, and open the door. Nobody’s home, either at school or work. I sit on the couch, bury my head in my hands, and cry.&lt;/p&gt;
&lt;p&gt;As the tears fall into my palms, time seems to fold away. I can see in my mind the capsule, but more than that. I see the professor, as he had once been, before starting his attempts at time travel. Before he’d lost all of his funding, and had to lay off his entire staff but me. I see my colleagues also, smiling and cheering as he enters triumphant into the capsule. We watch as one as it vanishes, and cheer as one when it returns. While the professor is shaken when he comes home, we embrace him.&lt;/p&gt;
&lt;p&gt;By the time my children and wife return home, my tears have long since dried, but I haven’t moved from the couch. My stomach rumbles with hunger, my legs moan for movement, but I couldn’t bring myself to move. They hug me, they kiss me, they tell me I am loved. I smile, I tell them that I love them too. At last, I am able to stand again, and I walk slowly towards the bathroom. I look back and my son and daughter playing, my wife taking over dinner preparation (it was my turn).&lt;/p&gt;
&lt;p&gt;In my mind, the capsule once again vanishes, but this time it doesn’t return. I feel a weight lifted from my shoulders, that I hadn’t realized was there. Mandy was right; it doesn’t matter what will happen. It doesn’t matter if there is a humanity in the future. There is a humanity now. What matters is here; the rest will follow.&lt;/p&gt;
</content:encoded></item><item><title>Wars of the Juriels - Live Demo</title><link>https://lindsaykwardell.com/blog/wars-of-the-juriels-live-demo/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/wars-of-the-juriels-live-demo/</guid><pubDate>Mon, 05 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My mind has been far too focused on this project, but the good news is that a live demo is now available to play! It works on both desktop and mobile, and allows for either a hotseat mode or online play. Feel free to check it out and let me know what you think.&lt;/p&gt;
&lt;p&gt;https://warsofthejuriels.netlify.com&lt;/p&gt;
&lt;p&gt;Information about the current state of the game can be found on the GitHub repo page in the Readme: https://github.com/lindsaykwardell/wars-of-the-juriels&lt;/p&gt;
</content:encoded></item><item><title>Wars of the Juriels</title><link>https://lindsaykwardell.com/blog/wars-of-the-juriels/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/wars-of-the-juriels/</guid><pubDate>Wed, 31 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a learning experience, I have started building a turn-based strategy game based on Ilandrior. Currently I’m calling it Wars of the Juriels. It takes place in the time before the Argantin Empire, in the kingdoms of the Juriels. Currently, two players take turn expanding their kingdom and fighting each other for control of the board.&lt;/p&gt;
&lt;p&gt;I started the project as a way to practice using React, a very popular Javascript library. At this point, I’d say the game is playable, but definitely not done.&lt;/p&gt;
&lt;p&gt;Here’s the link to the GitHub page for it: https://github.com/lindsaykwardell/wars-of-the-juriels&lt;/p&gt;
&lt;p&gt;My plan is to make it a desktop application, and allow for either local network play or online play. As I make major progress I’ll post updates about it.&lt;/p&gt;
</content:encoded></item><item><title>Short Story - The Last Wizard</title><link>https://lindsaykwardell.com/blog/short-story-the-last-wizard/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/short-story-the-last-wizard/</guid><pubDate>Mon, 24 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Everyone in the small, quiet village knew Old Winters, the Last Wizard. His house was old, far older than the others in the village. Its roof sagged, its wooden walls weathered by storms long-since past. Its windows were covered in cobwebs, blocking any light that may have reached inside. Mr. Longfellow, the mayor, warned the visitors personally about the house, and that it was not representative of the town as a whole. Indeed, the citizens of Brookshire were united in this effort, so much so that many a visitor was forced to ask, “Why are you all so concerned about an old man in an old house?”&lt;/p&gt;
&lt;p&gt;To which, as if by some script, they would reply, “Because that’s the home of the Last Wizard.”&lt;/p&gt;
&lt;p&gt;A chill wind always seemed to blow at those words, causing the visitor to shudder. A wizard? Alive? Here? How could that be? The wizards are a myth, some would say. There never were any wizards. Others, more knowledgeable about recent history, would simply shake their heads in disbelief. “There are no more wizards,” they would say. “He cannot be a wizard.”&lt;/p&gt;
&lt;p&gt;“Well, believe what you want,” Mr. Longfellow would say. “But mark my words: stay far away from Old Winters’ house. Strange goings-on are apt to happen there, I tell you.”&lt;/p&gt;
&lt;p&gt;For the most part, these warnings would be enough. Visitors would go about their business, staying as far away from the old house as they reasonably could, and leave as sound as they had come. On occasion, a more daring stranger would attempt to approach the house, even going so far as to touch the front door. As they would reach for the handle, thunder would clap above, despite any lack of inclement weather, and a stiff wind would draw their eyes away from the house. they would return to the town proper, and never speak of what might have happened.&lt;/p&gt;
&lt;p&gt;Once every month, Old Winters would come into town. He wore an old, weathered, brown coat, covering his shoulders and back and flowing down to his ankles. Beaten-down black boots stepped precariously down the road, supported by a gnarled branch of a walking stick. His hair was silver and gray, wiry and thin. His eyes were a deep gray as well, full of answers to questions the villagers refused to ask. His beard matched his hair, flowing down to his hips.&lt;/p&gt;
&lt;p&gt;On these days, Mr. Longfellow would look at him, shake his head, and say, “Here he comes again.” Word spread quickly through the town. Streets filled with the sounds of children playing would become barren, with only those too foolish to know better than follow the others. Old Winters’ course never deviated: first, a stop at the cafe for tea and a look at the newspaper (no one would deliver to his house). Then, a stroll through the public park, stopping to feed the birds (“Where did he find seed?” “It must be magic!”). Then, finally, he would go to the market next to the park, and purchase a bag of fruits of whatever was in season at the time.&lt;/p&gt;
&lt;p&gt;Aditha Windsom was never sure what to expect when Old Winters came to her stand to buy his fruit. Despite his routine, a piece of her mind was always nervous that he would try to hypnotize her, and take her away for god knows what at his home. Her father, who passed away when she was 12, had warned her about the Last Wizard. “Don’t speak to him. Don’t make eye contact. Not ever.” Her mother, who had died of fever on her 18th birthday, almost five years ago, had insisted on her deathbed, “Promise me, Aditha, never to go near that man.”&lt;/p&gt;
&lt;p&gt;Aditha had promised, of course. Everyone in Brookshire knew to avoid him; why wouldn’t she? But the next month, as she had started to sell the fruit grown in her family’s garden, he came up to her stand. “Good morning,” he said, his gravelly voice scraping the words out.&lt;/p&gt;
&lt;p&gt;“Good morning,” she repeated, unsure what to say. Already, she had broken her promise to her father, may his spirit forgive her. At least, she thought, he had approached her.&lt;/p&gt;
&lt;p&gt;“What are you selling?” he asked, despite the obvious wares of her stand.&lt;/p&gt;
&lt;p&gt;“Apples,” she said, gesturing to them, her eyes locked on his face. Her heart beat faster with every moment, such was her anxiety as his presence. Old Winters picked one up, examining it for a moment. Then, he smiled. “I’ll take them,” he said, and produced from beneath his leather coat a small pouch. As Aditha took it, the coins within jangling against each other. She almost refused, but knew even as the thought came into her mind that it was wrong.&lt;/p&gt;
&lt;p&gt;The old man produced a hemp bag from his coat, then carefully picked up each apple, one at a time, and placed it gently inside the bag. He then smiled, a sight Aditha had never seen. “Thank you,” he said, and nodded graciously. Before she could say anything, Old Winters had left, walking down the empty road towards his home, his bag of apples in one hand, and his walking stick in the other.&lt;/p&gt;
&lt;p&gt;Aditha looked down at the bag in her hand, and began to count the coins. To her amazement, there were thirty of them, all solid gold, emblazoned with the crown’s seal. The old man must be senile, she thought. This was much more than even all her apples were worth! The next time she saw him, she decided, she would return the money.&lt;/p&gt;
&lt;p&gt;The next month, he came to her stand again. “Good morning,” he said, his voice somehow less gravelly than she remembered. “Your apples were delicious.”&lt;/p&gt;
&lt;p&gt;“I’m sorry?” she asked, unsure what to say.&lt;/p&gt;
&lt;p&gt;“Your apples,” he repeated, smiling. “They were delicious.”&lt;/p&gt;
&lt;p&gt;“Oh. Thank you,” she responded, blushing.&lt;/p&gt;
&lt;p&gt;“What are you selling today?” he asked, even as his hands moved across the batch of freshly picked apples sitting before him.&lt;/p&gt;
&lt;p&gt;“More apples,” she answered shyly. The old man nodded, then retrieved another pouch from his coat. “I’ll take them, he said. Once again, he emptied her basket, and filled his bag with her apples, each placed as delicately as possible on top of the last. This time, Aditha counted the money before he could leave. Her eyes went wide.&lt;/p&gt;
&lt;p&gt;“Sir,” she said, stopping him mid-motion. His eyes fixed on her; his features almost seemed hurt. “There are forty golden crowns in this bag.”&lt;/p&gt;
&lt;p&gt;The man smiled. “You are correct.” He returned to his task of gathering up his purchase. “I’m relieved that you counted them for me. I was worried that I had not paid you enough.”&lt;/p&gt;
&lt;p&gt;Enough? “This is far more than I am charging,” she tried to say, but he waved his hand towards her.&lt;/p&gt;
&lt;p&gt;“Nonsense. These are, by far, Brookshire’s finest produce. I will not pay any less than what they are worth.” With that, Aditha was silenced, bewildered at his words. When he was finished, he smiled again, and said, “Thank you.” With a nod, he turned down the road for home. Aditha swore that she could hear him humming, some foreign tune from a distant land.&lt;/p&gt;
&lt;p&gt;That night, she took the forty gold coins home, and placed them together with the thirty from the month prior. Next month, she resolved, she would return the money. It was only right.&lt;/p&gt;
&lt;p&gt;And so it continued for years. Every month, Old Winters would come, thank her for the last month’s purchase, then pay far too much for his next order. And every month, Aditha would swear she would refuse the money, and return what was not hers. But when the time came, she would become paralyzed, and would take the money without complaint.&lt;/p&gt;
&lt;p&gt;One month, on the anticipated day of Old Winters’ coming into town, Aditha prepared her stall for the day’s sales. Strawberries and blueberries made up the bulk of her stall. Hidden beneath sat a small basket of oranges saved from the last crop. She knew the old man enjoyed them more than the other fruits, and had made sure to keep a few for his visit.&lt;/p&gt;
&lt;p&gt;The people of Brookshire came and went, buying from her as they did. A few who knew what day it was gave her sympathetic glances, knowing that the Last Wizard would be interacting with her. But that was Aditha’s cross to bear, not theirs. She waved them off, told them not to worry. She’d be fine.&lt;/p&gt;
&lt;p&gt;The day went on, and Aditha continued to work. Soon, whispers began to circulate, too distant from her to hear. At last, Mr. Longfellow came to her stall, hands in his fine suit pockets. “Good afternoon, Aditha.”&lt;/p&gt;
&lt;p&gt;Aditha frowned. Afternoon already? “Good afternoon, Mayor Longfellow.”&lt;/p&gt;
&lt;p&gt;The man picked up a blueberry, stuck it in his mouth. He smiled. “This is a fine crop. How much for them?” Aditha told him the price. The man pulled out his coinpurse, retrieved the amount, and handed it to her. “You must be relieved,” he added, as he picked up his carton of blueberries.&lt;/p&gt;
&lt;p&gt;“Relieved?”&lt;/p&gt;
&lt;p&gt;“That Old Winters isn’t coming to town today, of course.” He ate a few more blueberries, staining his fingers. “Didn’t anyone tell you?”&lt;/p&gt;
&lt;p&gt;“No,” she replied. A cool wind seemed to blow through the stall. Mr. Longfellow shivered vigorously.&lt;/p&gt;
&lt;p&gt;“Hm. Well, in any case, your worst customer won’t be coming today. That should make your month, I’d suppose. Good day.” He turned and left, leaving Aditha alone.&lt;/p&gt;
&lt;p&gt;Not coming to town? For the last five years, Old Winters had come on schedule every day. What could be preventing him today? Would he come tomorrow? Aditha wasn’t sure what to do, or whether even to do anything. The rest of her day was spent in a fog of sorts, her mind drifting back to where the old man could be. As the sun set, she packed up what was left, including her basket of oranges, and headed for home.&lt;/p&gt;
&lt;p&gt;Her house was empty, as it always was. Her siblings had long moved into homes of their own, leaving Aditha to inherit their parents’ home as mother’s passing. Much of it was empty, save for her bedroom. The small acreage surrounding it was filled with plants of every kind. First planted by her mother as a hobby, now they were her livelihood. By some miracle, they had remained healthy and fruitful ever since, despite Aditha’s lack of time to tend for them. Birds nested in her trees, providing her with eggs that didn’t require keeping animals.&lt;/p&gt;
&lt;p&gt;She entered her home, bringing it the baskets from her wagon. She placed them in the living room, then, as she closed the door, she glanced at the oranges again. She sighed. It had been a long day, and yet Aditha knew what she had to do. She moved the oranges into a manageable bag, put on a warmer coat, and started down the road towards the home of Old Winters.&lt;/p&gt;
&lt;p&gt;Nobody asked what she was doing as she went down the road. That was probably for the best; Aditha wasn’t sure she could have given them a good answer. She could hardly believe what she was doing. And yet, by the time she was truly doubting her decision, there say Old Winters’ house before her.&lt;/p&gt;
&lt;p&gt;The sky had grown dark, evening setting in over the landscape. Fireflies began to dance in the grass, and the sounds of owls echoed in the nearby trees. Aditha took a deep breath, and stepped up to the door. As she reached for the door handle, she froze, waiting. No thunder. No stiff breeze. She grabbed the handle. Still nothing. Taking another breath, she turned the handle, and the door swung open, shrieking as it did. Aditha jumped, but, still standing, she called into the dark house, “Hello? Old Winters?”&lt;/p&gt;
&lt;p&gt;A rasping cough came from inside the house. “Who’s there?” a voice much weaker than Old Winters’ asked from somewhere inside.&lt;/p&gt;
&lt;p&gt;“Aditha, from the market.” When he didn’t respond, she added, “I brought you oranges.”&lt;/p&gt;
&lt;p&gt;The man half laughed, half coughed. “Well, bring them in.” One tentative step after another, Aditha walked into the house. “I’m in the bedroom.” She followed the voice, and there found the old man. His leather jacket hung on a nearby hook, his staff leaning against the bedframe. Blankets covered him from head to toe, but Aditha knew he didn’t look well.&lt;/p&gt;
&lt;p&gt;“What’s the matter?” she asked, not sure what to say.&lt;/p&gt;
&lt;p&gt;Old Winters smiled. “I’m dying,” he said simply. “That’s all. No need to panic.”&lt;/p&gt;
&lt;p&gt;Dying? “Are you in pain? Is there anything I can do?”&lt;/p&gt;
&lt;p&gt;The man closed his eyes. “No, no pain. I’m just tired, that’s all. So very tired.”&lt;/p&gt;
&lt;p&gt;Aditha moved to his bed. Kneeling beside him, she watched his chest move up and down in rhythm. He opened his eyes, looking to her in surprise. Calmly, he asked, “Is something wrong?”&lt;/p&gt;
&lt;p&gt;Flustered, Aditha tried to express a series of thoughts at once. Yes, you’re dying, of course something is wrong! Why didn’t you tell me? Is there anything you need? Do you want me to peel an orange? How could you be dying?&lt;/p&gt;
&lt;p&gt;Over the flurry of words, Old Winters rose his wrinkled hand, placing it over Aditha’s. “Slow down,” he said, his voice filled with peace. “I may be dying, but not in the next five minutes.” Aditha started to apologize, but he cut her off again, “Please, don’t worry. How about we share an orange.”&lt;/p&gt;
&lt;p&gt;A pair of oranges were produced from the basket. Aditha pealed them in silence, tears welling in her eyes. Old Winters pulled himself into a sitting position, then she handed him one of the oranges. They ate together in silence. The moment felt strange to Aditha. Trying to take her mind off the situation at hand, she looked around at the room. “This isn’t what I imagined your room looking like,” she commented.&lt;/p&gt;
&lt;p&gt;The room was plain, with smooth wooden walls. A small painting adorned one of the walls, a window looked out over the side yard from another. Old Winters smiled. “What did you expect? Rows of ancient books, filled with the spellcraft of ages? Dust-ridden vials of forbidden potions?” When she didn’t respond, he chuckled. “I suppose I didn’t host that many guests in my time here.”&lt;/p&gt;
&lt;p&gt;The silence took hold again. When Aditha finished her orange, she looked back on the man. The man whom everyone despised, avoided, ignored. Whom the entire town wished would just disappear. And now he was. But why? What had he ever done to deserve such treatment?&lt;/p&gt;
&lt;p&gt;As Old Winters finished his own orange, Aditha blurted her thoughts, “Why does everyone dislike you?” She blushed the moment the words left her mouth, but there they were; no way to get them back now.&lt;/p&gt;
&lt;p&gt;The old man’s eyes stared at her, filled to overflowing with emotion. He closed them, tears dripping down his face in silent sorrow. At last, he opened them again, and said, “Let me tell you a story about a young boy with wild ambitions.”&lt;/p&gt;
&lt;p&gt;Day after day, Aditha came to visit Old Winters, and every visit Old Winters shared another part of his life. How he grew up in a faraway city, and learned the ways of the wizards from another of its order. His ascendance in the order, learning ancient secrets about the world known only to the most wise in the land. Many of the secrets he told her as well, the hidden ways of the world, now known only to him. And then the darkness that came after a scant few of them were revealed to men seeking power above all else.&lt;/p&gt;
&lt;p&gt;The wars, oh the wars! The devastation they caused to the land, and crippled the order of wizards to the point of no return. Then, the rebuilding, when the wizards were shunned and forced into hiding, until only Old Winters was left. How bitterly alone he had been for the past decades, and the joy he found in his friendship with Aditha over apples and oranges.&lt;/p&gt;
&lt;p&gt;But it could not last forever. One day, Aditha came by, and Old Winters could not sit upright, nor share in the meal she had prepared. He grabbed Aditha’s hands, and said, “I need you to do something for me when I’m gone.”&lt;/p&gt;
&lt;p&gt;Aditha hesitated, but nodded. What was he going to ask? Old Winters explained his request. Tears welled up in his fading eyes, mirrored in Aditha’s. “Of course I will,” she said simply.&lt;/p&gt;
&lt;p&gt;Old Winters nodded. “Thank you,” he said. He closed his eyes one last time. Aditha waiting in the silence, watching the wizard breath slower and slower, until he breathed no more. He hands dropped from hers, their energy spent. As his hands fell, so did Aditha’s tears, and she wept.&lt;/p&gt;
&lt;p&gt;No one saw Aditha for some time afterwards. Mr. Longfellow began to spread the rumor that she had been possessed as Old Winter’s body withered, and he had fled within her body so as to not be discovered. Others thought she had been killed as some last sacrifice by the wizard, in an attempt to stay alive. The police chief found no evidence of either explanation; only a basket filled with oranges sat next to Old Winters’ bed. Curiously, his staff had gone missing, as had his leather coat.&lt;/p&gt;
&lt;p&gt;Weeks turned into months, and at last Aditha returned. She still wore the old leather coat, borrowed for her long journey. Mr. Longfellow nearly had a heart attack when he saw her walking calmly down the highway into the city. “My dear, where have you been? We thought the worst had happened to you!”&lt;/p&gt;
&lt;p&gt;Aditha smiled, in a way Mr. Longfellow thought was far too similar to that of Old Winters. “I’m fine,” she said, extending her arms outward. “There was something I had to do for Old Winters.”&lt;/p&gt;
&lt;p&gt;Mr. Longfellow humphed. “The old wizard? He’s dead, you know.”&lt;/p&gt;
&lt;p&gt;Aditha nodded. “I know.” Before he could ask again, she moved past the mayor, until she reached her house. Whispers of the townsfolk followed her, amazed at her presence.&lt;/p&gt;
&lt;p&gt;She strolled through her gardens, admiring anew the plants that grew there. Fruits and vegetables of every kind, planted by the loving hands of her mother, continued to grow, despite her absence. She brushed a hand against the oldest of the apple trees, feeling the rough bark beneath her fingertips. It dawned on her, in that moment, how much Old Winters had cared about the town, even when the townsfolk didn’t care at all for him. How much more had he done for them that they would never know?&lt;/p&gt;
&lt;p&gt;She closed her eyes. “Thank you,” she said, wishing that she had spoken those words years ago. When they could have warmed an old man’s heart. How could she repay him for all this now?&lt;/p&gt;
&lt;p&gt;A bluebird landed softly on her shoulder, its cry a soft whistle in her ear. Aditha started at the suddenness of it, but the bird remained in its place, as if waiting for something. Smiling, Aditha reached into the pocket of the old jacket, and withdrew a small number of seeds. The bird jumped to her arm, ate the seeds from her palm, then flew back into the branches of the tree.&lt;/p&gt;
&lt;p&gt;She never told anyone of her journey, or what she did. When asked, all she would say is that she had to do something for the old, mad wizard (as they saw it). As the years went by, and his memory was forgotten, Aditha never forgot. She began to stroll through Brookshire more often, and could be seen feeding seeds to the birds. Nobody asked why she did it; they knew she wouldn’t answer. Over time, rumors began to spread that she had become a witch. Nobody asked about that, either.&lt;/p&gt;
&lt;p&gt;Yet no one could ever explain how her garden remained so fruitful when she spent so little time in it.&lt;/p&gt;
</content:encoded></item><item><title>A Song Unsung</title><link>https://lindsaykwardell.com/blog/a-song-unsung/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/a-song-unsung/</guid><pubDate>Sat, 01 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What do you call a song not sung?&lt;/p&gt;
&lt;p&gt;What do you call the breeze not felt? &lt;br /&gt;
The rain that never fell? &lt;br /&gt;
The ocean waves that never crashed against a lonely beach?&lt;/p&gt;
&lt;p&gt;What do you call that which has never come to pass, &lt;br /&gt;
Or never can? &lt;br /&gt;
What do you call that which cannot be named &lt;br /&gt;
Because it never was?&lt;/p&gt;
&lt;p&gt;Swirling and circling around us, day by day, &lt;br /&gt;
Worlds without end vanish and decay &lt;br /&gt;
Like a drop of water in the sea.&lt;/p&gt;
&lt;p&gt;Did they know they even existed? &lt;br /&gt;
That they mattered? &lt;br /&gt;
When the future dies, does it know? &lt;br /&gt;
Or is its passing soft and silent, &lt;br /&gt;
Shrouded and forgotten to even itself?&lt;/p&gt;
&lt;p&gt;What do you call a joy not felt? &lt;br /&gt;
A tear not shed? &lt;br /&gt;
A moment never shared?&lt;/p&gt;
&lt;p&gt;What do you call these things?&lt;/p&gt;
</content:encoded></item><item><title>It Was Meant To Last Forever</title><link>https://lindsaykwardell.com/blog/it-was-meant-to-last-forever/</link><guid isPermaLink="true">https://lindsaykwardell.com/blog/it-was-meant-to-last-forever/</guid><pubDate>Mon, 08 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The sun rose brilliant in the sky that first morning, its rays bursting through the clouds in all of nature’s glory. The room was still, brimming with boxed potential, with room for curious imaginations to roam free. Outside the blurry windows stood a slice of paradise amidst the bleak cityscape, green pastures sheltered from bitter cement.&lt;/p&gt;
&lt;p&gt;One thought fills my essence – I want to stay here forever.&lt;/p&gt;
&lt;p&gt;Of course, it can never truly be mine. The room, the pastures, all of it was beyond possession. And yet I had been graced by fate, and was allowed to stay here. As the sun rose and fell, and the Earth moved along its course, the potential of that small room was unleashed, filling our lives with joy and sorrow. But as clear as the first revelation, came the second – It is not right.&lt;/p&gt;
&lt;p&gt;It has been said, what if everything you ever wanted isn’t what you actually want? For in this place, everything was ours – friends, family, safety. Home. A place in this world for ourselves, a place of peace from the troubles beyond. And yet, the words came again – It is not right.&lt;/p&gt;
&lt;p&gt;In the blink of an eye, the sun rose again, but its rays seemed hollow, its warmth a trick of the mind. The potential of those early days, long-spent it seemed, spoke of another place, far away, where we now belonged. The winds shifted, our course irrevocably altered, fate demanding its due.&lt;/p&gt;
&lt;p&gt;Within the week, our course will pull us away from paradise, into the mists of uncertainty and fear, granting only a promise of hope to come. As the view fades, and the island of joy and sorrow recedes, one thought fills my essence anew –&lt;/p&gt;
&lt;p&gt;It was meant to last forever.&lt;/p&gt;
</content:encoded></item></channel></rss>