<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ben Sinclair</title>
    <description>The latest articles on Forem by Ben Sinclair (@moopet).</description>
    <link>https://forem.com/moopet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31518%2Ffda7f57e-7bcf-4a04-b7bf-529373e2a1cd.jpg</url>
      <title>Forem: Ben Sinclair</title>
      <link>https://forem.com/moopet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/moopet"/>
    <language>en</language>
    <item>
      <title>Component-based CSS</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Tue, 07 Apr 2026 16:20:38 +0000</pubDate>
      <link>https://forem.com/moopet/component-based-css-4ic4</link>
      <guid>https://forem.com/moopet/component-based-css-4ic4</guid>
      <description>&lt;p&gt;There are a lot of CSS "solutions" out there. They generally act to solve a broad range of problems, none of which actually exist outside their authors' bubble.&lt;/p&gt;

&lt;p&gt;Let me take you by the mitten and show you how I like to style components.&lt;/p&gt;

&lt;p&gt;Here's "semantic-component-name.html":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"semantic-component-name-here"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Robin Hood&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
            &lt;span class="c"&gt;&amp;lt;!-- etc... --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;King Arthur&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
            &lt;span class="c"&gt;&amp;lt;!-- etc... --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Update history"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"disclaimer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        None of these people are what you think they are.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and "semantic-component-name.css":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;
&lt;span class="nc"&gt;.semantic-component-name-here&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="c"&gt;/* Put everything in here... */&lt;/span&gt;

  &lt;span class="err"&gt;legend&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--heading-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.disclaimer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ethically-dubious-color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;/* that's it. You're done. */&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your component, which has a distinct container class name, uses a semantic container tag, and inherits style variables from the parent theme... will just work.&lt;/p&gt;

&lt;p&gt;You can update your look and feel from your global theme. You can use container queries to your heart's content. You can use tag selectors. They're all nested under the component's class, so they're not going to clash with anything. &lt;/p&gt;

&lt;p&gt;You don't need to do anything special. You don't need to give every single element a generated random ID. You don't need to hard-code each component's padding inline with the tag, or define it in JavaScript. So put away that smelly CSS-in-JS. Stop importing stylesheets into React. Uninstall your preprocessors. You don't need any of that stuff.&lt;/p&gt;

&lt;p&gt;Okay, you can aggregate and minimise the CSS. Don't purge anything though.&lt;/p&gt;

&lt;p&gt;Don't remove unused CSS classes at build time. That's another solution to a problem that doesn't exist unless you literally design your code to BE the problem. And you don't do that, do you?&lt;/p&gt;

&lt;p&gt;As a side-note, Svelte partially does this for you if you want to do React- or Vue-like things without having to run that sort of obsolete bloatware. It &lt;em&gt;does&lt;/em&gt; give every component a random ID, but it's the closest to proper CSS handling I've seen outside of just doing it yourself in the first place.&lt;/p&gt;




&lt;p&gt;Any questions?&lt;/p&gt;

</description>
      <category>css</category>
      <category>ui</category>
      <category>quickies</category>
    </item>
    <item>
      <title>Printable layouts and accessibility</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Wed, 25 Mar 2026 11:47:49 +0000</pubDate>
      <link>https://forem.com/moopet/printable-layouts-and-accessibility-31oh</link>
      <guid>https://forem.com/moopet/printable-layouts-and-accessibility-31oh</guid>
      <description>&lt;h2&gt;
  
  
  Arguments against my whole post
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;You shouldn't be printing anything anyway, it's bad for the environment!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes. Fine. Noted.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That's what PDFs are for!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No, it's really not, and if you have a site somewhere with a button labelled "print" which exports something as a PDF then we probably have bigger problems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nobody prints web pages, what are you worried about?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Uh-huh.&lt;/p&gt;

&lt;p&gt;People print web pages. Some people will print off a copy of your CV, a receipt for a product, the confirmation page for a booking, some quick troubleshooting tips for their washing machine, a recipe...&lt;/p&gt;

&lt;p&gt;My partner has reduced vision and uses larger-than-usual text sizes on screen, and often prints articles out to read them at her leisure in a more comfortable way. Paper is still easier for a lot of people to read than a backlit screen. It's the big reason kindles are so popular.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's have a nice clear button that says "print"
&lt;/h2&gt;

&lt;p&gt;No, let's not. Browsers have that built in. You wouldn't reinvent the wheel, would you? You wouldn't recreate the browser's "back" button on ever... wait, you did that, didn't you?&lt;/p&gt;

&lt;h2&gt;
  
  
  What about "reader mode"?
&lt;/h2&gt;

&lt;p&gt;Reader mode is good. It's where the browser tries to remove as much of the formatting and unnecessary cruft it can and gives you a single column of clear text, in good contrasting colours.&lt;/p&gt;

&lt;p&gt;It's better than good, in fact: it's &lt;em&gt;great&lt;/em&gt;. But it solves a different problem than printing does. You'll lose the layout for an invoice, for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  So... what should we keep, then?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;p&gt;Odds are, you don't need to show the menu structure when printing something out. Ditch it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advertising
&lt;/h3&gt;

&lt;p&gt;You don't need to display ads in a print-out. It's not like anyone can click them, and it's not like anyone wanted to see them when they were on the screen in the first place. Ditch them from the print stylesheet.&lt;/p&gt;

&lt;h3&gt;
  
  
  That big honkin' footer/sidebar/whatever
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  The newsletter signup nag/subscription begging modal/plea for social media "likes"
&lt;/h3&gt;

&lt;p&gt;You didn't expect this to show up, did you? Because it's hidden by javascript until the user is least expecting it? And you're surprised it's mangled its way into a paper copy?&lt;/p&gt;

&lt;p&gt;Pay attention now: no.&lt;/p&gt;




&lt;p&gt;Cover photo from &lt;a href="https://morguefile.com/p/44955" rel="noopener noreferrer"&gt;Printer Leaf by zion at Morguefile.com&lt;/a&gt; &lt;/p&gt;

</description>
      <category>a11y</category>
      <category>print</category>
      <category>css</category>
    </item>
    <item>
      <title>How to talk to people</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Wed, 25 Mar 2026 10:52:07 +0000</pubDate>
      <link>https://forem.com/moopet/how-to-talk-to-people-5b82</link>
      <guid>https://forem.com/moopet/how-to-talk-to-people-5b82</guid>
      <description>&lt;p&gt;Have you ever wondered why some products use different language for what appears to be identical functionality? Why is it that we have so many ways of saying the same thing? Is it an artefact of English being the Internet's &lt;em&gt;lingua franca&lt;/em&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt;? Does it matter?&lt;/p&gt;

&lt;h2&gt;
  
  
  Bookmarks vs. Favourites&lt;sup id="fnref2"&gt;2&lt;/sup&gt; and the power of language to shape your experience
&lt;/h2&gt;

&lt;p&gt;There's more behind this than you might think. In prehistoric times, Netscape Navigator used &lt;em&gt;bookmarks&lt;/em&gt; and Internet Explorer used &lt;em&gt;favorites&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Bookmarks are a familiar concept to many older people. You know, people who read dead-tree books when they were growing up, and know why the "save" icon is a floppy. It's a word that's partially lost its meaning nowadays, apart from as a semi-synonym for &lt;em&gt;favourite&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;At its simplest, a bookmark is something you use to mark your place in a book. It doesn't infer any sense of approval. You can go through a terrible textbook and &lt;em&gt;bookmark&lt;/em&gt; all the worst bits. &lt;/p&gt;

&lt;p&gt;Bookmarks show interest, but are fundamentally impartial.&lt;/p&gt;

&lt;p&gt;Favourite, however, comes with baggage. If you &lt;em&gt;favourite&lt;/em&gt; something - he says, verbing the noun with near-Millenial abandon - you imply that you like it, that you approve of its content.&lt;/p&gt;

&lt;p&gt;If your favourites are public, then you might hesitate to press the button, cautious of what other people might make of it.&lt;/p&gt;

&lt;p&gt;If your favourites are private, then you still might worry that someone will see them one day.&lt;/p&gt;

&lt;p&gt;I deliberately didn't use the word "like" earlier, but we all feel its presence. The little star reaction on site A is a heart emoji on site B. &lt;em&gt;They are not the same&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;tl;dr: You can influence how people behave by using leading language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Followers vs. Friends and social pressure
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Don't think 'cause I understand, I care. Don't think 'cause I'm talking, we're friends."&lt;/p&gt;

&lt;p&gt;-- Sneaker Pimps&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a similar vein, there's the "buddy" problem.&lt;/p&gt;

&lt;p&gt;If I'm on a site or app where I am interested in a particular topic - a political issue, or a fandom, say - I might follow accounts around that topic. It doesn't mean I endorse them, but if your site calls that mechanism, "friends" then there's yet more baggage.&lt;/p&gt;

&lt;p&gt;As a side-note, if your site calls it "buddies" then it flags your service as US-centric as this term isn't widely used elsewhere. That may or may not be what you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Follow-back culture.
&lt;/h3&gt;

&lt;p&gt;I probably don't need to mention this, but there's a "follow-back" culture online, especially amongst younger people wanting to up their numbers.&lt;/p&gt;

&lt;p&gt;It's a whole mess, and feeds into these parasocial relationships we keep hearing about, and probably contributes a lot to vulnerable people feeling even more inadequate. It's not something that's as controlled by language as my other points, but there are a variety of things you can do in terms of UX when presenting people with notifications or "follow" actions.&lt;/p&gt;

&lt;p&gt;Use your imagination: if it feels like it's leading someone down an unnecessary path or pushing a certain behaviour, maybe don't make that component in the first place.&lt;/p&gt;

&lt;p&gt;TODO: insert picture of give-a-shit-ometer&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Likes and favourites and peer pressure
&lt;/h3&gt;

&lt;p&gt;Again, we all feel the presence of the like button.&lt;/p&gt;

&lt;p&gt;Sometimes this is what we want. Calls-to-action can help users navigate an unfamiliar interface. The "like" is familiar in all of its forms. But they're not interchangeable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gamification
&lt;/h3&gt;

&lt;p&gt;We know gamification pushes users in one direction or another. But gamification is the last resort of a system that doesn't have a real reason to exist.&lt;/p&gt;

&lt;p&gt;If there's no benefit to the end users to have awards, for example... don't have them. It's not rock surgery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relevance to DEV
&lt;/h2&gt;

&lt;p&gt;Sometimes I try to bring the context of this platform back into my posts. I think DEV does it pretty well: there's a variety of different reactions you can add to a post as well as leaving comments, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;neutral iconography (exploding head)&lt;/li&gt;
&lt;li&gt;a bookmarking feature neutrally labelled, "save"&lt;/li&gt;
&lt;li&gt;a discrete "boost" option to separate promotion from reaction &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stats, follower counts and social influence
&lt;/h2&gt;

&lt;p&gt;Some sites lean heavily on stats. They show people's follower count in little tooltips or next to the person's name. It's placed as more important than the person's bio, because it's seen more frequently.&lt;/p&gt;

&lt;p&gt;Imagine that: it's more important to show you that 800 people follow Alice and 5,000 people follow Bob than it is to mention that Alice shares similar interests with you, while Bob mostly rants about sportsball.&lt;/p&gt;

&lt;p&gt;I could write a lot about numbers. I have, in other places, but it's a little off-topic for here, so I'll try to stick with the context.&lt;/p&gt;

&lt;p&gt;I said it's more &lt;em&gt;important&lt;/em&gt; to show you the stats, but what I mean is that they fit into a conveniently small area, are quick enough to generate and, well, big numbers generally impress people. They make you feel inadequate and get you all gamified. There's no real reason that a user should care what they are, but by including them, you're telling visitors that popularity is rewarded on the site - and probably popularity &lt;em&gt;for its own sake&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In my opinion, this might not be a smart move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ultracrepidarianism
&lt;/h2&gt;

&lt;p&gt;The title of this post is a little in-joke because I'm not very good at talking to people. Whatever.&lt;/p&gt;

&lt;p&gt;Cover photo by &lt;a href="https://www.pexels.com/photo/use-your-words-text-on-dice-17141799/" rel="noopener noreferrer"&gt;Brett Jordan from Pexels&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Why is this phrase not in the lingua franca? ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;I'm not American. In code or when talking about a specific American instance, I'll call it a "favorite" because I &lt;em&gt;code&lt;/em&gt; in American, but in posts and conversation I'll spell it the way nature intended. Suck it up. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Meh, can't be bothered. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ux</category>
      <category>language</category>
      <category>relations</category>
    </item>
    <item>
      <title>Maybe there's a little time left</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Mon, 16 Mar 2026 10:29:12 +0000</pubDate>
      <link>https://forem.com/moopet/maybe-theres-a-little-time-left-1ml3</link>
      <guid>https://forem.com/moopet/maybe-theres-a-little-time-left-1ml3</guid>
      <description>&lt;p&gt;I wonder how long until people stop tagging things with #ai... because everything's AI?&lt;/p&gt;

&lt;p&gt;Maybe there's a little time left.&lt;/p&gt;

&lt;p&gt;Remember when everything was listicles? I'm almost nostalgic for those days. At least you could identify them at a glance. Now it often takes a paragraph or two before you click that it's slop.&lt;/p&gt;

&lt;p&gt;But it's not the slop I'm trying to stop. At least, not directly. It's the fact that &lt;strong&gt;everything&lt;/strong&gt; on my feed is about &lt;em&gt;how to AI harder&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu75beldu71fe7r20w6t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu75beldu71fe7r20w6t.png" alt="Screenshot of the top of the DEV tag page for " width="694" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, DEV supports &lt;em&gt;hiding&lt;/em&gt; tags. I think I did find this out a while ago, but forgot. Last thing I remember, you could only &lt;em&gt;weight&lt;/em&gt; tags, from somewhere hidden in your profile. Setting something to a negative weight theoretically made it show up less (but still show up). I never found it that helpful.&lt;/p&gt;

&lt;p&gt;I'd not really bothered before because the site used to be a reasonable balance of articles, if skewed a little towards web development. That's not the case in 2026.&lt;/p&gt;

&lt;p&gt;But it turns out there is still content left that's about development, and you can get to it by blocking everything AI.&lt;/p&gt;

&lt;p&gt;Since noticing (or re-noticing) that you can "hide" tags, I've hidden every combination of AI&lt;sup id="fnref1"&gt;1&lt;/sup&gt; I found, and my feed is now... about development again?&lt;/p&gt;




&lt;p&gt;And since I started this, and because I'm on a mission at this point, I've also used &lt;a href="https://dev.to/moopet/remixing-other-peoples-websites-331n"&gt;my Adblock superpowers&lt;/a&gt; to get rid of all the little robot icons and "agent" CTAs encouraging me to not author my own post.&lt;/p&gt;

&lt;p&gt;I had all but abandoned this place.&lt;/p&gt;

&lt;p&gt;But maybe, maybe there's a little time left.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;while I'm at it, everything not about development or about how to get rich quick: #ai, #blockchain, #crypto, #gemini, #geminicode, #openclaw, #opencode, #mcp, #seo, ... ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>(My first time) Running an Azure Function</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Mon, 03 Mar 2025 19:06:13 +0000</pubDate>
      <link>https://forem.com/moopet/my-first-time-running-an-azure-function-4k0f</link>
      <guid>https://forem.com/moopet/my-first-time-running-an-azure-function-4k0f</guid>
      <description>&lt;p&gt;Other people have done this before, of course. I'm not breaking new ground here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background picture
&lt;/h2&gt;

&lt;p&gt;I know how to make a Lambda function in AWS. Amazon are wildly unfriendly with their TLAs but the first time I used it the whole thing took about an hour, and I wrote something that uses a webhook to forward some information to a different API. It was straightforward so far as I remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why did I do this?
&lt;/h2&gt;

&lt;p&gt;Microsoft stack for a different client. Needed to learn the ropes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's do Azure!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  $ign me up (tldr; issues: multiple, confidence: suspicious)
&lt;/h3&gt;

&lt;p&gt;Step one is to get a free Azure account.&lt;/p&gt;

&lt;p&gt;Well, I say free. You have to provide your credit/debit card details even if you want to use the free tier, which does &lt;em&gt;not&lt;/em&gt; fill me with confidence.&lt;/p&gt;

&lt;p&gt;Microsoft let me sign up with a GitHub account - a service they own - but apparently that's not enough to identify me, so I have to use my phone. Honestly, this tells me more about how much they trust &lt;em&gt;their own other products&lt;/em&gt; than it does about Azure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;However&lt;/em&gt;, the process went without any technical glitches.&lt;/p&gt;

&lt;p&gt;And you know something? I quite like their retro theme. Looks straight out of the early 2000s, like cPanel or sommat:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4nemvz0d1vn7uagpsd4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4nemvz0d1vn7uagpsd4.png" alt="Crop of the Azure dashboard showing links to different services and my exploratory function listed" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing prerequisites (tldr; issues: multiple, confidence: are we learning yet?)
&lt;/h3&gt;

&lt;p&gt;There aren't any real prerequisites so far as the official documentation goes, except needing to install whatever language you choose. I'm going to go with TypeScript.&lt;/p&gt;

&lt;p&gt;I'm also using VSCodium&lt;sup id="fnref1"&gt;1&lt;/sup&gt; because I want to follow the tutorials and their VSCode examples seem the best documented. Full disclosure, I'm not a big GUI person; the only thing I have installed so far is the VSCode neovim extension and the Gruvbox theme so I feel comfortable.&lt;/p&gt;

&lt;p&gt;I install the "Azure Functions" extension, and open their "http trigger" example function. I want something simple, like a "hello world" effort where I can visit a particular URL and get a canned response.&lt;/p&gt;

&lt;p&gt;Looks good so far, syntax highlighting all works, tooltips show me Azure-related guff.&lt;/p&gt;

&lt;p&gt;Apparently I can run this function locally. I hit "Run" to see what it does...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpogn0rwg6hop3ie5vks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpogn0rwg6hop3ie5vks.png" alt="Dialog box telling me " width="473" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh. Ok, let's learn more, shall we?&lt;/p&gt;

&lt;p&gt;I get taken to a web page telling me to install &lt;code&gt;Azurite&lt;/code&gt;. Cool. Would have been nice if this was a hard dependency of the AzureFunctions extension, or at least listed in the documentation before this point, but ok. We're here now. I'll do what you say, Microsoft Help Page, I'll install it as a VSCode extension.&lt;/p&gt;

&lt;p&gt;Except that extension doesn't exist in the VSCode extensions search.&lt;/p&gt;

&lt;p&gt;Hmm. Maybe VSCode isn't getting an up-to-date list of add-ons? I'll fall back to a web search.&lt;/p&gt;

&lt;p&gt;Bingo!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l5e0ol6doc03uxpasfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l5e0ol6doc03uxpasfx.png" alt="The VSCode marketplace page for Azurite, showing how to install it using the quick open dialog" width="780" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's do that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c4l2z2cq7hidep0npax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c4l2z2cq7hidep0npax.png" alt="The extensions pane in VSCode filtered by " width="789" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Boo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it something I said?
&lt;/h3&gt;

&lt;p&gt;Maybe it's because I told Microsoft's telemetry to get in the sea, and run VSCodium instead of VSCode? Perhaps a pre-requirement of running an Azure function is that they leech more of my personal data?&lt;/p&gt;

&lt;p&gt;I'll install the Bad Place version of the IDE and see what difference that makes...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vqxm9cm7b72i0re19f3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vqxm9cm7b72i0re19f3.png" alt="The same extensions pane as before, but this time with one result: Azurite" width="328" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...it was something I said.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello, world? (tldr; issues: oodles, confidence: I don't know the meaning of the word)
&lt;/h2&gt;

&lt;p&gt;It prompts me to sign in, so I do. Twice. Once for the Azure functions and then for the Azure functions, but on a page with a slightly different colourscheme.&lt;/p&gt;

&lt;p&gt;I run "debug". It fails, and I read the documentation again. Ah, the Azurite extension doesn't start automatically, I have to run it manually every time. No biggie&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;It can't find the function in Azure. Tells me it doesn't exist.&lt;/p&gt;

&lt;p&gt;Reading the issues tells me this is because I left it idle too long. Apparently an hour of debugging and getting more coffee is enough for this to silently log you out and prepare a misleading error message for when you return.&lt;/p&gt;

&lt;p&gt;I log in again. In order to do that I have to quit and restart VSCode. Maybe there's a way to do it in the IDE, but I couldn't find it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hello Node my old friend, I've come to speak with you again.
&lt;/h3&gt;

&lt;p&gt;Let's go!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[error] Incompatible Node.js version (v23.9.0). Refer to our documentation to see the Node.js versions supported by each version of Azure Functions: &lt;a href="https://aka.ms/functions-node-versions" rel="noopener noreferrer"&gt;https://aka.ms/functions-node-versions&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It seems Azure Functions currently max out at node version 20. That's not unusual for these sort of platform-specific things, Vercel, Netlify, AWS, etc., all have similar restrictions.&lt;/p&gt;

&lt;p&gt;However.&lt;/p&gt;

&lt;p&gt;I cannot find out how to get VSCode to use a different version of node. Various articles online tell me to set a &lt;code&gt;runtimeVersion&lt;/code&gt; in my &lt;code&gt;launch.json&lt;/code&gt;. This does nothing whatsoever, and as an added indicator of its relevance, the keyword "runtimeVersion" has that little yellow squiggle that means, as the Australians might say, "yeah, yeah, no."&lt;/p&gt;

&lt;p&gt;If you're adventurous enough to follow that link, you might notice that it takes you to a page which says absolutely nothing about node versions.&lt;/p&gt;

&lt;p&gt;If I navigate to their node troubleshooting page, the only relevant part I see is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure you're using Node.js v18 or higher.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe the problem is that 23 &amp;lt; 18 in Microsoft Units.&lt;/p&gt;

&lt;p&gt;Other articles tell me to set the default in nvm to 20 - &lt;code&gt;nvm alias default 20&lt;/code&gt;. This does not help.&lt;/p&gt;

&lt;h3&gt;
  
  
  200mph solution incoming...
&lt;/h3&gt;

&lt;p&gt;I "fix" this by closing VSCode and launching it from an environment where 20 is already the current version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm use 20 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't particularly like this because if I launch the IDE from my desktop it'll fail and I might not remember why. Still, it's a way forward!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk568aot8i3jueaq4wyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk568aot8i3jueaq4wyl.png" alt="Borat, his thumbs raised" width="606" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I made it? I think.&lt;/p&gt;

&lt;p&gt;I can debug a local copy of an Azure function and instantly deploy it when I'm happy with my work. Now it's running, it's pretty smooth.&lt;/p&gt;




&lt;p&gt;Cover image by Bing, based on artwork stolen from real humans.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;VSCodium is the open-source project that Microsoft make before they add their telemetry and bundle it up as an app. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Stupid, but no biggie. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>node</category>
      <category>azure</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I made my DEV articles into a blog using the Forem API</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Wed, 12 Feb 2025 16:02:43 +0000</pubDate>
      <link>https://forem.com/moopet/i-made-my-dev-articles-into-a-blog-using-the-forem-api-42d2</link>
      <guid>https://forem.com/moopet/i-made-my-dev-articles-into-a-blog-using-the-forem-api-42d2</guid>
      <description>&lt;p&gt;Other people have done this before, of course. I'm not breaking new ground here.&lt;/p&gt;

&lt;p&gt;It's a couple of API calls and a styling exercise. All the actual &lt;em&gt;work&lt;/em&gt; is done by DEV.&lt;/p&gt;

&lt;p&gt;It lives at &lt;a href="https://moopet.net/blog" rel="noopener noreferrer"&gt;moopet.net/blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why did I do this?
&lt;/h2&gt;

&lt;p&gt;I lost my job a few weeks ago&lt;sup id="fnref1"&gt;1&lt;/sup&gt; and decided it was time to make my personal website, which was pretty much a one-shot joke at the time, into something I could show recruiters. A links page, a CV, a portfolio, and... a blog. You know, something to show I was an "active member of society" or whatever.&lt;/p&gt;

&lt;p&gt;The obvious choice was to use DEV, since I've made quite a few posts here over the years, and everything I've posted elsewhere is, let's say, "not representative of the opinion of my employers".&lt;/p&gt;

&lt;p&gt;I also decided to do it with Svelte, because I'm getting into that, too. So birds, stones, etc., you know the drill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I want it to look like?
&lt;/h2&gt;

&lt;p&gt;I love &lt;a href="https://github.com/morhetz/gruvbox" rel="noopener noreferrer"&gt;gruvbox&lt;/a&gt; as a colour scheme, and I wanted something kind of retro. Something that wasn't another swish corporate-looking effort full of stock images but that also didn't look &lt;em&gt;too&lt;/em&gt; playful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dark mode
&lt;/h3&gt;

&lt;p&gt;I wanted to support dark- and light-modes based on the system setting followed by the user's preference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultColorMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getBrowserColorMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;defaultColorMode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userSavedMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSavedMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;userSavedMode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;defaultColorMode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isDarkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getBrowserColorMode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;$effect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDarkMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/images/icons/dark-mode.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"dark-mode-toggle"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;bind:checked=&lt;/span&gt;&lt;span class="s"&gt;{isDarkMode}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"dark-mode-toggle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Dark mode&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code blocks
&lt;/h3&gt;

&lt;p&gt;For the code blocks, I decided to &lt;em&gt;not&lt;/em&gt; change the style when switching between dark- and light-mode, because I think the dark block looks good against either background. &lt;/p&gt;

&lt;p&gt;But while I was styling it, I noticed the syntax highlighter used on DEV handily includes the language in the wrapping element's class.&lt;/p&gt;

&lt;p&gt;And I can do something cool with that!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswjml4h8nosut10lztny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswjml4h8nosut10lztny.png" alt="A code block, with the language (in this case " width="496" height="112"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.highlight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-gruvbox-black&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--syntax-text-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;padding-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;padding-inline-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;padding-block-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;padding-block-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;margin-block-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="err"&gt;.highlight&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="py"&gt;margin-block-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="err"&gt;&amp;amp;:after&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-0.75em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;text-transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;monospace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;padding-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;padding-inline-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;padding-block-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;padding-block-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;border-inline-start-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;border-inline-end-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;border-block-start-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;border-block-end-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-gruvbox-white&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;border-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-gruvbox-brown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="nc"&gt;.plaintext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="nc"&gt;.html&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"html"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="nc"&gt;.javascript&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="nc"&gt;.shell&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="nc"&gt;.sql&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"sql"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="nc"&gt;.yaml&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"yaml"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inline code with backticks
&lt;/h3&gt;

&lt;p&gt;I decided to style inline code the opposite way, with a light background. Dark didn't look good when the site was in dark mode and I wanted to do more than just use a monospace font.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6s3fdl7ooropik6h43l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6s3fdl7ooropik6h43l.png" alt="Some unimportant blog text demonstrating the use of inline code, rendered with a different (lighter) background" width="497" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What problems did I find?
&lt;/h2&gt;

&lt;p&gt;Honestly, very few. Most were because I was a Svelte beginner - barely using anything beyond the router - and I've either fixed them since or decided they're fine as-is.&lt;/p&gt;

&lt;p&gt;I couldn't figure out how to get pagination to work in Svelte without loading &lt;em&gt;all&lt;/em&gt; my posts and slicing the array. Doesn't matter since there aren't enough posts to make it an issue. The API payload is small enough.&lt;/p&gt;

&lt;p&gt;I realised too late that some posts had generic DEV-themed cover images (well, &lt;code&gt;social_image&lt;/code&gt; really) because I hadn't uploaded one&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;br&gt;
I decided to make the text full-width if there was no &lt;code&gt;cover_image&lt;/code&gt; set instead:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4ggillj8f5c75vgiurf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4ggillj8f5c75vgiurf.png" alt="Close-up of two blog post cards, one with a teaser image and one without" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;... and I kinda like it. It's big and bold and goes with the big links and colours I've used elsewhere.&lt;/p&gt;

&lt;p&gt;When faced with posts containing code blocks, I could have rendered the markdown myself and styled it (the API provides posts both as markdown and HTML) but then I'd have needed to handle liquid tags and all sorts of other content. So I took the HTML version and styled it up myself.&lt;/p&gt;

&lt;p&gt;Similarly, links were something I needed to fudge, since they were often relative. I could add routes in Svelte to match the post slugs and show my articles, but I'd need to do that &lt;em&gt;just&lt;/em&gt; for my own articles. Anyone else's and I'd need to redirect to dev.to.&lt;/p&gt;

&lt;p&gt;Some elements in DEV's HTML (from liquid tags, mostly) don't always contain what I'd guess. For example, an &lt;code&gt;{% embed ... %}&lt;/code&gt; tag for another user's post will render with an image and a description, which I styled up fine. It wasn't until I went through all the posts to double-check that I discovered that under some circumstances there can be more than one image. I had to style pretty defensively.&lt;/p&gt;

&lt;h2&gt;
  
  
  So am I pleased with the result?
&lt;/h2&gt;

&lt;p&gt;Yeah. Yeah, I am. I have a website which doesn't look too modern, and uses proper HTML (it doesn't rely on any weird utility classes).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzhlivcino4gkfelp166.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzhlivcino4gkfelp166.png" alt="Grandpa Simpson saying, " width="493" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted to make a blog that felt like it fit in without having to rethink all the design decisions I'd made for the rest of the site. Or maybe it was the other way around. Whatever, I wanted it to go nicely together.&lt;/p&gt;

&lt;p&gt;I wanted it to be as simple and extendable as I could make it, with bold, accessible colours and spacing, yet still cram in all the different things you can do in a DEV post.&lt;/p&gt;

&lt;p&gt;I learnt a buttload about Svelte. Most significantly I learnt that the things I don't like about React are suddenly not a problem with Svelte. I'm not an expert with either so there's room to change my mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what's a good example post?
&lt;/h2&gt;

&lt;p&gt;Try &lt;a href="https://moopet.net/blog/98475" rel="noopener noreferrer"&gt;moopet.net/blog/98475&lt;/a&gt;. It's an older post, but it checks out.&lt;/p&gt;

&lt;p&gt;It doesn't particularly matter what the it's about; it showcases blockquotes, code blocks, inline code, different heading levels, images and tags, so it covers most of the things I've styled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there anything left to do?
&lt;/h2&gt;

&lt;p&gt;There's always something to improve, right?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;del&gt;make the router work with slugs instead of/as well as article IDs&lt;/del&gt;. &lt;em&gt;Done&lt;/em&gt;!&lt;/li&gt;
&lt;li&gt;read the highlighter source code to find what other languages it supports and add them in.&lt;/li&gt;
&lt;li&gt;make it update without having to push to the repo :)&lt;/li&gt;
&lt;li&gt;probably fix a million silly little styling bugs on different devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final notes and engagement ploy
&lt;/h2&gt;

&lt;p&gt;There are a couple of posts here on DEV where people have shown how they integrated posts in their own website, most of which go into a lot more detail than I have.&lt;/p&gt;

&lt;p&gt;But I'd love to see some comments about your own examples, showing off how you made it look!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;It's ok, I got a new one. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;In fact, this post you're reading now doesn't have a cover image, and it's because I couldn't think of something that would work and wouldn't be a recursive image of the blog itself! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>svelte</category>
      <category>showdev</category>
      <category>forem</category>
    </item>
    <item>
      <title>How to get into your CMS when you've locked the keys in your car.</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Thu, 19 Dec 2024 14:23:36 +0000</pubDate>
      <link>https://forem.com/moopet/how-to-get-into-your-cms-when-youve-locked-the-keys-in-your-car-5oo</link>
      <guid>https://forem.com/moopet/how-to-get-into-your-cms-when-youve-locked-the-keys-in-your-car-5oo</guid>
      <description>&lt;p&gt;Sometimes, you have shell access to a server, or a database dump, but you don't know how to log in as an admin. Maybe you've been given a copy of the latest database from production and are trying to replicate some bug locally. Maybe you're just forgetful.&lt;/p&gt;

&lt;p&gt;How to get around this isn't going to be the same between frameworks, so here're a few of the popular ones I've had to use. Sometimes it's a way to reset a user's password without having access to their email, sometimes it's a way to log in as a user without needing to know the password at all.&lt;/p&gt;

&lt;p&gt;This isn't a way to hack into servers. It's a way to get better access to something you already have access to.&lt;/p&gt;

&lt;p&gt;This is currently very much a "PHP" cheatsheet. I might add more later. Throw me a comment if you have one to add?&lt;/p&gt;

&lt;h2&gt;
  
  
  Craft
&lt;/h2&gt;

&lt;p&gt;Tip: the default login form for a Craft site uses the path &lt;code&gt;/admin&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the craft command-line client
&lt;/h3&gt;

&lt;p&gt;Log in as an existing user (in this case, “admin”):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;craft &lt;span class="nb"&gt;users&lt;/span&gt;/impersonate admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the database
&lt;/h3&gt;

&lt;p&gt;Generate a new password hash using the tool at &lt;a href="https://craftcmspwgen.james-nock.co.uk/" rel="noopener noreferrer"&gt;craftcmspwgen&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"&amp;lt;password_hash&amp;gt;"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Drupal (up to version 7), Backdrop, etc.
&lt;/h2&gt;

&lt;p&gt;Tip: the default login form for a Drupal site uses the path &lt;code&gt;/user&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;drush user-password admin &lt;span class="nt"&gt;--password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;password&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Drupal (version 8+)
&lt;/h2&gt;

&lt;p&gt;Tip: the default login form for a Drupal site uses the path &lt;code&gt;/user&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;drush user:password admin &lt;span class="s2"&gt;"&amp;lt;password&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  WordPress
&lt;/h2&gt;

&lt;p&gt;Tip: the default login form for a WordPress site uses the path &lt;code&gt;/wp-login.php&lt;/code&gt;. There are numerous redirects, but out the box that one should work.&lt;/p&gt;

&lt;p&gt;It's incredibly common for WordPress site administrators to &lt;a href="https://en.wikipedia.org/wiki/Security_through_obscurity" rel="noopener noreferrer"&gt;change the login path to something obscure&lt;/a&gt;, so this might not work. This is a solution to the wrong problem, but let's not get into that here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the wp-cli command-line client
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;wp user list&lt;/code&gt; to find the user name you want to log in as (in this example, it’s “admin”) and then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp user update admin &lt;span class="nt"&gt;--user-pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://developer.wordpress.org/cli/commands/user/update/" rel="noopener noreferrer"&gt;wp user update – WP-CLI Command&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;Alternatively if this doesn’t work (sometimes it just… doesn’t), use &lt;br&gt;
&lt;code&gt;wp user list&lt;/code&gt; to find the user ID you want to log in as (in this example, it’s 1) and then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp user reset-password 1 &lt;span class="nt"&gt;--show-password&lt;/span&gt; &lt;span class="nt"&gt;--skip-email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the database
&lt;/h3&gt;

&lt;p&gt;Generate a new password hash using the tool at &lt;a href="https://craftcmspwgen.james-nock.co.uk/" rel="noopener noreferrer"&gt;craftcmspwgen&lt;/a&gt; (yes, it says Craft, but it works on WordPress as well).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;wp_users&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;user_pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"&amp;lt;password_hash&amp;gt;"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_login&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@purzlbaum" rel="noopener noreferrer"&gt;Claudio Schwarz&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-wall-covered-in-lots-of-graffiti-and-stickers-S2W3Q_2Pv84" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>howto</category>
    </item>
    <item>
      <title>(My first time) Installing Laravel</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Sun, 24 Nov 2024 15:22:45 +0000</pubDate>
      <link>https://forem.com/moopet/my-first-time-installing-laravel-2ngc</link>
      <guid>https://forem.com/moopet/my-first-time-installing-laravel-2ngc</guid>
      <description>&lt;p&gt;Sometimes, especially when you're at the beginning of your career, it can seem that you're following the instructions and getting nowhere - while everyone else seems to find it terribly easy.&lt;/p&gt;

&lt;p&gt;It can be pretty disheartening, and I want to describe a couple of ways I experience exactly the same thing even after, well, &lt;em&gt;decades&lt;/em&gt;. So here I am, trying to detail the stumbles and bumbles I make trying to get things to work. This is my first post on the subject, but I hope to make more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's learn Laravel.
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Laravel strives to provide an amazing developer experience [...]&lt;br&gt;
Whether you are new to PHP web frameworks or have years of experience [...]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Laravel is touted as being the "developers' framework, at least in PHP circles, because it's more bare-bones than the others and it takes simple yet strict architecture decisions. I've done a few other PHP frameworks - Drupal 7, Drupal 8+, Symfony, WordPress, Concrete5, PrestaShop, CodeIgniter off the top of my head - so I'm not going into this unprepared.&lt;/p&gt;

&lt;p&gt;I'll start from a barebones laptop and see how far I get.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing prerequisites (tldr; issues: zero, confidence: supreme)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Before creating your first Laravel application, make sure that your local machine has PHP, Composer, and the Laravel installer installed. In addition, you should install either Node and NPM or Bun so that you can compile your application's frontend assets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OK. Not a problem. I'll &lt;code&gt;apt install&lt;/code&gt; myself some of that PHP, get composer from... getcomposer.org, and figure out how to install the "Laravel installer" next. Aparently my distro has Node 22 out the box, or it installed when I set something else up earlier, so that should be covered.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;$ composer global require laravel/installer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Boom. Done. No problems yet, we're off to a good start and confidence is at an all-time high.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up a new Laravel project (tldr; issues: some, confidence: high)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;$ laravel new example-app&lt;br&gt;
zsh: command not found: laravel&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh. Maybe I have to revisit that supreme confidence thing.&lt;/p&gt;

&lt;p&gt;So it seems that composer doesn't install anything into regular binary paths and the composer installer doesn't do anything to add itself to the system path either. I've never really had to face that before, since I've directly run composer-installed binaries from whatever path they appear in. For example, with Drupal, there's &lt;code&gt;vendor/drush/drush/drush&lt;/code&gt; or &lt;code&gt;vendor/bin/drush&lt;/code&gt; depending on which version you're running. Do I need to add myself a symlink or alias or find the laravel binary wherever composer added it, "globally"?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I don't know&lt;/em&gt;, so I have to search for this.&lt;/p&gt;

&lt;p&gt;I find some clues on a Stack Overflow answer: you can find the composer binary directory with &lt;code&gt;composer global config bin-dir --absolute&lt;/code&gt;, and apparently in modern versions of composer everything with an executable command gets it put into that directory rather than losing itself to the hierarchy.&lt;/p&gt;

&lt;p&gt;Good. I can add something to my startup script to put that in my path... except that command produces more than just the path...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;$ composer global config bin-dir --absolute&lt;br&gt;
Changed current directory to /home/moopet/.config/composer&lt;br&gt;
/home/moopet/.config/composer/vendor/bin&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;... and I can't use that whole string as a directory. Maybe I'll need to use &lt;code&gt;tail&lt;/code&gt; to get the last line, or something. Wait, no there's another comment on this SO answer which includes the &lt;code&gt;--quiet&lt;/code&gt; flag. What does that do? I'll try &lt;code&gt;composer --help&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Usage:&lt;br&gt;
  list [options] [--] [&amp;lt;namespace&amp;gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, uh, turns out that running &lt;code&gt;--help&lt;/code&gt; on a bare &lt;code&gt;composer&lt;/code&gt; command actually gives help for the &lt;code&gt;list&lt;/code&gt; subcommand rather than composer itself. That had me stumped for a minute.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;-q --quiet        Do not output any message&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hmm, that doesn't sound useful! We want some output. What else is there?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;--raw             To output raw command list&lt;br&gt;
--format=FORMAT    The output format (txt, xml, json, or md) [default: "txt"]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe it's one of these?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The "--raw" option does not exist.&lt;br&gt;
The "--format" option does not exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nope. Like I thought, these are options to &lt;code&gt;list&lt;/code&gt; and not general use flags.&lt;/p&gt;

&lt;p&gt;Let's run it with &lt;code&gt;--quiet&lt;/code&gt; anyway, just for poops and funsies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;$ composer global config bin-dir --absolute --quiet&lt;br&gt;
/home/moopet/.config/composer/vendor/bin&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, what do you know, it worked. It's just badly documented.&lt;/p&gt;

&lt;p&gt;I'll pop that into my shell startup script with a little guard code and we can continue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; composer &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;composer global config bin-dir &lt;span class="nt"&gt;--absolute&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting up a new Laravel project, take 2 (tldr; issues: some, confidence: wavering)
&lt;/h3&gt;

&lt;p&gt;This time &lt;code&gt;laravel new example-app&lt;/code&gt; launches, and prompts me for a few things. I accept the defaults, since I haven't read far enough in the documentation to know the difference, except for the starter kit. I choose, "Breeze" because that's how it is in the documentation.&lt;/p&gt;

&lt;p&gt;It starts the installation process and all looks good until:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;- Root composer.json requires laravel/pint ^1.0 -&amp;gt; satisfiable by laravel/pint[v1.0.0, v1.1.0, v1.1.1].&lt;br&gt;
 - laravel/pint[v1.0.0, ..., v1.1.1] require ext-xml * -&amp;gt; it is missing from your system. Install or enable PHP's xml extension.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wait, PHP needs the XML extension? That was never listed as a requirement! OK, I'll do a quick &lt;code&gt;apt install php-xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Good, all installed. I'll run the setup again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;laravel new example-app&lt;br&gt;
In NewCommand.php line 789:&lt;br&gt;
Application already exists!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh.&lt;/p&gt;

&lt;p&gt;So the installer got part way through, failed because it hadn't verified its dependencies, and has left the app in a broken state. That's not a good sign. Laravel's on, what, version 11?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbie4vnm9w57dpy0g6khi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbie4vnm9w57dpy0g6khi.png" alt="A still from Stranger Things, with Elle hiding behind a wall. Subtitles read, " width="453" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Surely they'd have basic up-front requirements checking by now? Oh well. I'll just &lt;code&gt;rm -r example-app&lt;/code&gt; and start again, nothing lost because I haven't really started yet.&lt;/p&gt;

&lt;p&gt;Long story short&lt;sup id="fnref1"&gt;1&lt;/sup&gt; the next missing dependency was the the DOM extension, or maybe the XML extension. Or maybe the cURL extension.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;- phpunit/phpunit[11.0.1, ..., 11.4.3] require ext-dom * -&amp;gt; it is missing from your system. Install or enable PHP's dom extension.&lt;br&gt;
 - Root composer.json requires phpunit/phpunit ^11.0.1 -&amp;gt; satisfiable by phpunit/phpunit[11.0.1, ..., 11.4.3].&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I need to install &lt;code&gt;php-dom&lt;/code&gt;? No. Try some others. Stack Overflow again. Turns out I need to install &lt;code&gt;php-curl&lt;/code&gt;. Riiiight.&lt;/p&gt;

&lt;p&gt;Onward. &lt;code&gt;rm -r&lt;/code&gt; the directory and run through the setup wizard again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up a new Laravel project, take 3 (tldr; issues: hngg, confidence: still wavering but bolstered by recent problem-solving success)
&lt;/h3&gt;

&lt;p&gt;It prompts me for which database server to use. All of them say, "Missing PDO extension" next to them.&lt;/p&gt;

&lt;p&gt;Sigh.&lt;/p&gt;

&lt;p&gt;I break out of the installer, delete the whole directory again, &lt;code&gt;apt install php8.3-mysql&lt;/code&gt; because there's no direct &lt;code&gt;php-pdo&lt;/code&gt; package and no alias for &lt;code&gt;php-mysql&lt;/code&gt; that works either, so I've done some tiresome &lt;code&gt;apt search&lt;/code&gt;ing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Illuminate\Database\QueryException&lt;br&gt;
  SQLSTATE[HY000] [2002] Connection refused (Connection: mysql, SQL: select exists (select 1 from information_schema.tables where table_schema = 'laravel_example_app' and table_name = 'migrations' and table_type in ('BASE TABLE', 'SYSTEM VERSIONED')) as 'exists')&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What the what now? &lt;em&gt;At no point has this installer asked me for connection details for a database&lt;/em&gt;. And it's trying to run SQL commands against... something. Who knows?&lt;/p&gt;

&lt;p&gt;As it happens, I have a MySQL server running on another host in my LAN and was prepared to use that (even though I'll note that bringing your own database was not listed as a requirement for Laravel). I guess I should have installed SQLite, maybe that would have worked, since it won't need any credentials.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;sudo apt install php8.3-sqlite&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Try again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;rm -r example-app&lt;br&gt;
laravel new example-app&lt;br&gt;
...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great success. Hacker voice "I'm in". For the win. Success kid.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w1p386c42rvulsjodil.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w1p386c42rvulsjodil.jpg" alt="Random probably popular blonde celebrity struggling to hold a armful of trophies. Text reads, " width="768" height="862"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I made it? I think.&lt;/p&gt;

&lt;p&gt;But if someone had asked me how long this might take, then based off the enthusiastic documentation and reputation, I'd have said 20 minutes. If I was doing it for work, my project manager would have doubled or quadrupled that based on experience of developer estimates.&lt;/p&gt;

&lt;p&gt;How long did it really take me? An hour or so one evening and an hour or so the next day. I wasn't rushing, but it wasn't straightforward.&lt;/p&gt;

&lt;p&gt;And you know what? &lt;em&gt;I'm not happy with it&lt;/em&gt;. It's not using MySQL, because that part of the installer appears to be completely broken. I'm using SQLite and that's one step further from what would be going on in a real production environment. So there're definitely some things left on the &lt;em&gt;TODO&lt;/em&gt; list before I can get going with the actual tutorials.&lt;/p&gt;

&lt;p&gt;But it runs. The build steps claim they went without a hitch.&lt;/p&gt;

&lt;p&gt;I'm ready for the next phase: fixing the &lt;code&gt;JsonException&lt;/code&gt;, &lt;code&gt;Syntax error&lt;/code&gt; and &lt;code&gt;ProcessTimedOutException&lt;/code&gt; that appear in the console as soon as I open the demo page in my browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdiir1aibc6gwbk9uo6yn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdiir1aibc6gwbk9uo6yn.png" alt="Console output of the laravel dev server including syntax errors and exceptions galore" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uh-oh.&lt;/p&gt;

&lt;p&gt;If only things worked out the box, eh.&lt;/p&gt;




&lt;p&gt;EDIT:&lt;br&gt;
I'd like to remind anyone that while I'm grateful for any comments, I'm not looking for solutions to the problems I found in this post. It's about the journey, and how it's difficult for everyone sometimes. I plan on making more posts with the same general aim. There are a lot of good tutorials and questions where people are asking for help already on DEV, and they might be a better place to help out!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Chorus: too late ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>beginners</category>
    </item>
    <item>
      <title>It's not just you. Some things are always a pain.</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Sun, 24 Nov 2024 15:22:35 +0000</pubDate>
      <link>https://forem.com/moopet/its-not-just-you-some-things-are-always-a-pain-35a4</link>
      <guid>https://forem.com/moopet/its-not-just-you-some-things-are-always-a-pain-35a4</guid>
      <description>&lt;p&gt;Sometimes, especially when you're at the beginning of your career, it can seem that you're following the instructions and getting nowhere - while everyone else seems to find it terribly easy.&lt;/p&gt;

&lt;p&gt;So if you're ever feeling like your last technical test was abord the &lt;a href="https://en.wikipedia.org/wiki/Kobayashi_Maru" rel="noopener noreferrer"&gt;Kobayashi Maru&lt;/a&gt;, I get it.&lt;/p&gt;

&lt;p&gt;Sometimes it's something you haven't been taught yet. Sometimes you've been shown it, but it hasn't &lt;em&gt;clicked&lt;/em&gt;. Sometimes you do everything right, and you grok the problem, but the documentation is missing a vital step.&lt;/p&gt;

&lt;p&gt;And you fail anyway.&lt;/p&gt;

&lt;p&gt;And it can be pretty disheartening.&lt;/p&gt;

&lt;p&gt;And you're still probably not an impostor.&lt;/p&gt;

&lt;p&gt;Do you remember a while back, some people started posting (&lt;em&gt;tweeting&lt;/em&gt;, when that was still a thing) confessions like, "I've been a dev 20 years and I don't know how dependency injection works". Or like, "I took down production in my first week at my new job"? They were strangely comforting. It was nice to see how other people screwed up.&lt;/p&gt;

&lt;p&gt;I want to describe some of the times I experience exactly the same thing even after, well, &lt;em&gt;decades&lt;/em&gt;. So here I am, trying something new. A series&lt;sup id="fnref1"&gt;1&lt;/sup&gt; of posts.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Series: &lt;em&gt;n. A number of objects or events arranged or coming one after the other in succession&lt;/em&gt;. I know there's only one post so far. One is a number! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>Fuzzy business: shadowing ssh</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Wed, 16 Oct 2024 09:09:41 +0000</pubDate>
      <link>https://forem.com/moopet/fuzzy-business-shadowing-ssh-21pb</link>
      <guid>https://forem.com/moopet/fuzzy-business-shadowing-ssh-21pb</guid>
      <description>&lt;h2&gt;
  
  
  Shadowing commands
&lt;/h2&gt;

&lt;p&gt;I'm going to show you how to shadow a "real" application so you can bolt things on after the fact.&lt;/p&gt;

&lt;p&gt;I'm going to use shell functions to do it.&lt;/p&gt;

&lt;p&gt;Functions in the command line are like more powerful aliases.&lt;/p&gt;

&lt;h2&gt;
  
  
  bUt YoU dOnT lIke aLiAsEs
&lt;/h2&gt;

&lt;p&gt;Yeah. I posted about that. I know.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/moopet/the-case-against-aliases-2mb1" class="crayons-story__hidden-navigation-link"&gt;The case against aliases&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/moopet" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31518%2Ffda7f57e-7bcf-4a04-b7bf-529373e2a1cd.jpg" alt="moopet profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/moopet" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ben Sinclair
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ben Sinclair
                
              
              &lt;div id="story-author-preview-content-47470" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/moopet" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31518%2Ffda7f57e-7bcf-4a04-b7bf-529373e2a1cd.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ben Sinclair&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/moopet/the-case-against-aliases-2mb1" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 3 '18&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/moopet/the-case-against-aliases-2mb1" id="article-link-47470"&gt;
          The case against aliases
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/shell"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;shell&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/git"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;git&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/moopet/the-case-against-aliases-2mb1" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;93&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/moopet/the-case-against-aliases-2mb1#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              20&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Everything I said there stands.&lt;/p&gt;

&lt;p&gt;We're not adding a subcommand here this time, or forcing default flags. We're going to be overriding one specific scenario, which is when the user types &lt;code&gt;ssh&lt;/code&gt; on its own. Normally &lt;code&gt;ssh&lt;/code&gt; would give you its usage message, and we don't care about that too much. &lt;code&gt;ssh&lt;/code&gt; will never be run without arguments in a script, so there's no worry we'll mess up between environments, and the user can still see the usage information if they mess up, or explicitly by running &lt;code&gt;command ssh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is basically &lt;a href="https://en.wikipedia.org/wiki/Monkey_patch" rel="noopener noreferrer"&gt;monkey patching&lt;/a&gt; as used by such peers as Ruby On Rails, and 1990s virus authors. &lt;/p&gt;

&lt;h2&gt;
  
  
  By your command
&lt;/h2&gt;

&lt;p&gt;So how can you tell if a command is shadowed? Can you run the "real" command?&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;command -v &amp;lt;command&amp;gt;&lt;/code&gt; command on the command to see what your  command is. And you can command your shell to use the "real" command by prefixing it with &lt;code&gt;command&lt;/code&gt;, like, &lt;code&gt;command &amp;lt;command&amp;gt;&lt;/code&gt;.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;command -v ssh&lt;/code&gt; will show you what's going to run when you type &lt;code&gt;ssh&lt;/code&gt; into the shell. I'll use it in a minute to stop my script in its tracks in the case where &lt;code&gt;fzf&lt;/code&gt; isn't available on the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok, I'm sold, what are we going to add?
&lt;/h2&gt;

&lt;p&gt;A fuzzy host matcher&lt;sup id="fnref2"&gt;2&lt;/sup&gt; that gets a friendly description of the host from comments in your SSH config file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41k5texv2pszsbit7uu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41k5texv2pszsbit7uu3.png" alt="The fuzzy matches from my SSH config with the term 'government' entered as an example. The results are unimportant." width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you'll need &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt; installed for this example to do anything interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important bits of the function
&lt;/h2&gt;

&lt;p&gt;It's not massively important how this thing works, except for checking whether any arguments have been passed and falling back to the "real" command if we don't need to call our code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# If any arguments were provided, use the original ssh command instead.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;command &lt;/span&gt;ssh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;return&lt;/code&gt; on its own will return the last exit code, so for example, if someone runs &lt;code&gt;ssh -plop&lt;/code&gt;, it will return 255 because &lt;code&gt;lop&lt;/code&gt; isn't a port number.&lt;/p&gt;

&lt;p&gt;Note that we're using &lt;code&gt;return&lt;/code&gt; rather than &lt;code&gt;exit&lt;/code&gt; here, because otherwise we'd immediately log the user out and that's probably not what they're expecting!&lt;/p&gt;

&lt;p&gt;If you're confused by &lt;code&gt;exit&lt;/code&gt; vs. &lt;code&gt;return&lt;/code&gt;, check my "sourcery" post mentioned later.&lt;/p&gt;
&lt;h2&gt;
  
  
  The full function
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Path to your SSH config file in case it's different.&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;SSH_CONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.ssh/config"&lt;/span&gt;

    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;RESET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;tput sgr0&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;BOLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;tput bold&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;CYAN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;tput setaf 6&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# If any arguments were provided, use the original ssh command instead.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;command &lt;/span&gt;ssh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

        &lt;span class="c"&gt;# "return" on its own will pass the last exit code.&lt;/span&gt;
        &lt;span class="k"&gt;return
    fi

    if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; fzf &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Shadowed ssh command will not run without fzf installed.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
        &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="k"&gt;fi

    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_CONFIG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"SSH config file not found at %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_CONFIG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
        &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="k"&gt;fi

    &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;hosts_with_descriptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;bold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOLD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;cyan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CYAN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;reset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'
        BEGIN { max_host_length = 0 }
        /^#/ {
            desc = substr($0, 2)  # Remove the # from the comment
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", desc)  # Trim whitespace
        }
        /^Host / {
            host = $2
            if (length(host) &amp;gt; max_host_length) max_host_length = length(host)
            if (desc == "") desc = "No description"
            hosts[host] = desc
            desc = ""
        }
        END {
            for (host in hosts) {
                printf "%s%-*s%s  │  %s%s%s\n", bold, max_host_length, host, reset, cyan, hosts[host], reset
            }
        }
    '&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_CONFIG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;selected_line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hosts_with_descriptions&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | fzf &lt;span class="nt"&gt;--height&lt;/span&gt; 40% &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Select SSH host: "&lt;/span&gt; &lt;span class="nt"&gt;--ansi&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;selected_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$selected_line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;1B&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="s2"&gt;[0-9;]*[mK]//g"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$selected_host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Connecting to &lt;/span&gt;&lt;span class="nv"&gt;$selected_host&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
        &lt;span class="nb"&gt;command &lt;/span&gt;ssh &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_CONFIG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$selected_host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No host selected."&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;0
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Whoa, you say. What's that crazy &lt;code&gt;awk&lt;/code&gt; script in the middle doing there? Well I'll tell you what it's doing there. I cba to do that bit myself so I got AI to do it for me. I know, I know, I'm a bad person. It's not relevant to this blog post though.&lt;/p&gt;

&lt;p&gt;And why am I explicitly passing a config file path to &lt;code&gt;ssh&lt;/code&gt;? Because you might not keep it in &lt;code&gt;~/.ssh/config&lt;/code&gt;. I don't, and as for why, well, I'll probably write a post about that later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hook me up
&lt;/h2&gt;

&lt;p&gt;You'll need to load this function before you can use it. That's typically when you start your shell, so you could paste it into your &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt;, or whatever. You can't run it as a standalone script by adding a shebang, though. I wrote about that, too:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/moopet/what-sourcery-is-this-5387" class="crayons-story__hidden-navigation-link"&gt;What sourcery is this?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/moopet" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31518%2Ffda7f57e-7bcf-4a04-b7bf-529373e2a1cd.jpg" alt="moopet profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/moopet" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ben Sinclair
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ben Sinclair
                
              
              &lt;div id="story-author-preview-content-251606" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/moopet" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31518%2Ffda7f57e-7bcf-4a04-b7bf-529373e2a1cd.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ben Sinclair&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/moopet/what-sourcery-is-this-5387" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 14 '20&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/moopet/what-sourcery-is-this-5387" id="article-link-251606"&gt;
          What sourcery is this?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cli"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cli&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/shell"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;shell&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/moopet/what-sourcery-is-this-5387" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;17&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/moopet/what-sourcery-is-this-5387#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  (Hacker voice) I'm in
&lt;/h2&gt;

&lt;p&gt;Yeah, you are!&lt;/p&gt;

&lt;p&gt;Cover image by Photo by &lt;a href="https://unsplash.com/@marcobian" rel="noopener noreferrer"&gt;Marco Bianchetti&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/silhouette-of-man-standing-near-wall-on-dark-area-HY5r718ee7U" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;COMMAAAAAAAAND. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Yes, I'm aware you can use some kind of autocomplete in &lt;code&gt;zsh&lt;/code&gt; with a combination of asterisks and tabs and bears. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>cli</category>
      <category>shell</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Did you come to development from a different career?</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Sat, 24 Aug 2024 12:09:34 +0000</pubDate>
      <link>https://forem.com/moopet/did-you-come-to-development-from-a-different-career-n74</link>
      <guid>https://forem.com/moopet/did-you-come-to-development-from-a-different-career-n74</guid>
      <description>&lt;p&gt;If you weren't always a developer as a profession, what did you used to do?&lt;/p&gt;

&lt;p&gt;Why did you change?&lt;/p&gt;

&lt;p&gt;Looking back, do you have regrets, or do you wish you'd changed earlier in your career?&lt;/p&gt;

&lt;p&gt;I've always worked with computers, though I went through IT support / network admin / hardware engineer roles before settling on software development. Sometimes I wish I'd done dev work from the get-go - I was a hobbyist programmer - but other times I'm grateful for having a bit more wide-ranging experience as I get older.&lt;/p&gt;

&lt;p&gt;And I'm curious about you!&lt;/p&gt;

</description>
      <category>career</category>
      <category>watercooler</category>
      <category>discuss</category>
    </item>
    <item>
      <title>My work setup for PHP development</title>
      <dc:creator>Ben Sinclair</dc:creator>
      <pubDate>Wed, 10 Jul 2024 11:04:37 +0000</pubDate>
      <link>https://forem.com/moopet/my-work-setup-for-php-development-4dj8</link>
      <guid>https://forem.com/moopet/my-work-setup-for-php-development-4dj8</guid>
      <description>&lt;p&gt;These days the majority of my (programming) work is (in order of SLOC): PHP, Javascript (including Node), CSS, HTML, and shell scripts. I do sometimes dip into other languages, but the instances are tiny compared to these main ones.&lt;/p&gt;

&lt;p&gt;And this is my story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;I'm writing this on my main me-facing machine. It's running Windows 11, but that's almost entirely for Important Gaming Reasons.&lt;/p&gt;

&lt;p&gt;Work provides me with a MacBook Pro, which I need to use for Important Corporate Reasons such as VPN access, and because it has a nicer video meeting experience. But really, I just shell into it and use it as a docker host and network proxy. I don't get along with the Apple keyboard, which is a broken mix of UK ISO and US ANSI and feels like typing on a child's toy. I appreciate the touchpad and screen, which are both impressive - but I much prefer using a mouse. So the Mac is relegated to being a third screen on the corner of my desk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i7b70xe648hnulok41e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i7b70xe648hnulok41e.jpg" alt="A main in a suit carrying too many plain brown paper bags" width="620" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a mixed bag.&lt;/p&gt;

&lt;p&gt;I have a laptop I use if I'm working at the kitchen table, which I occasionally do. It's running ChromeOS though I'll probably swap that out for Linux at some point. "Developer" mode in ChromeOS gives me a command line and shell access, so my workflow with browser/cli is pretty much the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating system
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu0tf2a6kzm2z33smykj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu0tf2a6kzm2z33smykj.gif" alt="John Connor in Terminator 2 saying, " width="400" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything I do for work is through Ubuntu under WSL on the Windows 11 desktop, SSH on the ChromeOS laptop or Barrier'd into the MacBook Pro.&lt;/p&gt;

&lt;p&gt;I know this sounds impractical. There are weird issues, such as Apple's bug where you can't use an IPv4 address in &lt;code&gt;/etc/hosts&lt;/code&gt; for external connections because the firewall blocks it but you can if use IPv6, &lt;em&gt;even if you're not connecting via IPv6&lt;/em&gt;. Oh, there were fun times figuring that hack out.&lt;/p&gt;

&lt;p&gt;But though decade-ago me would have never thought it, nowadays Windows Subsystem for Linux - specifically WSL2 - is actually pretty decent. You can even run X applications if you install an X server, and there are a bunch of those available.&lt;/p&gt;

&lt;p&gt;I don't use X for anything though. If it's not in a browser, I don't tend to care about it, and there's no good reason to use a Linux browser via X compared to a native one.&lt;/p&gt;

&lt;p&gt;I can't help feeling a little bit &lt;em&gt;dirty&lt;/em&gt; for using Windows, but I cram that feeling way down inside and try to ignore it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desktop - &lt;a href="https://github.com/debauchee/barrier" rel="noopener noreferrer"&gt;Barrier&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I don't use this too much any more, but Barrier is a fork of Synergy from before Synergy went closed-source. It bills itself as a kind-of-software-KVM, but what it boils down to is that I can move my mouse between different computers exactly the same as I can between the pair of monitors on my PC. If I move the mouse off the right side of my desktop monitor, it appears on my Macbook, and all mouse and keyboard input are seamlessly transferred there, just as if it was all one glorious whole.&lt;/p&gt;

&lt;p&gt;Hashtag #gamechanger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terminal - &lt;a href="https://wezfurlong.org/wezterm/index.html" rel="noopener noreferrer"&gt;WezTerm&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I have only recently started using WezTerm. "Recently" in terminal-speak means in the last year or two, because things move slowly in this world. I don't tend to change unless there's a good reason.&lt;/p&gt;

&lt;p&gt;So what was my good reason? What was wrong with Windows Terminal/iTerm 2? Well, WezTerm is cross-platform, and I can share the same configuration - in Lua! - between hosts. It doesn't make weird restrictions (like the way Suckless will never support tabs), and it's very customisable.&lt;/p&gt;

&lt;p&gt;As far as customisation goes, it's mostly font, background image and colourscheme. I usually use &lt;a href="https://github.com/morhetz/gruvbox/wiki/Gallery" rel="noopener noreferrer"&gt;Gruvbox&lt;/a&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt; Dark for most things, though I override a couple of bits depending on the context. &lt;/p&gt;

&lt;p&gt;But wait. Background image, you ask? On a terminal? Why? Well, it so happens I have a post about that:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/moopet/my-one-and-only-terminal-tip-ooa" class="crayons-story__hidden-navigation-link"&gt;My one and only terminal tip&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/moopet" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31518%2Ffda7f57e-7bcf-4a04-b7bf-529373e2a1cd.jpg" alt="moopet profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/moopet" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ben Sinclair
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ben Sinclair
                
              
              &lt;div id="story-author-preview-content-516335" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/moopet" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31518%2Ffda7f57e-7bcf-4a04-b7bf-529373e2a1cd.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ben Sinclair&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/moopet/my-one-and-only-terminal-tip-ooa" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 16 '20&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/moopet/my-one-and-only-terminal-tip-ooa" id="article-link-516335"&gt;
          My one and only terminal tip
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/terminal"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;terminal&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/commandline"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;commandline&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/moopet/my-one-and-only-terminal-tip-ooa" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;11&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/moopet/my-one-and-only-terminal-tip-ooa#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            1 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;h2&gt;
  
  
  Shell - zsh
&lt;/h2&gt;

&lt;p&gt;I settled on zsh because it's the default on Macs. I know, I know: I don't really use the Mac much. But it's almost entirely compatible with bash for all the things I care about, and it's available on all the systems I use.&lt;/p&gt;

&lt;p&gt;I try to make every script I write POSIX-compliant, and if I use something that's different between BSD and GNU (like &lt;code&gt;sed -i&lt;/code&gt;) I'll wrap it in a condition.&lt;/p&gt;

&lt;p&gt;It's fair to say that the choice of shell doesn't really matter much.&lt;/p&gt;

&lt;p&gt;So before you say anything, no, I don't use oh-my-zsh. It doesn't offer me anything I care about that I can't do out-the-box with zsh (or bash for that matter).&lt;/p&gt;

&lt;p&gt;Oh, and &lt;a href="https://dev.to/moopet/the-case-against-aliases-2mb1"&gt;I almost never use aliases&lt;/a&gt;, either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor - &lt;a href="https://neovim.io/" rel="noopener noreferrer"&gt;Neovim&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I've been a Vi/Vim user for a long time, but I switched to neovim a year or two ago. I didn't originally see the point in switching, because all the features people went on about in neovim (and they did oh so go on about them! - async, LSPs, embedded terminals, etc.), well, they were either also available in recent versions of Vim or were stuff I really wasn't interested in. Stuff I didn't think belonged in an editor. Stuff that was trying to make Vim into Emacs.&lt;/p&gt;

&lt;p&gt;Well, I still think those same grumpy thoughts, but I decided to try out a few of the neovim "distros" just to see what was out there and have ended up sticking with it. When loaded with plugins, it's certainly buggier than Vim, but it's still also a nice and comfortable editor for me - and it has more of a future, especially now Bram is gone (RIP).&lt;/p&gt;

&lt;h2&gt;
  
  
  Productivity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/tmux/tmux/wiki" rel="noopener noreferrer"&gt;Tmux&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Tmux is, as it's always introduced, a "terminal multiplexer", like GNU screen. It lets you close a terminal session and reopen it without losing your work, and it lets you run multiple terminal sessions at the same time.&lt;/p&gt;

&lt;p&gt;The upshot of this is that I can leave my work running on the MacBook and connect from other machines via ssh and, by running &lt;code&gt;tmux attach&lt;/code&gt; I am right back in the thick of things. I don't need to open a bunch of sessions each time, or remember what directory I was in or what services were running, it's all just there.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/tmuxinator/tmuxinator" rel="noopener noreferrer"&gt;Tmuxinator&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The tmuxinator project is a wrapper for tmux, which lets you manage multiple separate tmux sessions.&lt;/p&gt;

&lt;p&gt;As an example, here's the list of projects I currently have running on my macbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ tmuxinator ls
tmuxinator projects:
bc           biascan      ec           fabric       gce          ifpma
leith        leith-2023   msgan        chickenland  ngs          ngs-new
ods          ren          renaissance  scramble     sf           sf-cms
sf-forms     sf-myplans   sgh          srn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's in these projects? Well... they have a lot of specifics in them, but what they mostly boil down to is similar to this random project I picked as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;windows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ddev start&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;storybook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;panes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;watcher&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd web/storybook&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yarn watch&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;workon mw8&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;panes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;workon ngs&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker exec -it ngs-cms-php drush -y --uri=ngs-cms.shore.signal.sh -r /shore_site/web ws --tail --full --extended --count=1&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;new-cms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;workon ngs-cms-2022&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker exec -it ngs-cms-2022-app vendor/drush/drush/drush ws --extended&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;workon mw8&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker exec -it ngs-middleware-app vendor/drush/drush/drush ws --extended&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;build-services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;panes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;new-cms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;workon ngs-cms-2022&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd web/storybook&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yarn storybook&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, I have tmux windows for a Drupal 7 project, its Drupal 10 rebuild, some shared middleware, the front-end build services, a storybook server and a metric ton of logging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqscb38nc4uknkctrol4t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqscb38nc4uknkctrol4t.jpg" alt="The ridiculous number of switches and dials in the Concorde cockpit" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can spin this all up with &lt;code&gt;tmuxinator &amp;lt;project name&amp;gt;&lt;/code&gt;, and switch between its collection of windows with &lt;code&gt;Ctrl-A &amp;lt;number&amp;gt;&lt;/code&gt; and to any number of completely different projects with a pop-up menu I get by hitting &lt;code&gt;Ctrl-A S&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://ddev.com" rel="noopener noreferrer"&gt;Ddev&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Ddev is something that lets you create containerised (docker) environments for projects. It works with PHP and Node and (experimentally) Python.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8izlytixeun18eup0g4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8izlytixeun18eup0g4.jpg" alt="A small statue of a giraffe standing on a zebra which in turn is standing on an elephant" width="491" height="960"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The thing that manages the stack, basically.&lt;/p&gt;

&lt;p&gt;My company used to use a home-grown docker-compose wrapper for this, back when there weren't many options. Ddev is really solid, though, and every time I have to work on a legacy or inherited project I'll convert it to Ddev as soon as I check it out. It doesn't usually take very long, and it means we have a consistent way of working. It makes it incredibly quick and easy to switch between PHP or Node versions, for instance.&lt;/p&gt;

&lt;p&gt;Some of its main selling points for me are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;a href="https://mutagen.io/" rel="noopener noreferrer"&gt;Mutagen&lt;/a&gt; for the file system, so it's fast even on MacOS (where Docker mounts are notoriously slo-o-ow)&lt;/li&gt;
&lt;li&gt;Easily lets you reconfigure PHP and Node versions&lt;/li&gt;
&lt;li&gt;Knows about a bunch of third-party apps - for instance, if you have a database GUI app like TablePlus or DBeaver you can launch it from the command line&lt;/li&gt;
&lt;li&gt;Supports community-maintained add-ons &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and so on. It's good.&lt;/p&gt;




&lt;p&gt;Image credits:&lt;br&gt;
"Man Carrrying Groceries" - &lt;a href="https://morguefile.com/p/1001058" rel="noopener noreferrer"&gt;DodgertonSkillhause&lt;/a&gt;&lt;br&gt;
"The Ridiculous Switcherama of the Concorde Cockpit" - Me.&lt;br&gt;
"Statue" - &lt;a href="https://morguefile.com/p/1070446" rel="noopener noreferrer"&gt;lauramusikanski&lt;/a&gt;&lt;br&gt;
"Scene from Terminator 2" - Sorry, I can't hear you I'm going through a tunnel&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I pronounce it the way it's spelt. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>php</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
