<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Steve Simkins</title><description>DX Engineer | Defending the Open Web</description><link>https://stevedylan.dev/</link><item><title>2026, the Future of this Website, and the Web Itself</title><link>https://stevedylan.dev/posts/2026-site-plans/</link><guid isPermaLink="true">https://stevedylan.dev/posts/2026-site-plans/</guid><description>A small reflection and set of plans for making a ripple in a big lake</description><pubDate>Thu, 01 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/surf-the-web.BzbfcNYC_Za5vKY.webp&quot; alt=&quot;surf&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I just finished reading &lt;a href=&quot;https://henry.codes/writing/a-website-to-destroy-all-websites/&quot; target=&quot;_blank&quot;&gt;A website to destroy all websites&lt;/a&gt; (if you haven’t already you really should), and I’m inspired to put a breath of fresh air into this website which I call home. I’ve always believed in the importance of having a personal site, and while I have added my own small pieces of flair, I think there’s a lot more I can do to help put myself on the page. Currently my website is utilitarian, built to deliver information in a clean way. While I do enjoy the minimal and clean aesthetic, a lot of my own personality gets washed out. Not sure what the changes look like yet, but they will come, and I will have fun :)&lt;/p&gt;
&lt;p&gt;Some other things I’m looking to add / change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Look into webmentions&lt;/li&gt;
&lt;li&gt;Create a &lt;a href=&quot;https://nownownow.com/about&quot; target=&quot;_blank&quot;&gt;“now page”&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Move my &lt;a href=&quot;https://bearblog.stevedylan.dev&quot; target=&quot;_blank&quot;&gt;bear blog&lt;/a&gt; style content to something like &lt;code&gt;/updates&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While something like a personal site is small and probably doesn’t matter much in the grand scheme of the internet, it does matter to me. I’ve spent plenty of time beating the point that the web is what you make of it and how you engage it. A few months ago I dumped most of my social media accounts and my mental state has improved significantly. Writing regularly on this blog and my bear blog have introduced me to so many wonderful people via &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;email&lt;/a&gt; that I now keep up with via RSS. I’ve taken small explorations into &lt;a href=&quot;https://geminiprotocol.net&quot; target=&quot;_blank&quot;&gt;Gemini&lt;/a&gt; and even have an iOS client in the works just for my own fun.&lt;/p&gt;
&lt;p&gt;Whether its HTML, CSS, or just plain text, &lt;em&gt;you&lt;/em&gt; can build, create, express, and share with real people. It doesn’t have to be mind numbing feeds that get you riled up over pointless issues, or a bunch of really crappy AI slop videos. The internet can be wonderful, and it starts with us.&lt;/p&gt;
&lt;p&gt;Make a personal website, make a blog, and if you have questions or have no idea where to start, I’m totally willing to &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;help&lt;/a&gt;. My time and my talents are small, but I firmly believe the we can make something great.&lt;/p&gt;
&lt;p&gt;See you around the web 🏄&lt;/p&gt;</content:encoded></item><item><title>48 Hours Disconnected</title><link>https://stevedylan.dev/posts/48-hours-disconnected/</link><guid isPermaLink="true">https://stevedylan.dev/posts/48-hours-disconnected/</guid><description>I spent two days with zero access to my cell phone or computer, and this is what I discovered</description><pubDate>Tue, 02 May 2023 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/disconnected.DBiuM4j8_2tLk7J.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;1086&quot; height=&quot;724&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It was a Thursday afternoon at the chiropractor. I was still on my paternity leave, helping my wife take our kids so our youngest could get some adjustments. Like most dads I sat around, waited, and scrolled on my phone. Halfway through I could tell my wife was miffed, and I thought it was due to our appointment being fifteen minutes late. In reality, my son had been trying to get my attention for several minutes, I ignored him, and he gave up. When she told me later that day I could vividly see it happen, like I was there, but I wasn’t.&lt;/p&gt;
&lt;p&gt;This was not the first time. I had a problem, like the majority of society, having self control over my phone. There were countless other instances like this one where I really screwed up and gave my kids a mental image of what it means to be an absentminded father. It was time to stop, so I made the decision to lock my phone and computer away all weekend. What happened next has honestly become a pivotal moment in my personal life. Here’s a few things I learned along the way that I hope will resonate with others.&lt;/p&gt;
&lt;h2&gt;I spend a lot of time checking my phone&lt;/h2&gt;
&lt;p&gt;Probably a no-brainer for most, especially if you just look at the Screen Time section of your iPhone. I was one of them, thinking “yeah I spend a good bit of time on my phone and could probably do a better job with that,” but it’s one thing to see a number of hours on a chart, and something completely different to experience it. During my two days I would feel the urge to check my phone at completely unnecessary moments, like in the middle of dinner or waiting for coffee to finish brewing.&lt;/p&gt;
&lt;p&gt;These were instances that I really didn’t need to check my phone but wanted to simply fill a gap; I felt a need to stay busy and occupied. It’s only when you’re forced into that scenario where you realize how little you can sit and do nothing. From this I also saw how much more time I had without my phone. The little moments of time between tasks or living that I filled with my phone piled up, and ultimately resulted in a large amount of time. I suppose that’s where the number of hours on Screen Time come into play.&lt;/p&gt;
&lt;p&gt;With all these spare pockets of time, I felt the need to fill them with something, and so my reading habits skyrocketed. I can’t remember the last time I read books just to read, and that is something truly valuable. While I do a lot of reading through my phone or computer, it’s completely different to find yourself alone with one author and their thoughts. The temptation to switch from a partially finished blog post, to a garbage Tweet, to a video of something silly is completely removed. Your attention span is forced to stick with just one thing. Writing this feels ridiculous because I grew up reading books, lots of them; this concept isn’t revolutionary. However, the self-realization of how far we have come is nothing short of shocking when you experience it yourself.&lt;/p&gt;
&lt;h2&gt;My mental health is directly affected&lt;/h2&gt;
&lt;p&gt;A natural effect of not having a phone or a computer is not checking any social media of any kind. As you would expect, mental health improves. Twitter has become my bread and butter for social media intake, and while I have carefully curated my feed to be as positive as possible, I can’t deny how much less negative I was during the two days. Truthfully, it was the happiest I had been in a while. For me at least, it was enough to convince me that any kind of social media will have negative affects on my mental health. Previously I always told myself social media is fine, that how you use it defines how it affects you, but I’m not sure I can say that anymore. Of course I’m sure there are others reading this that have achieved social media nirvana, but I would challenge them to just take two days and see for themselves if that’s true.&lt;/p&gt;
&lt;p&gt;Taking these two days also showed me how busyness was infecting my mental health. By always scrolling, always reading, always tinkering, I was more busy than productive. My hands and mind were always tending to something, but the distractions made them switch too often. Eliminating the distractions helped me realize how the clarity improved my mental state. How I go back to working on a computer every day is still something I’m working out, but I’m hoping the answer lies in closing everything but the task at hand, documenting thoughts for future ideas and projects, or just disconnecting entirely.&lt;/p&gt;
&lt;p&gt;The final area of social media that died was self image. Typically when I post some content, I’m always tempted to check the notifications to see if people like it or not. It’s a gauge of self worth and esteem that really shouldn’t be there, and it was refreshing to have it ripped out. I just created for the sake of creating, without thinking if someone would like it or not.&lt;/p&gt;
&lt;h2&gt;I am so much more aware without my phone&lt;/h2&gt;
&lt;p&gt;As I found myself in those moments where nothing in particular was happening, twiddling my thumbs in boredom or sitting with someone, I realized how aware of my surroundings I truly was. It makes sense, if you’re looking down at your phone then you’re not looking up ¯_(ツ)_/¯ There would be moments where I would be trying to talk to my wife and she would be on her phone, and it would take her a second to even notice I was talking. I can say this because in our marriage I have absolutely been worse in this area, more than I want to admit. It gave me an understanding of what it’s like to be on the other side of that fence, and I needed to feel that.&lt;/p&gt;
&lt;p&gt;Apart from human interaction, my awareness in the outdoors and nature was also heightened. We took a day to explore a river gulf we hadn’t been to yet, and there were multiple moments while walking that I caught myself feeling the urge to check my phone. If I had my phone, I would have missed the smile on my son’s face as he saw the river running next to us, or the butterfly that landed at our feet. Those are the moments I have missed before because my habits convinced me that I was bored and needed entertainment, and that’s deeply saddening. What other precious moments have I missed?&lt;/p&gt;
&lt;h2&gt;My Apple Watch can handle almost all my needs&lt;/h2&gt;
&lt;p&gt;I made the decision that I would keep my Apple Watch during the two days away, mostly because I knew it would be limited without the iPhone paired as it’s just the wifi model. This caused me to rely on it more for daily uses, and to my surprise, it did really well! Anytime I needed the time or to set a timer, it had me covered. I could still listen to music and podcasts while doing dishes, I could use my compass and waypoints while hiking, I could check the weather while using my wifi, I could send and receive iMessages, and more.&lt;/p&gt;
&lt;p&gt;This proved something important: when I go back to having a phone and computer, I could depend on my watch even more. If I have my phone turned on and somewhere in the house, I’ll get notifications if something urgent pops up. I didn’t have to worry about missing out, because everything else that is lower priority would still be there when I got back. I think this is the reason we get Apple Watches to begin with — to see a notification to know if you need to pull your phone out for the next thirty minutes — but actually practicing that effectively becomes rare. Having a day or two without a phone can quickly change that.&lt;/p&gt;
&lt;h2&gt;Now what?&lt;/h2&gt;
&lt;p&gt;The thought of going back to the world of overstimulating, entertainment saturated, and attention kidnapping was daunting. It was only two days, but I immediately could understand how I had changed for the better, and I didn’t want to lose that. Yet there is no good way to both free yourself of the internet and still be productive, engaging, and helpful to the people in our digital spaces.&lt;/p&gt;
&lt;p&gt;To be honest I’m still figuring it out, but the first step for me was to physically remove my phone as much as possible. Leave it in another room, have my Apple Watch on me, and just live as much as I can without it. When I go out of the house I now try to keep my phone in my pocket as much as possible, being aware that I can pull it out if I need to. More times than not, though, it can just stay there. I now try to designate time to work, to be focused in on a phone or computer, and separate that time and space from the time I spend with my family.&lt;/p&gt;
&lt;p&gt;My paternity leave will end in a few weeks, so how I handle being plugged in will probably change soon, but the feeling of these forty-eight hours will not. I will never forget how such a simple task of giving up my phone and computer gave me so much happiness, freedom, and clarity that I desperately needed. I still enjoy technology, the people building it, and how it ultimately improves our daily lives. Technology is neither good nor evil, it is neutral. However, how humans use it will always be in a balance of good and evil. Each day we have the personal choice of how we will use the tools at our disposal and how they will affect the people around us, and sometimes it’s as simple as leaving your phone in another room.&lt;/p&gt;</content:encoded></item><item><title>A Terminal Based Workflow</title><link>https://stevedylan.dev/posts/a-terminal-based-workflow/</link><guid isPermaLink="true">https://stevedylan.dev/posts/a-terminal-based-workflow/</guid><description>A deeper look at why a integrated terminal workflow is more than just using vim</description><pubDate>Wed, 06 Mar 2024 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/Qma8MhvupyHTDmBGiH3yz37acPdqu966YF7rrSk9QDXx6B.vLyctAR3_3xTuR.webp&quot; alt=&quot;header image&quot; loading=&quot;lazy&quot; width=&quot;3592&quot; height=&quot;2378&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In a &lt;a href=&quot;https://stevedylan.dev/posts/why-i-learned-vim&quot; target=&quot;_blank&quot;&gt;previous blog post&lt;/a&gt; I talked about why I learned Vim, and how it boosted my speed and productivity. In some ways it’s true, but I don’t think I had a grasp on the whole picture. After I watched &lt;a href=&quot;https://youtu.be/5wy2iLU5fs0?si=uZ2e6_EUFkrk4Vrp&quot; target=&quot;_blank&quot;&gt;this video&lt;/a&gt; I realized it wasn’t necessarily just Vim/Neovim, but my terminal based workflow that was at play. I recently gave &lt;a href=&quot;https://zed.dev&quot; target=&quot;_blank&quot;&gt;Zed&lt;/a&gt; another try as it has some attractive features, including &lt;a href=&quot;https://zed.dev/docs/vim&quot; target=&quot;_blank&quot;&gt;a whole page in their docs dedicated to Vim.&lt;/a&gt; It is minimal with Vim keybindings enabled, as well as pretty speedy since its GPU enable and built on Rust. Nevertheless I still felt clumsy, and I realized why: the terminal. With my current terminal workflow I’m able to easily switch between different projects without having multiple code editor windows opened, and I have better access to CLI tools that compliment my developer workflow. I just can’t replicate that outside of the terminal right now.&lt;/p&gt;
&lt;p&gt;This epiphany led me to think more about the flow that I had built, and the tools I couldn’t do without. So in this post I’ll layout my workflow and programs in hopes it benefits someone else looking to take advantage of a terminal based dev environment.&lt;/p&gt;
&lt;p&gt;I will clarify that by no means do I suggest that a terminal based workflow guarantees speed. It’s just one way of creating an effective environment, and in the end I hope people find what works best for them. I’m personally open to whatever will make me more productive, and I hope this post might give insights to others.&lt;/p&gt;
&lt;h2&gt;Tmux &amp;amp; Session Management&lt;/h2&gt;
&lt;p&gt;At the core of my workflow is &lt;a href=&quot;https://github.com/tmux/tmux&quot; target=&quot;_blank&quot;&gt;Tmux.&lt;/a&gt; This tool allows you to create multiple terminal sessions, windows, and panes in just one terminal emulator window. Instead of having a terminal open for every project I might be in, I can just have one. A Tmux session will look like any other terminal window, the difference is the ability to disconnect and then re-attach to that same session. So I can be working on something, leave it, then come right back to it.&lt;/p&gt;

&lt;p&gt;Additionally I can create multiple panes and windows inside a session. I generally create a session per project, and each session might have two windows (e.g. one for a client side repo, the other for a server side repo). This both helps keep projects unified yet organized.&lt;/p&gt;

&lt;p&gt;Where it gets really good is having a solid session manager, and that’s where &lt;a href=&quot;https://github.com/joshmedeski/sesh&quot; target=&quot;_blank&quot;&gt;Josh Medeski’s Sesh&lt;/a&gt; comes into play. With this I can easily change between different sessions, and thus easily switch between different projects. This has become essential to my workflow as I often might be working on 2-3 different projects at a time, all with multiple windows and panes each. Each one can be unique to what I’m working on as well.&lt;/p&gt;

&lt;h2&gt;Neovim Plugins&lt;/h2&gt;
&lt;p&gt;After Tmux, of course the next import piece of my workflow is Neovim. Configuring Neovim will be different for everyone as it should be since people have different preferences and workflows. However I will share some plugins that really help my workflow.&lt;/p&gt;
&lt;p&gt;The first is &lt;a href=&quot;https://github.com/nvim-telescope/telescope.nvim&quot; target=&quot;_blank&quot;&gt;Telescope&lt;/a&gt;, which is a no brainer for most people already using Neovim. It allows me to quickly jump to different files, search a string through my entire project, or sort through diagnostic issues. It’s incredibly powerful and extensible, and I would highly recommend getting familiar with all its abilities.&lt;/p&gt;

&lt;p&gt;Another one I use fairly often is &lt;a href=&quot;https://github.com/nvim-neo-tree/neo-tree.nvim&quot; target=&quot;_blank&quot;&gt;Neo-tree&lt;/a&gt;. I know some people are anti-file-tree but I personally really enjoy it. It is setup to appear in the middle of my screen, and I use it to help navigate where a file might be, edit a file name, add new files, etc. Since the majority of my work is in JavaScript / Next.js, it’s helpful to distinguish which &lt;code&gt;page.tsx&lt;/code&gt; or &lt;code&gt;route.ts&lt;/code&gt; file I’m currently working on.&lt;/p&gt;

&lt;p&gt;Having an LSP (Language Server Protocol) and completions setup is also essential for a good workflow in Neovim. This is what provides hints, completions, diagnostics, or even docs for the language you’re working in. Cannot state how helpful these are when working in a typed language like Typescript or Go. I would also say it’s beneficial to learn how to set it up manually, and &lt;a href=&quot;https://youtu.be/S-xzYgTLVJE?si=xG7c-Yx0fkxRHwx0&quot; target=&quot;_blank&quot;&gt;Typecraft’s video&lt;/a&gt; does a great job showing how it’s done.&lt;/p&gt;

&lt;p&gt;These two are on the smaller side but are still really great to use. The first is &lt;a href=&quot;https://github.com/christoomey/vim-tmux-navigator&quot; target=&quot;_blank&quot;&gt;Tmux Navigator.&lt;/a&gt; This allows you to navigate between an open Neovim pane and a Tmux pane without using a Tmux prefix. For example, instead of navigating with &lt;code&gt;Ctrl + b - l&lt;/code&gt; I can just use &lt;code&gt;Ctrl - l&lt;/code&gt;. It’s the same mapping for switching between Neovim panes and Tmux panes, which is a huge quality of life improvement. The other small mention is &lt;a href=&quot;https://github.com/FabijanZulj/blame.nvim&quot; target=&quot;_blank&quot;&gt;blame.nvim.&lt;/a&gt; With this tool I can hit &lt;code&gt;Space-b&lt;/code&gt; to see line by line who changed what when. This is great when working with other people on a project and you’re trying to find out what changed when. There’s also the LazyGit plugin for Neovim, but it deserves its own section.&lt;/p&gt;
&lt;h2&gt;Git Management&lt;/h2&gt;
&lt;p&gt;For the longest time I just used git in the command line for handling any of my git needs, but a lot of that changed when I started working on more team oriented projects. Handling git conflicts was a nightmare, as well as going through git history to see what changed at each commit. LazyGit changed all of that. This tool really simplifies things like cherry picking commits, handling conflicts, and viewing rich history. There’s so much it can do that I haven’t even touched the surface on, and I would recommend &lt;a href=&quot;https://youtu.be/CPLdltN7wgE?si=7XqMjlwpi5tmWFpA&quot; target=&quot;_blank&quot;&gt;this video&lt;/a&gt; by its creator to see what’s possible (if you’re like me you’ll also learn more about git itself from it).&lt;/p&gt;

&lt;h2&gt;Bringing it All Together&lt;/h2&gt;
&lt;p&gt;Let’s do a run through of what starting and managing a project might look for me with this workflow. First &lt;code&gt;t&lt;/code&gt; to start my Tmux session manager, then navigate to my dev folder. There I’ll run a command like &lt;code&gt;npx create-next-app@latest&lt;/code&gt;. Once the repo is created I’ll &lt;code&gt;cd&lt;/code&gt; into it and run &lt;code&gt;nvim&lt;/code&gt; which will greet me with a telescope window of all the files that I can fuzzy find through. Then I might open a split pane to the right with Tmux so I can have a terminal to run commands like &lt;code&gt;npm run dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the project has multiple repos like a server/client combo or I’m referencing another repo, I’ll create a new window with the same pane setup. As I work and make changes, I’ll open LazyGit in one of my Tmux panes and run &lt;code&gt;ctrl-b + z&lt;/code&gt; to make it full screen. From there I’ll add my commits and push them up, or make a branch that I can merge main into if I’m already working on a shared project.&lt;/p&gt;

&lt;p&gt;Back in my main Next.js window I might open another split pane below the right one so while the dev server is running I can make test API calls from the terminal with httpie, maybe pipe the results into jq then into a file.&lt;/p&gt;

&lt;p&gt;This is the flexibility of a terminal based workflow that is hard to replicate on something like VSCode or Zed. It’s not even an editor issue in my opinion: it’s a development environment issue. Do code editors like VSCode take out out of that environment? Sorta, not totally, but it’s definitely not the same.&lt;/p&gt;
&lt;p&gt;In my previous post about learning Vim, I posed the question if you yourself should learn it. Again I think that’s perhaps the wrong question. Instead you should ask yourself “How well do I know my development environment?” If you’re happy with running the basic tools in your terminal and switching back and forth between a terminal window and VSCode that’s fine, do what works best for you. On the other side, you might be blown away by how productive you could be with a terminal based workflow, or at the very least what you can do with effective CLI tools. Never stop learning!&lt;/p&gt;</content:encoded></item><item><title>AT Protocol Primer</title><link>https://stevedylan.dev/posts/atproto-starter/</link><guid isPermaLink="true">https://stevedylan.dev/posts/atproto-starter/</guid><description>What is the AT Protocol and why does it matter in the grand scheme of the web</description><pubDate>Wed, 21 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/atprotocol.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you’ve been on the internet for the last 10-20 years then you probably have a good sense of how bad the internet has become. We experience it every day when we pick up our phones and scroll through apps that used to mean something to us. Take Instagram for example. About 10-12 years ago it was a refreshing way to create and share moments in our lives. Resurrecting the square 1:1 aspect ratio that resembled a medium format camera viewfinder, filters that gave a “I mustache you a question” while wearing a short brimmed fedora, and a new wave of photographers making their mark. I even met my wife on Instagram. It was fun. Fast forward to the present and it’s a mindless feed of videos from people you don’t know, making you emotionally angry or trying to convince you to buy something. We lost control over our data, what massive companies did with it, and what actually mattered to us in the end. I don’t think that era of the web is coming back, but I do think there is hope for a better web that is more open.&lt;/p&gt;
&lt;h2&gt;The Problem With the Web&lt;/h2&gt;
&lt;p&gt;There’s a lot that could fill in the blank here in regards to what is wrong with the web right now, but I personally think there are two major pain points.&lt;/p&gt;
&lt;h3&gt;Fragmented Data&lt;/h3&gt;
&lt;p&gt;Right now the majority of the data you create on the internet is stored on servers owned by platforms. If you make a post on Facebook, it lives on Meta’s servers. If you make a post on X, it’s stored on their servers, and so on and so forth.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/old-data-model.png&quot; alt=&quot;old data model&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What you end up with is a fragmented spread of data across multiple platforms that you have no control over. If X decides to nuke your account or if LinkedIn goes out of business, that data is probably gone. Perhaps you can do what my wife and I did when we deleted all our Meta accounts and download archives of that data, but it ends up being pretty useless. Trying to resurrect it and use it on a new platform would be next to impossible. It’s dead.&lt;/p&gt;
&lt;h3&gt;Fragmented Identity&lt;/h3&gt;
&lt;p&gt;Another problem with the current web model is that your online identity is composed of a bunch of small profiles spread across multiple platforms. We all love that existential dread that comes over us when we have to sign up for yet another service and we have to pick a username that may or may not be available, and then somehow keep up with the 15 different usernames across all of our stuff.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/fragmented-identity.png&quot; alt=&quot;fragmented identity&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What an absolute mess. Our identity situation is so bad that we end up creating a link tree page with our huge list of fragmented platforms to help push people in the right direction. Who is Steve online? Hard to say, you have to visit maybe five different sites to figure that out. Perhaps even more frustrating is that we have a solution for this that no platform uses: domains. You’re reading from &lt;code&gt;stevedylan.dev&lt;/code&gt;, a domain I’ve used for years now that I have control over. What if you could use a single domain for everything?&lt;/p&gt;
&lt;h2&gt;The AT Protocol&lt;/h2&gt;
&lt;p&gt;Thus enters the &lt;a href=&quot;https://atproto.com&quot; target=&quot;_blank&quot;&gt;AT Protocol&lt;/a&gt; (aka ATProto for short), a new model of how our data and our identity can live on the internet. Imagine this: you pick one username, &lt;code&gt;stevedylan.dev&lt;/code&gt; for example, you can use it for any app, and your data travels with it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/pds-model.png&quot; alt=&quot;pds model&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With the AT Protocol model for data, everything lives on a single server called a PDS (personal data server). Your content is organized into neat folders that are used by each app. Let’s imagine that we were rebuilding Instagram with this model and we called it &lt;code&gt;at-gram.com&lt;/code&gt; (horrible I know but stay with me). You login to this website, the app will ask for permission to write files to your server, and once you’ve approved it then you can start posting. You post an image of a cat, and in that moment the app will create a new “folder” for this post called &lt;code&gt;com.at-gram.post.image&lt;/code&gt;. When you look inside your personal data server you can look at that data, see how it’s put together, and where it comes from: &lt;code&gt;at-gram.com&lt;/code&gt;. That content you just created doesn’t get stored on some server owned by &lt;code&gt;at-gram.com&lt;/code&gt;; it’s owned by &lt;strong&gt;you&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Everything is tied to your identity that can also be moved from server to server. Let’s say you created an account on a PDS owned by an organization and you want to move it somewhere else. The tools exist where you can make that happen, all your data sticks with your identity, and everything keeps working as normal. My ATProto identity lives on my own PDS called &lt;code&gt;andromeda.social&lt;/code&gt;, but I could just as easily move it to an organization like &lt;code&gt;selfhosted.social&lt;/code&gt;. That is the true beauty and freedom of the AT Protocol: unified identity, unified data, both portable and interoperable.&lt;/p&gt;
&lt;p&gt;I’m glazing over a lot of technical pieces that make up the AT Protocol, so if you’re a nerd like me and want to dive deeper, I would highly recommend &lt;a href=&quot;https://www.youtube.com/watch?v=F1sJW6nTP6E&quot; target=&quot;_blank&quot;&gt;this video&lt;/a&gt; and check out the official &lt;a href=&quot;https://atproto.com&quot; target=&quot;_blank&quot;&gt;website&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How to Get Started&lt;/h2&gt;
&lt;p&gt;There’s a few ways you could start using ATProto. Probably the easiest one is to sign up through &lt;a href=&quot;https://bsky.app&quot; target=&quot;_blank&quot;&gt;Blue Sky&lt;/a&gt;. It’s a social media platform that allows you to create custom feeds and have way more control over your experience than other platforms. You can also check out &lt;a href=&quot;https://blog.vicwalker.dev.br/3lz4g6zxeic2p&quot; target=&quot;_blank&quot;&gt;some other servers out there&lt;/a&gt; or join mine if you &lt;a href=&quot;mailto:contact@stevedylan.dev?subject=Request%20to%20Join%20andromeda.social&quot;&gt;ask nicely&lt;/a&gt; :). When you sign up you’ll generally get an alias like &lt;code&gt;steve.andromeda.social&lt;/code&gt; which is your handle that you can use everywhere, but if you have a custom domain like &lt;code&gt;stevedylan.dev&lt;/code&gt; that you want to use you can follow &lt;a href=&quot;https://www.bskyinfo.com/guides/advanced-features/custom-domains/&quot; target=&quot;_blank&quot;&gt;this guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you have an ATProto account here are a few things you can check out!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leaflet.pub&quot; target=&quot;_blank&quot;&gt;Leaflet&lt;/a&gt; - Tiny interconnected social documents&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pckt.blog&quot; target=&quot;_blank&quot;&gt;pckt.blog&lt;/a&gt; - Blogging made easy&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://offprint.app/&quot; target=&quot;_blank&quot;&gt;offprint&lt;/a&gt; - Publishing for the open web&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://smokesignal.events/&quot; target=&quot;_blank&quot;&gt;Smoke Signal&lt;/a&gt; - Events built on ATProto&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/flashes-for-bluesky/id6741443033&quot; target=&quot;_blank&quot;&gt;Flashes&lt;/a&gt; - Instagram alternative&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://grain.social/&quot; target=&quot;_blank&quot;&gt;grain&lt;/a&gt; - Instagram alternative&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://margin.at/&quot; target=&quot;_blank&quot;&gt;Margin&lt;/a&gt; - Write on the margins of the internet&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blento.app/&quot; target=&quot;_blank&quot;&gt;Blento&lt;/a&gt; - Create your own website stored on your PDS&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://plyr.fm/&quot; target=&quot;_blank&quot;&gt;plyr.fm&lt;/a&gt; - Music on ATProto&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://semble.so/&quot; target=&quot;_blank&quot;&gt;Semble&lt;/a&gt; - A social knowledge network for researchers&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://skyreader.app/&quot; target=&quot;_blank&quot;&gt;Skyreader&lt;/a&gt; - RSS Reader built on ATProto&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tangled.org/&quot; target=&quot;_blank&quot;&gt;Tangled&lt;/a&gt; - Tightly knit social coding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s a lot out there and I’ll try to update this list over time as I find more apps that use ATProto, so save this post and revisit if that sounds interesting to you. If you’re trying to get started with ATProto and get stuck, &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;my inbox is always open&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;The web is in a rough place, but what encourages me is the number of people trying to fight for a more open web through technology like ATProto. Plenty of talented developers are trying to improve the protocol as well as build apps that people can use, but the ultimate deciding factor is us and our time. We can choose to keep using walled garden platforms and sending our data to massive companies, or we can choose a smaller web that actually puts the power back in our hands. With enough people making the same decisions to support protocols like this, we can change the course of the web for good.&lt;/p&gt;</content:encoded></item><item><title>Back to Basic</title><link>https://stevedylan.dev/posts/back-to-basic/</link><guid isPermaLink="true">https://stevedylan.dev/posts/back-to-basic/</guid><description>Wading my way through the mess that is programming today</description><pubDate>Fri, 06 Mar 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/back-to-basics.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you read &lt;a href=&quot;https://stevedylan.dev/posts/programmers-on-the-verge-of-extinction/&quot; target=&quot;_blank&quot;&gt;my last post&lt;/a&gt; then you have a pretty good idea how conflicted I feel about AI and how I’m trying to find balance within programming. In that search I think I found a path I’m going to take for now.&lt;/p&gt;
&lt;p&gt;My wife got me a &lt;a href=&quot;https://www.clockworkpi.com/picocalc&quot; target=&quot;_blank&quot;&gt;Picocalc&lt;/a&gt; for Christmas. It just got here this week, and man it’s a fun device. Essentially a keyboard, speaker, and screen hooked up to a Raspberry Pi Pico H. It includes multiple firmwares you can run, but the most general purpose one is for &lt;a href=&quot;https://mmbasic.com/&quot; target=&quot;_blank&quot;&gt;mmbasic&lt;/a&gt;, a remake of basic for micro controllers. Back in the day when people got computers, they had to learn how to program to use them using something like basic. You would write programs that helped compute or handle tasks. What’s nice about mmbasic is that it has a few extra tricks that older computers didn’t really have.&lt;/p&gt;
&lt;p&gt;With that said, one of my goals is to return to basic (pun intended) and use this little device to solve programming challenges. Having the limitations of basic feels like a nice creative restriction. Ironically, I found a good use for AI in this setup. The manual for mmbasic is a 200 page PDF, not something I can read on this tiny device. So I had Claude dissect the manual into smaller markdown chunks I can open on the picocalc. Now I have everything I need on this chunky scientific calculator, and wherever I take it, I can build.&lt;/p&gt;
&lt;p&gt;I’m still working through the Rust book which I plan to continue alongside the picocalc, but if push comes to shove I’ll put it on pause until I have more dedicated time. I still want to learn it for the versatility, and just the lower level knowledge that comes with it.&lt;/p&gt;
&lt;p&gt;I plan to still use AI in my day to day work, and in some of my side projects, but that’s about it. There are more thoughts on the topic of AI since I discovered &lt;a href=&quot;https://radiant.computer&quot; target=&quot;_blank&quot;&gt;radiant.computer&lt;/a&gt; but will save that for a longer post. For now, I’m going back to basic.&lt;/p&gt;</content:encoded></item><item><title>Building a Guestbook with PGlite, Clerk, and Pinata</title><link>https://stevedylan.dev/posts/building-a-guestbook-with-pglite-clerk-and-pinata/</link><guid isPermaLink="true">https://stevedylan.dev/posts/building-a-guestbook-with-pglite-clerk-and-pinata/</guid><description>A quick walkthough of how I built a guestbook for my website</description><pubDate>Tue, 24 Sep 2024 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/guestbook-cover.CLGMPwqU_MPoSq.webp&quot; alt=&quot;image&quot; loading=&quot;lazy&quot; width=&quot;2260&quot; height=&quot;1186&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When I was first getting started in web development I remember seeing someone’s website and was immediately impressed by one thing: a guestbook. You could sign in with Github and leave a message, similar to someone’s Facebook wall back in the day. I thought that was the coolest thing but had no idea how to build it. Fast forward to this weekend, I was reminded how cool that was and I decided to build it for my own website.&lt;/p&gt;
&lt;p&gt;Normally I would use Supabase DB + Auth for something like this for the ease of use, but I wanted to take a slightly different route. I’ve been playing with PGlite quite a bit in the last few weeks and decided it would be fun to see if I can host it as a server. My coworker Justin recently had a &lt;a href=&quot;https://pinata.cloud/blog/how-to-build-a-persistent-crud-app-using-sqlite-and-deno-js/&quot; target=&quot;_blank&quot;&gt;post about building a CRUD app with Deno, SQLite and Pinata&lt;/a&gt; to handle backups, and it seemed like the perfect setup to pair with PGlite.&lt;/p&gt;
&lt;p&gt;With a weekend to spare I built this out and you can check it out now with &lt;a href=&quot;/guestbook&quot;&gt;this link&lt;/a&gt;! In this post I’ll show you how I built the server, integrated Clerk auth into both the server and the backend, and finally rendering it out into a UI for people to use.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;When it came to setting up this project there were several moving pieces that had to work together.&lt;/p&gt;
&lt;h3&gt;Pinata&lt;/h3&gt;
&lt;p&gt;Naturally since I work for Pinata I already have an account ready to go for this project, but if you haven’t tried it yet then you really should! Creating your account, getting your API key, and firing up the SDK takes just minutes to add file uploads to your account. It was actually the easiest piece of this project, so don’t be shy and try it out &lt;a href=&quot;https://app.pinata.cloud/register&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Clerk&lt;/h3&gt;
&lt;p&gt;Since I wasn’t going to use Supabase for this project I decided to try out &lt;a href=&quot;https://clerk.com&quot; target=&quot;_blank&quot;&gt;Clerk&lt;/a&gt;. I’ve heard nothing but good things, and there’s a reason for that. This platform truly understands how to make auth easy while also giving you loads of control if you want it. Setting it up for my website and the server was super simple, just followed the quick start guide when I onboarded and it was done. Since I’m using Github as my login method I also followed &lt;a href=&quot;https://clerk.com/docs/authentication/social-connections/github&quot; target=&quot;_blank&quot;&gt;this guide&lt;/a&gt; to set that piece up.&lt;/p&gt;
&lt;h3&gt;Server&lt;/h3&gt;
&lt;p&gt;There are so many options out there for building a server/API, so by all means use what feels best for your needs, but I personally love using Hono via Bun. Starting up the project was simple as the command below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;bun create hono guestbook-db&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the repo was created and initialized I installed a few other packages I would need.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;bun add pinata @clerk/backend @hono/clerk-auth @electric-sql/pglite croner&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With everything installed I did some initial setup for the project. In the &lt;code&gt;index.ts&lt;/code&gt; file I imported my dependencies, setup some of the middleware like Clerk and CORS, and just have an entry point setup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Hono&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;hono&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;cors&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;hono/cors&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;PGlite&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@electric-sql/pglite&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;./pinata&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Cron&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;croner&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;clerkMiddleware&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;getAuth&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@hono/clerk-auth&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Hono&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/*&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;cors&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;clerkMiddleware&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Welcome!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only other file I needed to add was in the same &lt;code&gt;src&lt;/code&gt; folder called &lt;code&gt;pinata.ts&lt;/code&gt; with a quick export of the Pinata SDK instance.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;pinata&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pinataJwt&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PINATA_JWT&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pinataGateway&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GATEWAY_URL&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally we have a simple &lt;code&gt;.env&lt;/code&gt; to handle our secrets and vars.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;PINATA_JWT= # The primary Pinata API key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;GATEWAY_URL= # Your Pinata gateway domain e.g. example.mypinata.cloud&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;GROUP_ID= # Group ID where we will store backups&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;CLERK_PUBLISHABLE_KEY= # Clerk public key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;CLERK_SECRET_KEY= # Clerk private key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ADMIN_KEY= # optional key for your own overrides&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Backend&lt;/h2&gt;
&lt;p&gt;With the database server setup it was time to work on adding in the database itself. PGlite is unique in that it runs on WASM, making it lightweight and operable in a browser. For this project I took advantage of the size by creating a backup and restore feature into the server. Instead of relying on a local disk setup, the server will routinely backup copies of the database to Pinata, and if at any point it needs to restart it will reboot from the last backup. Depending on how your database library works you could also add it writing to disk as well for extra security.&lt;/p&gt;
&lt;p&gt;To setup this workflow I created the following &lt;code&gt;initDb&lt;/code&gt; function and call it using an immediately invoked function expression.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async function &lt;/span&gt;&lt;span&gt;initDb&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GROUP_ID&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;DESC&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;gateways&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dbFile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; as &lt;/span&gt;&lt;span&gt;Blob&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			db&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PGlite&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;loadDataDir&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;created_at&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		db&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PGlite&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;./guestbook&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	       CREATE TABLE IF NOT EXISTS messages (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         id SERIAL PRIMARY KEY,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         note TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         author TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         user_id TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         pfp_url TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         username TEXT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	       );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     `&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;&quot;New DB Created&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		throw &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;(async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;initDb&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Database initialized. Snapshot:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Failed to initialize database:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This initialization function will first check if there is a backup file using Pinata. We keep the files organized by creating a &lt;code&gt;group&lt;/code&gt;, and this allows us to filter our files by said group and order them by date. This group was created beforehand, and you could use a script like the following to do so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;pinata&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pinataJwt&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PINATA_JWT&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pinataGateway&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;example-gateway.mypinata.cloud&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;groups&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;My New Group&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there is a database backup in the group, then we download it as a file using the SDK once more with &lt;code&gt;pinata.gateways.get&lt;/code&gt;. Then we simply create and load up a new instance of the database using &lt;code&gt;new PGPlite({ loadDataDir: file })&lt;/code&gt;. However if there isn’t a database backup on Pinata, the function will create a new instance of DB locally and create the default table. Then it’s just a matter of calling the function as soon as the server starts!&lt;/p&gt;
&lt;p&gt;With the database setup and ready to go it was time to start building some endpoints. The first one would be a simple &lt;code&gt;GET /messages&lt;/code&gt; to fetch all current rows in the database and return them as JSON. Additionally we structured the query to reverse the feed results so the most recent would be at the top.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/messages&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;ret&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		SELECT * FROM messages ORDER BY id DESC LIMIT 50;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  `&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ret&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Restore database first&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course we’ll need an endpoint to actually add messages to the database and we’ll use that with the same route &lt;code&gt;/messages&lt;/code&gt; but as a &lt;code&gt;POST&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;interface &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	user_id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	username&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/messages&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (await &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()) as &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getAuth&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;clerkClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;clerk&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; typeof &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; &quot;string&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Invalid note&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;clerkClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; auth&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;INSERT INTO messages (note, author, user_id, pfp_url, username) VALUES ($1, $2, $3, $4, $5)&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				[&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;firstName&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;imageUrl&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error creating message:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to create message&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is where things start to get good. Instead of having the client send a full JSON payload, we can use Clerk to securely fetch some of that information for us as we use Github OAuth as the only authentication method. In the database we have the &lt;code&gt;note&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt; or name of the writer, &lt;code&gt;user_id&lt;/code&gt; from Clerk which we’ll get into later, and the &lt;code&gt;username&lt;/code&gt; for link to the writer’s Github profile. All we have to do is use the Clerk Hono middleware to get the &lt;code&gt;auth&lt;/code&gt; object and verify the user is logged in, otherwise we send a 401. We also make sure that the &lt;code&gt;note&lt;/code&gt; attached from the client is a string. Finally we can get a &lt;code&gt;user&lt;/code&gt; object from Clerk using the &lt;code&gt;userId&lt;/code&gt; and getting all the information we need! Then we just insert a row into the table and return it back to the client.&lt;/p&gt;
&lt;p&gt;In the event that someone mistyped something and wanted to delete their message, or if someone left something unkind, we will want a method to delete it. This time we’ll use &lt;code&gt;DELETE /messages/:id&lt;/code&gt; and get the &lt;code&gt;id&lt;/code&gt; of a message using the path param.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/messages/:id&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getAuth&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;checkQuery&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;MessageRow&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;SELECT user_id FROM messages WHERE id = $1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				[&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			if (&lt;/span&gt;&lt;span&gt;checkQuery&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Message not found&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;404&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;messageUserId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; checkQuery&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;user_id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			if (&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; messageUserId&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					{ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not authorized to delete this message&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					403&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;DELETE FROM messages WHERE id = $1&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			if (&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;affectedRows&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Message not found&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;404&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Ok&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error deleting message:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to delete message&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this endpoint we have to check a few things. First we need to grab the &lt;code&gt;id&lt;/code&gt; of the target note. Then we need to do a general auth check to see if the requester is a user or the admin (me). If they pass that then we do a query of the messages for that note &lt;code&gt;id&lt;/code&gt;, and then we do a check if the requester is either the admin or the author of the note. If they pass then we delete the row from the table and send back an &lt;code&gt;Ok&lt;/code&gt; message.&lt;/p&gt;
&lt;p&gt;That really covers the majority of what I wanted in the guestbook, but if we wanted to we could easily add a &lt;code&gt;PUT&lt;/code&gt; message as well to enable editing old messages. There are a few things left to do though, including our &lt;code&gt;/restore&lt;/code&gt; and &lt;code&gt;/backup&lt;/code&gt; routes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/restore&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		await &lt;/span&gt;&lt;span&gt;initDb&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Ok&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error restoring database:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to restore database.&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/backup&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dumpDataDir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;auto&quot;&lt;/span&gt;&lt;span&gt;)) as &lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				.&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GROUP_ID&lt;/span&gt;&lt;span&gt; ??&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error backing up database:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to backup database&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are really simple routes but play an important role in our database. The &lt;code&gt;/restore&lt;/code&gt; route is a manual reset without restarting the server, where it runs our &lt;code&gt;initDb()&lt;/code&gt; function from the beginning. &lt;code&gt;/backup&lt;/code&gt; will dump our current database state as a compact zip file into our Pinata group using the best upload experience: &lt;code&gt;pinata.upload.file&lt;/code&gt; &lt;em&gt;chef’s kiss&lt;/em&gt;. We’ll use the same logic in our cron job to regularly backup the database.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;job&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Cron&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;0 0 * * *&quot;&lt;/span&gt;&lt;span&gt;, async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dumpDataDir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;auto&quot;&lt;/span&gt;&lt;span&gt;)) as &lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GROUP_ID&lt;/span&gt;&lt;span&gt; ??&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like that our server is ready!&lt;/p&gt;
&lt;h2&gt;Front End&lt;/h2&gt;
&lt;p&gt;Now the fun part, pulling everything together in the app 😎 To start I needed to install and setup Clerk for Astro using &lt;a href=&quot;https://clerk.com/docs/quickstarts/astro&quot; target=&quot;_blank&quot;&gt;this guide&lt;/a&gt;. To stay somewhat in my comfort zone I used the React plugin for Astro so I could make a component that handles everything, and once again Clerk &lt;a href=&quot;https://clerk.com/docs/references/astro/react&quot; target=&quot;_blank&quot;&gt;made that easy too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I made a new component called &lt;code&gt;GuestbookFeed.tsx&lt;/code&gt; and slowly built out the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useStore&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@nanostores/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/client&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	UserButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignUpButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	user_id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pfp_url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	username&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; import.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PUBLIC_API_ENDPOINT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default function &lt;/span&gt;&lt;span&gt;GuestbookFeed&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setMessages&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;[]&amp;gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsSending&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setInputText&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setMessages&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} finally {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	function &lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;DELETE&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInForceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInFallbackRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						forceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						mode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;modal&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						Sign&lt;/span&gt;&lt;span&gt; in &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; Github&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex items-start gap-4 w-full&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;UserButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							appearance&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								layout: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									animations: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							afterSignOutUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;p-1 bg-bgColor border-current border-2 rounded-md w-96&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;&quot;Posting...&quot;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Post&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt; ? (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Loading&lt;/span&gt;&lt;span&gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			) : (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					{&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row justify-between items-start&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row gap-2 items-start&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex-shrink-0 h-7 w-7&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;h-full w-full rounded-full object-cover&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;pfp_url&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col justify-between&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;font-bold text-gray-400&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										{&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;break-words&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;user_id&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () =&amp;gt; &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									x&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					))}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s a lot of code to look at, but when you break it down it’s pretty easy to understand, so let’s do that now. To start we have our main imports at the top.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/client&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	UserButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignUpButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	user_id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pfp_url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	username&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; import.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PUBLIC_API_URL&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default function &lt;/span&gt;&lt;span&gt;GuestbookFeed&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setMessages&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;[]&amp;gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsSending&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setInputText&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we have some simple yet important pieces to our component. The first is our stores like &lt;code&gt;$sessionStore&lt;/code&gt; and &lt;code&gt;$userStore&lt;/code&gt;. These are provided by the Clerk middleware and will give us access to the logged in user’s session tokens to authorize requests. We also have some neat components from Clerk which we’ll get into shortly. Finally we have a slew of state from React to handle inputs and loading states, as well as the &lt;code&gt;useStore&lt;/code&gt; for our auth stores.&lt;/p&gt;
&lt;p&gt;Below that we have just a few primary functions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setMessages&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} finally {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	function &lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;DELETE&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the top we have our function to fetch messages from our API. We made this a public endpoint so we don’t have to authorize it at all, and we just store the array of messages from the database into our &lt;code&gt;Message&lt;/code&gt; array. Next we have our &lt;code&gt;inputHandler&lt;/code&gt; as well as our &lt;code&gt;sendMessage&lt;/code&gt; function, which again just takes the input state and sends it as an API request. What’s special here is the &lt;code&gt;Authorization&lt;/code&gt; header where we use the &lt;code&gt;await session.getToken()&lt;/code&gt; so that our API can authenticate the request. Simple, clean, and effective. If successful we’ll clear the input and refetch the messages. We also have a &lt;code&gt;deleteMessage&lt;/code&gt; function so the author can delete a note from the site if they want to, and finally we have a simple &lt;code&gt;useEffect&lt;/code&gt; to load our messages when the page loads.&lt;/p&gt;
&lt;p&gt;Now all that’s left is rendering our UI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInForceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInFallbackRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						forceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						mode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;modal&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						Sign&lt;/span&gt;&lt;span&gt; in &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; Github&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex items-start gap-4 w-full&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;UserButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							appearance&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								layout: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									animations: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							afterSignOutUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;p-1 bg-bgColor border-current border-2 rounded-md w-96&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;&quot;Posting...&quot;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Post&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt; ? (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Loading&lt;/span&gt;&lt;span&gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			) : (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					{&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row justify-between items-start&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row gap-2 items-start&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex-shrink-0 h-7 w-7&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;h-full w-full rounded-full object-cover&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;pfp_url&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col justify-between&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;font-bold text-gray-400&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										{&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;break-words&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;user_id&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () =&amp;gt; &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									x&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					))}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, a lot of code, but overall not too complicated. At the top we have our Clerk components that determine what a user sees based on their login state. If they’re not logged in then we have a &lt;code&gt;&amp;lt;SignUpButton /&amp;gt;&lt;/code&gt;, and once they do sign in we show them a profile button with &lt;code&gt;&amp;lt;UserButton /&amp;gt;&lt;/code&gt;, and input to put a message in, and a button to &lt;a href=&quot;https://youtu.be/RSuLFvalhnQ&quot; target=&quot;_blank&quot;&gt;send it&lt;/a&gt;. Below our Clerk components we have the actual message feed which renders our &lt;code&gt;messages&lt;/code&gt; array, and includes things like their pfp, name, message, and even an &lt;code&gt;&amp;lt;a /&amp;gt;&lt;/code&gt; tag to link to their Github profile. Finally we also have a little button that will appear for that specific user if they want to delete one of their messages, but not anyone else’s.&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Overall this was a really great little project to build and it touches some of the key pieces of the web: uploads/storage, databases, backend APIs, and front end UIs. It gives you a great feel and stretches your understanding of how all of these pieces work together and how the space is moving. Using tools like Pinata or Clerk really give you the best of both worlds when building tools, which is a great developer experience and a solid finished product. Be sure to &lt;a href=&quot;https://stevedylan.dev/guestbook&quot; target=&quot;_blank&quot;&gt;drop a message&lt;/a&gt; there now if you haven’t already, and thanks for reading! :)&lt;/p&gt;
&lt;h3&gt;Repos&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/stevedylandev/guestbook-db&quot; target=&quot;_blank&quot;&gt;Database &amp;amp; API&lt;/a&gt;
&lt;a href=&quot;https://github.com/stevedylandev/stevedsimkins-dev-astro&quot; target=&quot;_blank&quot;&gt;Astro Site&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Concerning Omarchy and Distro Philosophy</title><link>https://stevedylan.dev/posts/concerning-omarchy-and-distro-philosophy/</link><guid isPermaLink="true">https://stevedylan.dev/posts/concerning-omarchy-and-distro-philosophy/</guid><description>Some thoughts on how distros should be approached and where people should go</description><pubDate>Sun, 09 Nov 2025 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/omarchy-post-cover.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There has been quite a bit of &lt;a href=&quot;https://x.com/dhh/status/1986710414504112336&quot; target=&quot;_blank&quot;&gt;controversy&lt;/a&gt; surrounding DHH’s Omarchy. If you haven’t heard of it, &lt;a href=&quot;https://omarchy.org/&quot; target=&quot;_blank&quot;&gt;Omarchy&lt;/a&gt; is a pre-configured Arch Linux setup/distro using Hyprland. It’s sleek, beautiful, and comes with all the bells and whistles. The big driving factor for building this is DHH himself moved from MacOS to Linux and spent a lot of time configuring his setup, and thought it would be useful for others who want to make the switch. Personally I think it’s an admirable goal to build something that actually helps people move towards tech they otherwise would avoid. Linux is incredibly useful and powerful, especially for programmers. Omarchy has done so well that there is a massive community behind it and DHH’s company now uses it as the default for employees.&lt;/p&gt;
&lt;p&gt;Naturally as a serial tinkerer, I had to try it. I actually went down the Arch + Hyprland path a &lt;a href=&quot;https://github.com/stevedylandev/linux-dotfiles&quot; target=&quot;_blank&quot;&gt;couple of years ago&lt;/a&gt; on a Thinkpad T480 (I know, pumpkin spice latte levels of basic). It was a great learning experience and I enjoyed all the ricing. Omarchy gave me the excuse to buy a BeeLink SER8 as a home server that I’ve wanted for a while now. I ran the install script (before there was an image) and quickly started playing around with Omarchy. Overall it was pretty solid. I think at the time I used the install that left out a lot of the gui apps that I didn’t want. After a while, I started finding pieces I wanted to customize, and that’s where things got a bit hairy. Even today the process of trying to customize &lt;a href=&quot;https://youtu.be/d23jFJmcaMI&quot; target=&quot;_blank&quot;&gt;doesn’t seem that simple&lt;/a&gt;, and that’s where I want to dig in particularly.&lt;/p&gt;
&lt;p&gt;Distros are built to give you a starting point. Arch on its own is a Linux distro. While it does take a lot more configuration, it still puts some of the most crucial pieces together for you. Another distro like Manjaro uses Arch but adds a desktop environment and all the basic setup people expect in a desktop computer experience. Depending on how much you want to customize your experience will greatly influence what distro you pick; the more you want to customize, the harder it will be to use an opinionated distro.&lt;/p&gt;
&lt;p&gt;Neovim is very similar. You can either start a config from scratch or you can use a distro like LazyVim. It’s generally good to start with LazyVim and see what it’s like, however it’s also very opinionated. Over time the chances are high that you will find your own preferences and want to configure it differently. When that time comes, you’ll find customizing a Neovim distro is a horrible experience. It also generally ends up in a bloated complicated mess. Once you take the time to truly understand how to configure it, the manual from scratch approach is a breath of fresh air. It’s completely tailored to you and your needs, not someone else’s.&lt;/p&gt;
&lt;p&gt;For some people distros are the end game; you use it, you’re happy. For others, the distro is just the gateway to a wholly different setup. It really does depend on the context, ie Arch Vanilla vs Arch Manjaro, but the whole reason so many people use Linux is to customize it. How far that customization goes depends on the person, but I generally find there is a balance of what is easier to customize and what is not. In my personal experience, Omarchy was not easy to customize. I ended up doing a fresh installation of Arch and slowly added piece by piece as the need arose, including a window manager. The result is an incredibly lean configuration that fits my personal needs, and that is what is most important to me.&lt;/p&gt;
&lt;p&gt;By all means if you enjoy Omarchy, use it! It’s a damn cool distro to play with. However, if something doesn’t feel right, or you want to change more than seems possible, consider building your setup from scratch. It will be bumpy. You will get stuck. However the knowledge you gain and the tailored experience will be well worth the journey. Call distros for what they are, and don’t treat them as the end-all-be-all of desktop experiences.&lt;/p&gt;</content:encoded></item><item><title>How Gemini Gives me Hope for a Future Internet</title><link>https://stevedylan.dev/posts/how-gemini-gives-me-hope/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-gemini-gives-me-hope/</guid><description>A journey down a deep rabbit hole thanks to cassette tapes</description><pubDate>Thu, 18 Dec 2025 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/gemini-post.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Last weekend I was building a mini RSS reader (you know how I do), and in the process I was going through some of the blog posts in my feed. One of them stood out:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.pabloecortez.com/lost-media-konpeito-tapes/&quot; target=&quot;_blank&quot;&gt;Lost Media: Konpeito Tapes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This grabbed me particularly because I’ve been getting into cassette tapes as a hobby, both collecting and creating them. If you read the post you’ll see that the tapes were originally released on something called &lt;a href=&quot;https://geminiprotocol.net&quot; target=&quot;_blank&quot;&gt;Gemini&lt;/a&gt;. I didn’t know much about it but I did know I wanted to download those tapes! After a bit of searching I found a Gemini client and was able to download the zip files which had everything I needed to make replicas of the original tapes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/konpeito-tapes.jpeg&quot; alt=&quot;konpeito tapes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The tapes are fantastic, but this whole event peaked my curiosity about Gemini. The more I learned, the more I liked. It’s relatively new, only five or six years old, yet seems to have a small yet steady userbase. I found a better client to use in the terminal and I started exploring some more. Soon I found some gemlog (a micro blog on Gemini) aggregators which let me find some interesting capsules (a website on Gemini). I found one in particular that was really well done: loads of posts, cool links to other pages, and even a photo log! I had to navigate to each photo, press a key to open it, and then choose whether to download it or view it in my default app for viewing photos (Apple Preview in my case).&lt;/p&gt;
&lt;p&gt;There was something magical about viewing bits and pieces of this person and their life through text on a screen and images that didn’t load directly on the page. Who else knew this capsule existed? How many more people know Gemini exists? A small web floating on the internet with a vibrant set of people just wanting a peaceful existance. That is one of the primary reasons Gemini was created. The founders wanted a text based internet protocol that would let you link out to other pages, similar to HTML, but without CSS and JavaScript. The web had become too noisy and slowly invaded privacy down to the favicon. There were protocols like Gopher, but they had problems that went unsolved since is inception in the early 1990s. A new protocol was needed, and it became Gemini.&lt;/p&gt;
&lt;p&gt;While I have become a new fan of Gemini, and will talk more about that soon, it’s not the reason of this post. To be frank I don’t think that Gemini is a replacement for the web or that it answers every single problem. I don’t see myself ditching HTML altogether for something like Gemini. It’s not Gemini itself the reason that I write this post, but rather the hope it brings. Gemini exists because a few people said enough is enough with the current state of the internet and did something about it. There were enough people who believed in the same idea and principles that now there is a decent community living on this network. People are sharing their lives, recipes, resources, you name it. It’s not full of people who only talk about a niche topic or just talk about Gemini itself. They just use the protocol the way they envisioned the internet to be.&lt;/p&gt;
&lt;p&gt;That, is what brings me hope. We don’t have to take the abuse of our current internet sitting down. We don’t need a VC funded company to build something that “saves us” by moving us to just another mental prison. We, the people, have the power to buid whatever we want. We can control our experience the internet, not the other way around. We can still connect with other people without social media, without noise, and to much surprise, we can do it without HTML.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/jurassic-park-life-finds-a-way-gif.gif&quot; alt=&quot;life finds a way&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I hope your main takeaway isn’t some kind of lecture on how you should use the internet and what it should look like. You can do whatever you want. However, if you’re like me, and you feel tired, worn out by the current state of the web, and you want to see a change: it’s already happening. That change starts with you, deciding to do something different, no matter what anyone else thinks.&lt;/p&gt;
&lt;p&gt;I know this is a bit weird to make this post on HTML while boasting about another protocol, but I decided it was worth it to communicate the idea. If you’re interested in Gemini and want to learn more or even get started, just follow these two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;amfora&lt;/code&gt; with &lt;code&gt;brew install amfora&lt;/code&gt; or one of the many other &lt;a href=&quot;https://github.com/makew0rld/amfora?tab=readme-ov-file#installation&quot; target=&quot;_blank&quot;&gt;install methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In the terminal run &lt;code&gt;amfora gem.stevedylan.dev/gemini-quickstart&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’ll see you in space 🫡&lt;/p&gt;
&lt;p&gt;p.s. if you thought this post was about some coding CLI, sorry :)&lt;/p&gt;</content:encoded></item><item><title>Building Personal Software in Rust</title><link>https://stevedylan.dev/posts/building-personal-software-in-rust/</link><guid isPermaLink="true">https://stevedylan.dev/posts/building-personal-software-in-rust/</guid><description>The good, the bad, and the ugly of using AI to build software</description><pubDate>Sun, 19 Apr 2026 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/sipp-demo.7eid2iwF_Z18Vi6B.webp&quot; alt=&quot;cover image&quot; loading=&quot;lazy&quot; width=&quot;3600&quot; height=&quot;2263&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For the past month or two I’ve been experimenting with building personal, self hosted software in Rust. The apps include anything from simple note taking, a wine tracker, image resizing, even a full blown RSS aggregator to replace FreshRSS. I’m not going to go into too much detail around the apps themselves, you can check them out &lt;a href=&quot;https://andromeda.build&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt; if you’re really interested. Instead, I want to use this post to talk about how I got here, why Rust, and the good, the bad, and the ugly of building personal software with AI.&lt;/p&gt;
&lt;h2&gt;How I got here&lt;/h2&gt;
&lt;p&gt;It all started with something I wanted: a code sharing app. In my line of work I often had to share snippets of code or ask someone to show me a quick piece of code to help them debug, and I really didn’t like the options out there. Many of them were riddled with ads or they just looked awful. I decided to build my own, as the basics of this app is simply a database with some CRUD operations. At the time I used Bun since it had everything I needed in one neat package, including web server and an SQLite interface. The result was exactly what I wanted. Time passed and I had a few other apps I built with a similar philosophy.&lt;/p&gt;
&lt;p&gt;Between then and now, I had also been learning Rust as much as I could. Along with going through the Rust book and doing exercises, I also had built a simple app or two in order to get more experience. This drive was accelerated as the points from &lt;a href=&quot;https://youtu.be/WhjEL817Onw&quot; target=&quot;_blank&quot;&gt;this video&lt;/a&gt; resonated with me. The thought came to me: what if I rewrote the couple of personal apps I made in Rust? For the memes, but I also liked the idea of making my coding app a web app and TUI all in one. My Rust skills probably could have handled most of the work if I did it by hand, but naturally I took the lazy route. This after all was an experiment; I have very little free time to do such a rewrite by hand. What could go wrong?&lt;/p&gt;
&lt;p&gt;Everything ended up going right. The result of my rewrite of the code sharing app was fantastic. I loved having a single binary no bigger than 13MB that ran the server and the TUI app to create and manage code snippets. Quickly the experiment turned into building more and more apps that I wanted. The stack was so simple that it made it easy to crank out more CRUD apps, so anytime I found myself dissatisfied with a current situation that could be solved with an app, I built it. Now I have a suite of apps that I use all the time and I absolutely love how they’ve turned out. I even started optimizing the workspace to use shared crates and Docker setups to make self hosting a breeze.&lt;/p&gt;
&lt;p&gt;I’ve previously written about how using AI to write code not only takes the fun out of it, but also can reduce your capacity to think critically and solve problems. Admittedly this is still happening as I crank out these apps, but there’s more nuance I’ve discovered while thinking through this experiment.&lt;/p&gt;
&lt;h2&gt;The Good&lt;/h2&gt;
&lt;p&gt;The biggest benefit I’ve found through this experience is the ability to get exactly what I want out of my software, and to do it quickly. I should clarify that I’m not an AI maxi bro. I don’t use git worktrees or have some swarm of agents. I just use Claude Code with the $20 a month plan with no extra usage. Only every now and then would I hit a limit and have to wait. This was possibly due to the fact that I knew exactly what I wanted, and the apps were stupid simple. The beauty of CRUD apps is how repeatable everything is. I quickly created two skills for scaffolding a new app along with styles that unify them.&lt;/p&gt;
&lt;p&gt;Rust proved to be pretty great for these kinds of apps. One of the main reasons I was pursuing Rust was memory efficiency. As I mentioned earlier, my code sharing app was originally made with Typescript using Bun. While that approach was much simpler and easier to do, it also took way more memory. My Bun runtime instances on my server were taking average 65MB of RAM to run, which isn’t the worst, but if you start to multiply that across multiple apps or add more expensive logic, it can quickly grow out of control. Ironically this has become a bigger problem in our current age of AI due to the shortage of RAM across the globe, and how &lt;a href=&quot;https://www.theverge.com/ai-artificial-intelligence/914672/the-ram-shortage-could-last-years&quot; target=&quot;_blank&quot;&gt;it’s only going to get worse&lt;/a&gt;. Optimizing for memory and using little as possible has become a big piece of why I used Rust. The same app with more features only runs on ~10MB. I acknowledge that this logic can’t be applied everywhere, but for personal software like this running on my own hardware, I love having that level of control.&lt;/p&gt;
&lt;p&gt;There are plenty of other lower level languages that could have accomplished these apps, but so far Rust has proven to be especially good for Claude. The Rust compiler is a beautiful thing, providing detailed info about any problems in the code base that AI can react to efficiently. Not only that, if you use the Rust LSP extension in Claude then it will catch issues on the fly. There is still plenty of room for error though, and I can’t say I would take this approach with a serious project, but it’s fine for my own personal software.&lt;/p&gt;
&lt;p&gt;While all of this sounds amazing, and for me personally it excites me at the possibility of &lt;a href=&quot;https://www.inkandswitch.com/end-user-programming/&quot; target=&quot;_blank&quot;&gt;end-user programming&lt;/a&gt;, there are some downsides.&lt;/p&gt;
&lt;h2&gt;The Bad&lt;/h2&gt;
&lt;p&gt;With all the good parts of Rust, there is still the opportunity for a solid footgun. It feels almost dangerous for someone of my level of skill in Rust to create this much software in a language that is known to be incredibly complex. Sure I can test it, and I know how to identify bad patterns that can be corrected, but it does not replace what comes with years of experience in Rust that I simply don’t have yet. Not to mention the dependencies. My apps don’t have many, but it’s still a level of abstraction that I have to eat and hope doesn’t hurt me later.&lt;/p&gt;
&lt;p&gt;The idea of other people taking this approach also has some flaws. I want to believe in a future where people can just create their own software as they need it, but today, the truth is I already know a fair bit of how all of this works. While I don’t have lots of experience in Rust, I do have experience in programming in general and hosting applications. Before even building an app I already knew all the pieces I needed to make it happen, what choices to make in all the available options, and how to execute it in a way that I know will work. AI just works better if you already know exactly what you want and if you can explain it well. The average person is likely not going to have this.&lt;/p&gt;
&lt;h2&gt;The Ugly&lt;/h2&gt;
&lt;p&gt;What I hate the most is the dependency on AI itself. Yes I could probably build these apps without Claude, but it would take much, much longer; more time than I have. Going another level deeper, I hate the dependency on an AI provider. Local models are slowly getting better, but they are a long ways from competing with frontier models, and I hate that. This means if Anthropic has an outage (I know, that could never happen) then I no longer have the ability to keep building what I want. Or if they decide to change their pricing or limits, I might find my speed crippled.&lt;/p&gt;
&lt;p&gt;Perhaps the most frightening, what if I just get kicked out of Claude? Andrew Olsson made a &lt;a href=&quot;https://youtu.be/03wgVlCKKVg&quot; target=&quot;_blank&quot;&gt;video&lt;/a&gt; recently explaining how that happened to him. With little to no explanation, he got an email saying he violated the usage policy. Didn’t detail how exactly, and after reviewing the policy he couldn’t see what he did wrong. Where it really goes downhill is how Claude has zero support. Ironically not even a chatbot. The email did have a Google form to file an appeal, but even it said that the wait could be extensive. It’s been weeks and Andrew still hasn’t heard back. From the comments in the video, this incident isn’t isolated either.&lt;/p&gt;
&lt;p&gt;The irony of this whole project is using something like AI, that could be taken away from me in an instant, to build software that can liberate me from other apps or platforms. In some ways it’s rather depressing, but I’m trying to look beyond our current moment.&lt;/p&gt;
&lt;h2&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;While a few major companies hold the keys to the best AI models, progress is being made in the realm of open source. I have hope that eventually we will get to a place where AI is liberated and works just like an operating system. If we do get there, it opens up a whole realm of end-user programming, where people can use natural language to describe what they want to accomplish on a computer. That specific focus is already being built towards in projects like &lt;a href=&quot;https://radiant.computer&quot; target=&quot;_blank&quot;&gt;Radiant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While I don’t know what the future holds for programming or AI, I do know that I love my little apps, and one way or another, I want to keep writing my own software.&lt;/p&gt;</content:encoded></item><item><title>Indexing Standard.site</title><link>https://stevedylan.dev/posts/indexing-standard-site/</link><guid isPermaLink="true">https://stevedylan.dev/posts/indexing-standard-site/</guid><description>A journey to index a new standard for content publishing and why it matters</description><pubDate>Tue, 14 Apr 2026 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/indexing-standard-site.DhTqT5i0_Z1CGgfT.webp&quot; alt=&quot;A white outline icon of a floppy disk on a dark gray background. The floppy disk has a rectangular shape with a cut corner at the top left, a small rectangular label area at the top, and a circular element in the center with a crosshair or targeting symbol inside it.&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;630&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For decades the internet has been a place to make your voice heard, and the cornerstone for most of that time has been blogs. Even in the rise and fall of social media, blogs continue to have their place in the internet. RSS, as old as it sounds, has also been proven to help connect and keep up with people and their content. However these two pieces of technology have one main problem: distribution. Back in the day, webrings and blogrolls were attempts to help cover this gap, but social media and algorithms became the default way to get that distribution.&lt;/p&gt;
&lt;p&gt;Thankfully, &lt;a href=&quot;https://atproto.com&quot; target=&quot;_blank&quot;&gt;atproto&lt;/a&gt; is paving a different path. Instead of using the old platforms owned by the 1%, people are building solutions that are owned by everyone. One community built solution is &lt;a href=&quot;https://standard.site&quot; target=&quot;_blank&quot;&gt;Standard.site&lt;/a&gt;, a set of JSON schemas known as &lt;a href=&quot;https://atproto.com/guides/lexicon&quot; target=&quot;_blank&quot;&gt;lexicons&lt;/a&gt; that finally give hope to solving the content distribution problem. When a blog, or any app for that matter, uses the Standard.site lexicons, the published content can be indexed by just about anyone. That index can be used to build so many mechanisms for distribution, and none of it is controlled by one individual or organization. You can control how you explore and consume that content.&lt;/p&gt;
&lt;p&gt;This promise of blogs finally getting a new wave of uninhibited distribution truly excited me, and I saw the possibilities at hand for not just blogs, but any kind of social app that has shared content and lexicons. Of course I started hacking away, first by building my own publishing mechanism on my website, then slowing building tools like &lt;a href=&quot;https://sequoia.pub&quot; target=&quot;_blank&quot;&gt;Sequoia&lt;/a&gt; that help anyone with static blogs publish to the same shared network. Naturally I also wanted to see how I could tap into the final state: indexing, the bridge that promised freedom. I eventually completed this mission with a fun app with a feed called &lt;a href=&quot;https://docs.surf&quot; target=&quot;_blank&quot;&gt;docs.surf&lt;/a&gt;. This post goes into a journey to index Standard.site lexicons, the challenges, and how a great community can come together and push the boundaries further.&lt;/p&gt;
&lt;h2&gt;The Challenges&lt;/h2&gt;
&lt;p&gt;It turns out that indexing Standard.site documents in particular has several noteworthy challenges.&lt;/p&gt;
&lt;h3&gt;Publications&lt;/h3&gt;
&lt;p&gt;Each blog post that is turned into a &lt;code&gt;site.standard.document&lt;/code&gt; record also contains a &lt;code&gt;site.standard.publication&lt;/code&gt; referred to as the &lt;code&gt;site&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;$type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;site&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;at://did:plc:abc123/site.standard.publication/3lwafzkjqm25s&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;path&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;/blog/getting-started&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Getting Started with Standard.site&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Learn how to use Standard.site lexicons in your project&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;coverImage&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;$type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;blob&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;ref&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;$link&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafkreiexample123456789&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;mimeType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;245678&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;textContent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Full text of the article...&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;tags&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;tutorial&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;atproto&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;publishedAt&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-01-20T14:30:00.000Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is important, because while the document might have the main content of the blog post, it doesn’t have the full canonical URL of the blog with its post. The &lt;code&gt;textContent&lt;/code&gt; or &lt;code&gt;content&lt;/code&gt; fields are not required, so at the very least we need a link to the post. It has a path, but we need to combine it with the &lt;code&gt;site.standard.publication&lt;/code&gt; record’s &lt;code&gt;url&lt;/code&gt; property to make a complete link:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;$type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.publication&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://standard.site&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;icon&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;$type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;blob&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;ref&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;$link&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafkreiexample123456789&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;mimeType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;12345&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Standard.site Blog&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Documentation and updates about Standard.site lexicons&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;preferences&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;showInDiscover&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means if we want to properly index Standard.site, we need to first grab the document, then grab the publication.&lt;/p&gt;
&lt;div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/standard-site-challenge-1.svg&quot; alt=&quot;Diagram showing the standard site challenge workflow between a client and PDS (Personal Data Server). The client requests a document URI (at://document-uri) from the PDS, which returns a document record containing a publication URI (at://publication-uri). The client then requests this publication URI from the PDS, which responds with a publication record containing the site URL.&quot; loading=&quot;lazy&quot; /&gt; &lt;span&gt;       &lt;/span&gt; &lt;/div&gt;  &lt;div&gt; &lt;div&gt; + −    × &lt;/div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/standard-site-challenge-1.svg&quot; alt=&quot;Diagram showing the standard site challenge workflow between a client and PDS (Personal Data Server). The client requests a document URI (at://document-uri) from the PDS, which returns a document record containing a publication URI (at://publication-uri). The client then requests this publication URI from the PDS, which responds with a publication record containing the site URL.&quot; /&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;  
&lt;p&gt;So lets say someone has the document AT URI (something like &lt;code&gt;at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.document/3mii2k5x4hd2h&lt;/code&gt;) then we need to make a total of two API requests at minimum. Not bad, but it gets a bit more complicated.&lt;/p&gt;
&lt;h3&gt;Verification&lt;/h3&gt;
&lt;p&gt;To quote the &lt;a href=&quot;https://standard.site/docs/verification/&quot; target=&quot;_blank&quot;&gt;Standard.site docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Since Standard.site records reference domains and web pages, a verifiable way for these resources to point back to their corresponding records is needed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We want to make sure that a record for a post is actually from the author of that site. This necessary verification is achieved two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A publication record returned from &lt;code&gt;/.well-known/site.standard.publication&lt;/code&gt; on the publication site&lt;/li&gt;
&lt;li&gt;A document record returned as a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag in the post header&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re keeping track, that means we now need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Request the &lt;code&gt;site.standard.document&lt;/code&gt; record so we can get the publication record&lt;/li&gt;
&lt;li&gt;Request the &lt;code&gt;site.standard.publication&lt;/code&gt; record to get the URL of the site&lt;/li&gt;
&lt;li&gt;Request the publication verification&lt;/li&gt;
&lt;li&gt;Request the main post URL for the document verification&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/standard-site-challenge-2.svg&quot; alt=&quot;Diagram showing the flow of a standard site challenge. A Client sends an at://document-uri request to a PDS (Personal Data Server). The PDS responds with a document record containing an at://publication-uri. The Client then sends this publication URI back to the PDS, which returns a publication record with a site URL. Finally, the Client makes a GET request to the User&apos;s Website/Blog at the path /.well-known/site.standard.publication to complete the verification process.&quot; loading=&quot;lazy&quot; /&gt; &lt;span&gt;       &lt;/span&gt; &lt;/div&gt;  &lt;div&gt; &lt;div&gt; + −    × &lt;/div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/standard-site-challenge-2.svg&quot; alt=&quot;Diagram showing the flow of a standard site challenge. A Client sends an at://document-uri request to a PDS (Personal Data Server). The PDS responds with a document record containing an at://publication-uri. The Client then sends this publication URI back to the PDS, which returns a publication record with a site URL. Finally, the Client makes a GET request to the User&apos;s Website/Blog at the path /.well-known/site.standard.publication to complete the verification process.&quot; /&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;  
&lt;p&gt;You can start to see why this is slowly growing in complexity, and unfortunately it only gets worse (we’ll get to that later). For now, you can get an idea of what we need to do and the challenges at hand. Let’s start talking about some of the solutions I cycled through.&lt;/p&gt;
&lt;h2&gt;Tap + Client&lt;/h2&gt;
&lt;p&gt;From a little bit of research, I found that &lt;a href=&quot;https://github.com/bluesky-social/indigo/tree/main/cmd/tap&quot; target=&quot;_blank&quot;&gt;Tap&lt;/a&gt; seemed to be the default service you can host to start indexing content on atproto. It gives you the ability to only index a specific record (in our case, &lt;code&gt;site.standard.document&lt;/code&gt;), and it can even backfill to a specific cursor. Spinning it up is pretty straight forward, so in no time at all I was starting to fill a database with events that pointed to &lt;code&gt;site.standard.document&lt;/code&gt; record.&lt;/p&gt;
&lt;p&gt;After starting to gather in some records, I thought it was worth seeing how bad the rendering might be client side to start before dedicating to a more complicated setup. Sure enough, putting an API layer on top of Tap and then doing the other multitude of API requests on top of that to fetch all the information we mentioned earlier, was just way too slow. It wouldn’t serve the purpose of &lt;a href=&quot;https://docs.surf&quot; target=&quot;_blank&quot;&gt;docs.surf&lt;/a&gt;: rendering a feed of blog posts from the atmosphere. Back to the drawing board.&lt;/p&gt;
&lt;h2&gt;Tap + Cloudflare&lt;/h2&gt;
&lt;p&gt;At first I thought I could build a service around Tap and a self hosted server that could help with making the extra API calls, but it ended up being a bit more complicated than expected. One piece of that complexity was the rate that documents started coming in. Since Tap will backfill posts, it will run at quite a rapid pace and start filling the database quickly. If you want to try to make those additional requests necessary to build the necessary objects and verification, you’re likely going to run into bottleneck issues.&lt;/p&gt;
&lt;p&gt;Another issue I found was during my development of Sequoia. If you want to implement Standard.site into your static blog, you have to publish the document records for the blog post first so you can get the AT URI, and then deploy the blog with the appropriate &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags for verification. That means there is likely going to be a slight delay between the record creation on the PDS and when the site is built and deployed with that information, so if you try to verify a document right after it was published, you’ll get a false negative.&lt;/p&gt;
&lt;p&gt;From previous experience I knew that Cloudflare had the perfect solutions for both of these problems, particularly &lt;a href=&quot;https://developers.cloudflare.com/queues/&quot; target=&quot;_blank&quot;&gt;queues&lt;/a&gt;. Thankfully Tap has a nice webhook solution built into the service that lets you send a payload when a valid event is received. In no time at all I had the following architecture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tap running on Railway sends webhook to Cloudflare worker&lt;/li&gt;
&lt;li&gt;Cloudflare worker sends payload to queue&lt;/li&gt;
&lt;li&gt;Queue handles the multiple requests to resolve the records and verification&lt;/li&gt;
&lt;li&gt;Queue stores data in D1 database&lt;/li&gt;
&lt;li&gt;Cron job runs to re-check records that were initially marked as not verified&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/tap-and-cloudflare.svg&quot; alt=&quot;Architecture diagram showing the data flow for a Bluesky content indexing system. The diagram illustrates how new record webhook events flow from Tap (represented by an octagon) through Railway&apos;s hosting platform to a Worker service. The Worker processes batches of documents and sends them to both a queue (represented by an oval) and a database (DB, shown as an octagon). The indexed documents are then made available through a GET /feed API endpoint to a Docs.surf application. Additionally, there&apos;s a Firehose service (shown as a circle) that connects via WebSocket (wss) to provide real-time data streams. The system also includes PDS (Personal Data Server) and Site/Blog components for record storage and verification processes.&quot; loading=&quot;lazy&quot; /&gt; &lt;span&gt;       &lt;/span&gt; &lt;/div&gt;  &lt;div&gt; &lt;div&gt; + −    × &lt;/div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/tap-and-cloudflare.svg&quot; alt=&quot;Architecture diagram showing the data flow for a Bluesky content indexing system. The diagram illustrates how new record webhook events flow from Tap (represented by an octagon) through Railway&apos;s hosting platform to a Worker service. The Worker processes batches of documents and sends them to both a queue (represented by an oval) and a database (DB, shown as an octagon). The indexed documents are then made available through a GET /feed API endpoint to a Docs.surf application. Additionally, there&apos;s a Firehose service (shown as a circle) that connects via WebSocket (wss) to provide real-time data streams. The system also includes PDS (Personal Data Server) and Site/Blog components for record storage and verification processes.&quot; /&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;  
&lt;p&gt;Overall this flow worked pretty well! &lt;a href=&quot;https://docs.surf&quot; target=&quot;_blank&quot;&gt;Docs.surf&lt;/a&gt; was born and I was able to build a front-end that could make API calls to the worker which would query the database for complete Standard.site documents. That was until I started blowing through egress limits on Railway when there was a sudden uptick in Standard.site records being created. There was just more and more data being sent out from Railway, and while the cost was manageable, I knew it wouldn’t scale at the rate at which records were being created.&lt;/p&gt;
&lt;p&gt;This led me to move my Tap instance to my home server, a humble little BeeLink SER8. For a while this also seemed to work well and I didn’t think much of it for another week. Then my family started complaining about WiFi speeds, and I too started noticing some issues. I checked my little home server and was astonished by the amount of incoming bandwidth it was consuming. What I didn’t know at the time is that Tap is listening to every single event from the firehose, and only indexing/sending webhook for the target collection. Turns out that my ISP was starting to throttle my speeds because the usage was just so high. I soon switched back to a Railway tap instance, and for a month or so got caught up in other projects and my day job.&lt;/p&gt;
&lt;h2&gt;Jetstream + Cloudflare&lt;/h2&gt;
&lt;p&gt;Last week I found out that my Railway Tap instance was starting to burn through money again as a new surge of Standard.site document records were being created. I made a &lt;a href=&quot;https://bsky.app/profile/stevedylan.dev/post/3mizfotl3xk2j&quot; target=&quot;_blank&quot;&gt;post on Bluesky&lt;/a&gt; saying it might be time to shut down my little app. It was just a little hobby project, and several other people with far more talent had built Standard.site exploration tools. However I got some great suggestions and feedback from several people, and one of those was to use &lt;a href=&quot;https://docs.bsky.app/blog/jetstream&quot; target=&quot;_blank&quot;&gt;Jetstream&lt;/a&gt;. Similar to Tap, Jetstream listens to events from the firehose and can subscribe to a specific set of record collections. There are some key differences though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No included database to store this information&lt;/li&gt;
&lt;li&gt;No backfilling&lt;/li&gt;
&lt;li&gt;Includes the full record rather than a pointer to the record&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since I already had a full queue flow with a database, it didn’t feel necessary to have that Tap database in the way. I could just subscribe to the Jetstream, send the records to the queue, then process the documents. There was the realization that Docs.surf only shows the latest 100 posts, so there’s no need to index the entire history of Standard.site records. It was also pointed out that I could subscribe to Jetstream through a &lt;a href=&quot;https://developers.cloudflare.com/durable-objects/&quot; target=&quot;_blank&quot;&gt;Cloudflare Durable Object&lt;/a&gt;, therefore keeping all traffic within Cloudflare and avoid ingress or egress fees.&lt;/p&gt;
&lt;div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/jetstream-and-cloudflare.svg&quot; alt=&quot;Architecture diagram showing data flow between Jetstream, Cloudflare, and various components. The diagram illustrates a Jetstream queue system connected via WebSocket (wss) to a Duable Object, which processes batch documents and records. The flow continues through a Worker that handles database reads and writes, connects to a database (DB), and serves a Docs.surf documentation site. The system processes new record events, manages document verification and indexing, and provides GET /feed endpoints. Additional components shown include a PDS (Personal Data Server) and Site/Blog integration points.&quot; loading=&quot;lazy&quot; /&gt; &lt;span&gt;       &lt;/span&gt; &lt;/div&gt;  &lt;div&gt; &lt;div&gt; + −    × &lt;/div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/jetstream-and-cloudflare.svg&quot; alt=&quot;Architecture diagram showing data flow between Jetstream, Cloudflare, and various components. The diagram illustrates a Jetstream queue system connected via WebSocket (wss) to a Duable Object, which processes batch documents and records. The flow continues through a Worker that handles database reads and writes, connects to a database (DB), and serves a Docs.surf documentation site. The system processes new record events, manages document verification and indexing, and provides GET /feed endpoints. Additional components shown include a PDS (Personal Data Server) and Site/Blog integration points.&quot; /&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;  
&lt;p&gt;The results of the refactor were amazing. By cutting out the external requests from Tap, we were able to bring request volume down dramatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/cloudflare-requests.yjt5Vk8d_Z2r1pRX.webp&quot; alt=&quot;Bar chart showing web requests over time from April 7-12. The chart displays three data series: d700a5c0 (1.63k requests, purple), e5fa4c32 (1.13M requests, orange), and f66fa1e6 (313 requests, blue). The y-axis shows request counts from 0 to 105k, while the x-axis shows time in EDT. Orange bars dominate the chart, reaching peaks around 90k requests on April 7-8, with most activity concentrated between April 7 08:00 and April 9 02:00. After April 9, the chart shows minimal activity with small purple and blue indicators near the baseline.&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;978&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now Docs.surf runs on a single Cloudflare account that only costs $5 a month. It was so refreshing to find a solution that fit my particular use case with the help of the atproto community.&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;One thing I do want to make clear is that this setup will probably not work for everyone; I had a very specific goal in mind that only requires a partial index. However I hope it does shed some light on the tools out there and the challenges you may face with them. There are several other tools that I have not had a chance to try yet, including &lt;a href=&quot;https://tangled.org/slices.network/quickslice&quot; target=&quot;_blank&quot;&gt;quickslice&lt;/a&gt; which uses Jetstream to build a GraphQL API. I’m also sure there are plenty of people out there smarter than me who have ideas on how this could be streamlined. If that is you, please do &lt;a href=&quot;mailto:contact@stevedylan.dev?subject=Re:%20Indexing%20Standard.site&quot;&gt;let me know&lt;/a&gt; so I can update this post! At the very least I hope this post piques your interest into &lt;a href=&quot;https://atproto.com&quot; target=&quot;_blank&quot;&gt;atproto&lt;/a&gt; and how it can fix a lot of the problems created by closed platforms. We have a long way to go, but we have a fantastic community that is doing the hard work and making it happen.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All the code mentioned is open sourced and is available on &lt;a href=&quot;https://tangled.org/stevedylan.dev/docs.surf&quot; target=&quot;_blank&quot;&gt;tangled&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Alcove: An RSS Reader for the Open Web</title><link>https://stevedylan.dev/posts/introducing-alcove/</link><guid isPermaLink="true">https://stevedylan.dev/posts/introducing-alcove/</guid><description>Pushing forward the consumption of content without the invasion of privacy</description><pubDate>Sun, 23 Nov 2025 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/alcove.jpg&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Most of us take RSS for granted. It’s old, outdated, and perhaps “useless” to many. I would assume a large number of Gen Z doesn’t even know what it is. Despite what you may think of RSS, it represents a crucial belief in the internet: openness. With an RSS reader you can subscribe to feeds from multiple sources, whether its a blog, a YouTube channel, or even a social feed, all without being present on those platforms. “Being present” is a key set of words: most of today’s platforms drive for your presence, attention, and time. The most time you spend on their platform the better, so they decide what is in your feed in order to keep you dialed in. RSS breaks us free from this need.&lt;/p&gt;
&lt;p&gt;Outside of breaking free of platforms, RSS also provides a way out of censorship and boasts freedom of speech. If someone is writing a blog, then someone else can read it and subscribe to it, with few external forces preventing it. It’s not a perfect system, as the publication side can face domain takedowns or even hosting. On the consumption side there is also the risk of what you read being used against you. This problem was &lt;a href=&quot;https://inessential.com/2025/10/04/why-netnewswire-is-not-web-app.html&quot; target=&quot;_blank&quot;&gt;originally raised&lt;/a&gt; by the creator of NetNewsWire, an RSS reader for the Apple ecosystem. It’s one of the main reasons NetNewsWire won’t be a web app:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could I encrypt the subscription lists on the server? Yes, but the server would have to be able to decrypt it, or else the app couldn’t possibly work. Which means I could decrypt the lists and turn them over.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a classic case of “won’t be evil” vs “can’t be evil.” With a setup where the subscription lists are encrypted server side then it’s a promise that the owner won’t do the wrong thing, even though there may not be that choice if the law demands it. The better route is a tech stack that enables privacy and makes it impossible to hand over any kind of data.&lt;/p&gt;
&lt;p&gt;I’m pleased to say that this tech stack does exist, and it was used to build &lt;a href=&quot;https://alcove.tools&quot; target=&quot;_blank&quot;&gt;Alcove&lt;/a&gt;: an RSS reader for an open web.&lt;/p&gt;
&lt;h2&gt;How Does it Work?&lt;/h2&gt;
&lt;p&gt;Alcove is primarily powered by a single yet powerful library: &lt;a href=&quot;https://evolu.dev&quot; target=&quot;_blank&quot;&gt;Evolu&lt;/a&gt;. With this library developers can build apps that run local fist using SQLite instances that are encrypted client side and then synced through relay servers. Anytime the app is opened a new identity keypair is generated to start encrypting data. These keypairs can be used to backup and restore instances on other devices, so I can easily start adding feeds on my computer and then access them on my phone later, all end-to-end encrypted. Evolu has done the hard work of solving CRDT issues that may arise as well as &lt;a href=&quot;https://www.evolu.dev/blog/scaling-local-first-software#evolu-protocol&quot; target=&quot;_blank&quot;&gt;complex solutions&lt;/a&gt; for scaling large applications. Huge shoutout to the team building it as Alcove would not exist without it!&lt;/p&gt;
&lt;p&gt;Relays are the server side of the stack that stores and syncs encrypted data from the clients. Even if I wanted to know what was in those databases, I can’t. Evolu makes running relays incredibly simple too, so much so that I have two running for Alcove to help keep redundancy. In theory anyone could run an instance and support the network if the app subscribes to it, which is incredibly powerful.&lt;/p&gt;
&lt;p&gt;Outside of Evolu, Alcove is built on a pretty standard Vite + React app with shadcn/ui components, using a theme I came up with a while ago that was recently added to &lt;a href=&quot;https://tweakcn.com/editor/theme?theme=darkmatter&quot; target=&quot;_blank&quot;&gt;tweakcn&lt;/a&gt; (thanks Sahaj!).&lt;/p&gt;
&lt;p&gt;Alcove is also MIT open sourced&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/stevedylandev/alcove&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mirror: &lt;code&gt;git clone https://git.stevedylan.dev/alcove.git&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Using Alcove is overall pretty simple. Visit &lt;a href=&quot;https://alcove.tools&quot; target=&quot;_blank&quot;&gt;alcove.tools&lt;/a&gt; and start by a feed you want to subscribe to, or import an OPML file. Once you do Alcove will start fetching the data and populating the local database.&lt;/p&gt;

&lt;p&gt;Once you have feeds you can start reading them and marking them as read, unread, or categorize them for better organization.&lt;/p&gt;

&lt;p&gt;Something important to keep in mind is that all of this is happening locally; if you cleared your browser cache all of it would go away. That’s why is critical to backup your account early on. Just click on the settings in the bottom left and then click “Backup.” This will show a word phrase representing your cryptographic keypair; keep this somewhere safe like a password manager.&lt;/p&gt;

&lt;p&gt;Now if you wanted to start using your Alcove instance on another device, simply use the “Restore from Backup” option on the home screen and paste in your passphrase. This will authorize your new device, download the data from the relay, and decrypt it with your keys.&lt;/p&gt;

&lt;h2&gt;Censorship Resistance&lt;/h2&gt;
&lt;p&gt;While there is only so much we can do in terms of decentralized and censorship resistance in web hosting, Alcove is taking one step by using &lt;a href=&quot;https://ens.domains&quot; target=&quot;_blank&quot;&gt;ENS&lt;/a&gt; and &lt;a href=&quot;https://ipfs.tech&quot; target=&quot;_blank&quot;&gt;IPFS&lt;/a&gt;. Every published release will have an IPFS CID that can be accessed through a gateway or directly with an IPFS node.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# CID: bafybeiejwvoe4bszg5rgppl4ww2lrvduglc2n55od53t3is7buh5fln5fu&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Gateway: https://dweb.link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://bafybeiejwvoe4bszg5rgppl4ww2lrvduglc2n55od53t3is7buh5fln5fu.ipfs.dweb.link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Local IPFS node&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;http://bafybeiejwvoe4bszg5rgppl4ww2lrvduglc2n55od53t3is7buh5fln5fu.ipfs.localhost:8080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can get this CID hash from the GitHub releases page, or alternatively you can get it from the &lt;a href=&quot;https://app.ens.domains/alcovetools.eth&quot; target=&quot;_blank&quot;&gt;alcovetools.eth ENS&lt;/a&gt;. The &lt;code&gt;contenthash&lt;/code&gt; field will be updated each time there is a release thanks to &lt;a href=&quot;https://omnipin.eth.limo/&quot; target=&quot;_blank&quot;&gt;Omnipin&lt;/a&gt;. You can also simply access it through a gateway as well with &lt;a href=&quot;https://alcovetools.eth.limo&quot; target=&quot;_blank&quot;&gt;alcovetools.eth.limo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;That’s Alcove. If you aren’t already using RSS feeds or blogs then I highly recommend you start today. As long as someone is writing, someone can read. These are core principles to a society that is truly free, compared to one where you can’t write freely at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For some reason the telescreen in the living-room was in an unusual position. Instead of being placed, as was normal, in the end wall, where it could command the whole room, it was in the longer wall, opposite the window. To one side of it there was a shallow alcove in which Winston was now sitting, and which, when the flats were built, had probably been intended to hold bookshelves. By sitting in the alcove, and keeping well back, Winston was able to remain outside the range of the telescreen, so far as sight went. He could be heard, of course, but so long as he stayed in his present position he could not be seen. It was partly the unusual geography of the room that had suggested to him the thing that he was now about to do. - George Orwell, 1984&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Introducing Sequoia: Publishing for the Open Web</title><link>https://stevedylan.dev/posts/introducing-sequoia/</link><guid isPermaLink="true">https://stevedylan.dev/posts/introducing-sequoia/</guid><description>A new CLI tool for publishing existing blogs to the AT Protocol</description><pubDate>Fri, 30 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/sequoia-hero.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Today I’m excited to release a new tool for the &lt;a href=&quot;https://atproto.com&quot; target=&quot;_blank&quot;&gt;AT Protocol&lt;/a&gt;: Sequoia. This is a CLI tool that can take your existing self-hosted blog and publish it to the ATmosphere using &lt;a href=&quot;https://standard.site&quot; target=&quot;_blank&quot;&gt;Standard.site&lt;/a&gt; lexicons.&lt;/p&gt;
&lt;p&gt;If you haven’t explored ATProto you can find a primer &lt;a href=&quot;https://stevedylan.dev/posts/atproto-starter/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;, but in short, it’s a new way to publish content to the web that puts ownership and control back in the hands of users. Blogs in some ways have already been doing this, but they’ve been missing a key piece: distribution. One of the unique features of ATProto is &lt;a&gt;lexicons&lt;/a&gt;, which are schemas that apps build to create folders of content on a user’s personal data server. The domain verified nature lets them be indexed and aggregated with ease. Outside of apps, lexicons can be extended by community members to build a common standard. That’s exactly how &lt;a href=&quot;https://standard.site&quot; target=&quot;_blank&quot;&gt;Standard.site&lt;/a&gt; was brought about, pushing a new way for standardizing publications and documents on ATProto.&lt;/p&gt;
&lt;p&gt;The founders and platforms behind the standard, &lt;a href=&quot;https://leaflet.pub&quot; target=&quot;_blank&quot;&gt;leaflet.pub&lt;/a&gt;, &lt;a href=&quot;https://pckt.blog&quot; target=&quot;_blank&quot;&gt;pckt.blog&lt;/a&gt;, and &lt;a href=&quot;https://offprint.app&quot; target=&quot;_blank&quot;&gt;offprint.app&lt;/a&gt;, all serve to make creating and sharing blogs easy. If you are not a technical person and don’t have a blog already, I would highly recommend checking all of them out! However, for those of us who already have blogs, there was a need for a tool that could make it easy to publish existing and new content with this new standard. Thus Sequoia was born.&lt;/p&gt;
&lt;p&gt;Sequoia is a relatively simple CLI that can do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authenticate with your ATProto handle&lt;/li&gt;
&lt;li&gt;Configure your blog through an interactive setup process&lt;/li&gt;
&lt;li&gt;Create publication and document records on your PDS&lt;/li&gt;
&lt;li&gt;Add necessary verification pieces to your site&lt;/li&gt;
&lt;li&gt;Sync with existing records on your PDS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s designed to be run inside your existing repo, build a one-time config, and then be part of your regular workflow by publishing content or updating existing content, all following the Standard.site lexicons. The best part? It’s designed to be fully interoperable. Doesn’t matter if you’re using Astro, 11ty, Hugo, Svelte, Next, Gatsby, Zola, you name it. If it’s a static blog with markdown, Sequoia will work (and if for some reason it doesn’t, &lt;a href=&quot;https://tangled.org/stevedylan.dev/sequoia/issues/new&quot; target=&quot;_blank&quot;&gt;open an issue!&lt;/a&gt;). Here’s a quick demo of Sequoia in action:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;ATProto has proven to be one of the more exciting pieces of technology that has surfaced in the past few years, and it gives some of us hope for a web that is open once ore. No more walled gardens, full control of our data, and connected through lexicons.&lt;/p&gt;
&lt;p&gt;Install Sequoia today and check out the &lt;a href=&quot;https://sequoia.pub/quickstart&quot; target=&quot;_blank&quot;&gt;quickstart guide&lt;/a&gt; to publish your content into the ATmosphere 🌳&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;bun&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; -g&lt;/span&gt;&lt;span&gt; sequoia-cli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Learning Rust With AI</title><link>https://stevedylan.dev/posts/learning-rust-with-ai/</link><guid isPermaLink="true">https://stevedylan.dev/posts/learning-rust-with-ai/</guid><description>A glimpse into a better way of learning to code, where you put the LLM in the backseat while you drive</description><pubDate>Fri, 11 Jul 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/ai-mentor.B4Shyr6b_ZVcN7L.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;3860&quot; height=&quot;2510&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I think most developers out there would agree that we’re in a bit of an AI hype bubble, yet one piece of AI tech that has recently hit the market which developers can’t stop talking about might be different. Of course I’m talking about “agents” like Claude Code or my personal favorite &lt;a href=&quot;https://opencode.ai&quot; target=&quot;_blank&quot;&gt;Opencode&lt;/a&gt;. With a few prompts you can have an AI create a plan to implement a feature or fix a bug, and it will just do it. They generally have deep system integration with your terminal and LSPs to have a great understanding of how to build something. However in my opinion these tools, like any other AI tool, are a double edged sword.&lt;/p&gt;
&lt;h2&gt;Using AI to Learn&lt;/h2&gt;
&lt;p&gt;The truth is if you don’t know what’s going on in the code, you’re going to have a hard time when the AI fails or falls into a bad context loop. Due to this limitation, your AI tools will only take you as far as your programming knowledge. While some CEOs and VCs would argue that programmers aren’t needed anymore, I would counter that it’s more important than ever to learn how to code and program. Agents are going to be 10x more effective in the hands of someone who knows how a language works, how to break down and solve big problems, and just knows fundamental programming principles.&lt;/p&gt;
&lt;p&gt;What if you’re not one of those people yet? I personally found myself in this position where I wanted to learn Rust more, but I knew that if I leaned on AI too much I wouldn’t actually learn anything. It’s far too tempting to let the agent write everything for you and not know exactly what’s happening. On the flip side, AI can still serve as a mentor, but with some caveats. In my expedition to learn Rust I decided that outside of reading the Rust book, I would build a CLI with an AI mentor. The key here is that I would be writing all the code, as well as asking questions along the way when something didn’t make sense. The end result was a great experience, and I wanted to share a few tips in case you want to take this route yourself!&lt;/p&gt;
&lt;h3&gt;Use &lt;code&gt;agents.md&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This might be &lt;code&gt;claude.md&lt;/code&gt; or something else depending on your CLI, but it’s essentially a markdown file with specific instructions for your AI agent. In the case of learning a new language I make it crystal clear of my objectives, and that the agent should never write any code for me. Here’s the &lt;code&gt;agents.md&lt;/code&gt; file from my CLI project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Learning Approach&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;I want to use AI as a learning tool only. Please follow these guidelines:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;1.&lt;/span&gt;&lt;span&gt; **DO NOT write code for me**&lt;/span&gt;&lt;span&gt; - I want to write all the code myself&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2.&lt;/span&gt;&lt;span&gt; **DO provide explanations**&lt;/span&gt;&lt;span&gt; - Help me understand Rust concepts and patterns&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3.&lt;/span&gt;&lt;span&gt; **DO suggest approaches**&lt;/span&gt;&lt;span&gt; - Give me high-level guidance on how to implement features&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;4.&lt;/span&gt;&lt;span&gt; **DO review my code**&lt;/span&gt;&lt;span&gt; - Provide feedback on code I&apos;ve written&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;5.&lt;/span&gt;&lt;span&gt; **DO answer questions**&lt;/span&gt;&lt;span&gt; - Explain concepts when I ask about them&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;6.&lt;/span&gt;&lt;span&gt; **DO provide examples**&lt;/span&gt;&lt;span&gt; - When relevant, show small examples to illustrate concepts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;The goal is for me to learn Rust by doing the implementation work myself, with AI serving as a guide and mentor rather than doing the work for me.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you happen to use something like Opencode you can use the &lt;code&gt;/init&lt;/code&gt; command to create one of these!&lt;/p&gt;
&lt;h3&gt;Write Every Line&lt;/h3&gt;
&lt;p&gt;As you might have guessed by now, the key thing I wanted to achieve was writing every line of code. I knew from past experience that even if I didn’t fully understand what I was writing, the action of writing it made me slow down and think of each piece of the code. I could guess or ask questions about what it did. By repeating patterns over and over again I started to feel those patterns imprint my memory. It’s like riding a bike: you just gotta pedal. At least for me, this became quite therapeutic! Like writing a letter or contemplating in a journal, slowly writing each line of code helps your brain process and think through what flows.&lt;/p&gt;
&lt;h3&gt;Keep Short Sessions&lt;/h3&gt;
&lt;p&gt;One of the big problems with AI tools these days is context limits. If your coding session starts to get too long, your AI is going to try to comprehend everything that is happening. Eventually it starts to lose track of what’s happening and where it is, and that’s when the hallucinating happens. Best thing you can do is break each task into a short session and start a new one when there’s a new goal or task. Where you draw that line is honestly a skill you have to grow as a programmer, so it’s good practice in cases like this.&lt;/p&gt;
&lt;h3&gt;Understand Basic Tooling&lt;/h3&gt;
&lt;p&gt;One of the best things you can do for yourself is learning some basic tooling like LSP and how to debug. LSP or Language Server Protocol is how you get those helpful hints when coding that say you’re doing something wrong. I had multiple instances while learning Rust through building this CLI where the AI was stuck and couldn’t get me out of a bug, but thankfully I knew how to read the LSP, the error checking through cargo, and I could fix the issue myself. Don’t be afraid to check the docs and the LSP, because sometimes that will be faster than attempting to do it a fifth time through AI!&lt;/p&gt;
&lt;h2&gt;The End Product&lt;/h2&gt;
&lt;p&gt;When all was said and done, I accomplished my goal of building a fun little CLI called &lt;a href=&quot;https://github.com/stevedylandev/walletfetch&quot; target=&quot;_blank&quot;&gt;walletfetch&lt;/a&gt; that works like Neofetch but for EVM based wallets!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/walletfetch.CNATRPbu_1LDw8F.webp&quot; alt=&quot;walletfetch&quot; loading=&quot;lazy&quot; width=&quot;1790&quot; height=&quot;1352&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Taking the time to learn this way was super helpful, and I will definitely be doing it more often. It’s certainly not perfect, and it’s no replacement for core materials like the Rust book, but in my opinion it’s a great way to build projects and learn something new. If you’re an aspiring developer, I cannot stress this enough: be competent. Always be curious, ask questions, figure out “why,” and look to solve problems you really care about. I saw a post on X recently that went something like “quitting software development now due to AI tools is like quitting woodworking because the table saw was invented.” It’s a tool like anything else, but you’ve got to know your fundamentals, and you’ve got to know what to build. Be a woodworker. Be a craftsman.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s more fun to be competent&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Leaving Neovim for Zed</title><link>https://stevedylan.dev/posts/leaving-neovim-for-zed/</link><guid isPermaLink="true">https://stevedylan.dev/posts/leaving-neovim-for-zed/</guid><description>A journey through text editors and how I landed on Zed after years of Neovim</description><pubDate>Fri, 16 Aug 2024 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/leaving-neovim-for-zed.O8ct96Tt_4vUUx.webp&quot; alt=&quot;header image&quot; loading=&quot;lazy&quot; width=&quot;3760&quot; height=&quot;2410&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I think every developer has their own text editor journey and how they landed on the tool they use today. Perhaps I’m a geek but I love those stories. I have a great appreciation for developer tools and the work that goes into them. This post is for the other geeks out there that also care, and I hope my journey and perspective can prompt others to experiment and try developer tools outside their comfort zones. You never know what you might land on and how much you might enjoy it!&lt;/p&gt;
&lt;p&gt;My text editor journey starts with a faint memory of Atom. I was learning the true fundamentals of HTML CSS and Javascript, and I honestly can’t tell you how I landed on Atom as a text editor. I do remember using it for a few weeks, and I kept seeing other people use or mention VSCode, so of course I gave it a shot and used it for a while. However this didn’t last long. At the time my wife needed my laptop for her photo editing job, so I used my brother’s old Macbook that was holding on for dear life. VSCode’s Electron build started taking a noticeable toll on performance, and by chance I also discovered Vim around the same time.&lt;/p&gt;
&lt;p&gt;Immediately I was mesmerized by the speed and powers demonstrated by The Primeagen’s early videos. I was already a keyboard maximalist from &lt;a href=&quot;/posts/why-i-learned-vim&quot;&gt;previous jobs&lt;/a&gt; where I learned speed = productivity, so it was a no brainer that I had to learn it. Started with the basic motions and Vim tutor, and I had the advantage that I was just learning programming on the side instead of doing it full time. Within a few weeks I was in Vim consistently, writing and learning to code. The tweaking of my Vim RC eventually led to discovering Neovim thanks to &lt;a href=&quot;https://www.youtube.com/@chrisatmachine&quot; target=&quot;_blank&quot;&gt;chris@machine&lt;/a&gt; and his early videos.&lt;/p&gt;
&lt;p&gt;For the next several years I stuck with Neovim and I loved it, and I owe a mass amount of my productivity to it. There were countless hours spent configuring it like many of us do. I eventually got to a point where I didn’t adjust my config much, but that soon didn’t matter.&lt;/p&gt;
&lt;h2&gt;What Changed&lt;/h2&gt;
&lt;p&gt;Every now and then I would update a plugin in Neovim and everything would break, and I would have to spend time fixing it instead of getting work done. This resulted in slimming down my config more and more, but there was still so much that went into making all the basics work. I stuck with it because it was still better than using VSCode, which I did try for a two week sprint to see if it could be any better. It was also key to a &lt;a href=&quot;/posts/a-terminal-based-workflow&quot;&gt;terminal based workflow&lt;/a&gt; that other editors couldn’t really match.&lt;/p&gt;
&lt;p&gt;The sentiment started to shift again not too long ago as I started working in some really large code bases, and boy Neovim was struggling. I would have random hang ups, frozen screens, stuff that just drove me nuts when productivity was king. I tried switching to other terminal emulators too such as Alacritty and Wezterm but it didn’t help much.&lt;/p&gt;
&lt;p&gt;This is when Zed came back into my sights. I heard about it months before and even gave it a shot back then, but didn’t stick with it because it wasn’t a terminal workflow. However it boasted as being fast, and I decided it was worth another shot. Two months later and I’ve been daily driving it since. I wasn’t sure if it would really hold up, but I can say now it has been an amazing experience and I don’t see myself going back.&lt;/p&gt;
&lt;h2&gt;My Experience with Zed&lt;/h2&gt;
&lt;p&gt;In order to understand why I eventually settled on Zed we’ll look at my general experience with it so far and how I made it work for me.&lt;/p&gt;
&lt;h3&gt;It Just Works&lt;/h3&gt;
&lt;p&gt;One of the biggest things that has stood out to me using Zed so far is how “everything just works.” There are so many features of an IDE or text editor that people take for granted until they have to set it up themselves in something lower level like Neovim. LSP (language server protocol) is certainly one of them. If you’re not familiar it’s the hints or errors that show up while you’re writing up your code, giving you deep insights to your repo on a language level. When you setup LSP in Neovim it’s a lot of work, and sometimes it can be a bit harder to figure out why it might be bugging out. However it does give you way more control and the option to do a lot of customization. With Zed LSP just works. There are configurations you can make to edit some things, but as a whole it just zips out of the box. There are already keybindings for things like “show definition”, “go to definition”, or even code actions. The only downside is outside of an extension you can’t use your own LSP that’s installed on your machine, but there’s always a pretty large language support that I haven’t had this issue yet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/lsp-demo.CMh2D_3a_Z2iIcRf.webp&quot; alt=&quot;LSP demo&quot; loading=&quot;lazy&quot; width=&quot;1246&quot; height=&quot;796&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Another piece that’s related to LSP is completions. This is when you’re typing some code and get suggestions for auto completions that quickly fill the rest of the code out. LSPs usually have great auto-completion because they’re aware of the patterns used in that language. Just to be clear we’re not talking about Copilot yet, this is just completions for snippets and LSP. Once again with Zed it just works out of the box, unlike Neovim which ends up requiring several plugins to make it work right.&lt;/p&gt;

&lt;p&gt;Finally there’s Git integrations. What normally required multiple plugins in Neovim is again ready out of the box with Zed, including feature like toggling Git Blame, viewing diffs, and gutter symbols showing the status of edited lines.&lt;/p&gt;

&lt;p&gt;If I had to make a crude comparison, it’s similar to Linux and Apple. Linux will give you far more control over every piece of your software and hardware at the cost of spending time to configure it. Apple will give you less control but it will likely run smoother.&lt;/p&gt;
&lt;h3&gt;Speed&lt;/h3&gt;
&lt;p&gt;Of course one of the biggest reasons I gave Zed a shot was speed, and boy it was worth it. It is noticeably snappy, handles larger codebases like a champ, and I haven’t experienced any lag so far. Normally I wouldn’t recommend building most things in Rust, but this app kinda makes me reconsider. There are so many developer tools written in Rust and perhaps that’s one of its biggest boasts. The team at Zed have really outdone themselves as this app; its truly a work of art.&lt;/p&gt;
&lt;p&gt;Could I possibly make neovim faster by adjusting my config? Perhaps, but in the end it’s more time down the drain that I could have used writing code and being productive. For productivity nerds like me I believe there is a delicate scale: how much time I spend automating a task vs how much time the task would have taken without automation. In this case the benefits aren’t worth the cost when there’s a tool like Zed that will do just fine.&lt;/p&gt;
&lt;h3&gt;Vim Mode&lt;/h3&gt;
&lt;p&gt;While in the middle of my time with Neovim I came across other frustrated Neovim users that found themselves spending too much time fixing issues in Neovim to the point they switched to VSCode. Of course I had a few moments like this myself and I wondered if I had missed something. At this point I had a much faster computer, so I figured I would give it another shot. Of course I downloaded the Vim plugin as the Vim keybindings are worth learning no matter what text editor you use, and it was incredibly disappointing. Nothing had ever felt so buggy and jagged, and it remained one of the big reasons I couldn’t stick with VSCode. I was back in Neovim within a week or two.&lt;/p&gt;
&lt;p&gt;When I was considering Zed again I read &lt;a href=&quot;https://registerspill.thorstenball.com/p/from-vim-to-zed&quot; target=&quot;_blank&quot;&gt;a blog post&lt;/a&gt; about the custom Vim mode built into Zed. This was not a third party plugin; this was a labor of love from the developers building Zed. They’ve made it clear that they don’t plan to port absolutely everything to Zed, but they have done a fantastic job supporting the important stuff that makes the editor an S-tier experience.&lt;/p&gt;
&lt;p&gt;I’ll likely get into this further into the post, but the way you can structure keybindings for Vim mode in Zed is fantastic. The structure allows for your typical VScode style config, but with the ability to scope a keybinding to a Vim mode is such a huge win for Neovim users. For instance, I can cheat my way into using a leader key when in normal mode and get things like &lt;code&gt;space d&lt;/code&gt; to see diagnostics, or &lt;code&gt;space t&lt;/code&gt; to open a full window terminal. It’s a pattern many Vim users will appreciate and I wish there was more docs for it as I’ve had to figure some of it out myself.&lt;/p&gt;
&lt;p&gt;Beyond keybindings all the other stuff you would expect in Vim motions are here, with of course a few obscure ones slowly being added release by release. Some of them take a unique approach by using some of Zed’s built in features as a replacement for what Vim would normally use, like search and replace for example. Generally you would have to do a Vim command to search for a word and replace it, and those commands still do what you expect, but it brings up Zed’s Multibuffer view when doing a project wide search. You get Vim mode but it’s still Zed and it’s unique feature set.&lt;/p&gt;
&lt;h3&gt;AI Stuff&lt;/h3&gt;
&lt;p&gt;When it comes to AI features I think Zed provides some great built in tools. I will disclose that I’m not a big user of AI within the text editor (a topic for another blog post), so there might be things you’re looking for compared to Cursor that I can’t speak to. With that said it does have Copilot built in which is probably what most people want to know.&lt;/p&gt;
&lt;p&gt;Zed also features an assistant panel where you can access several AI models via API, including OpenAI, Ollama, and Anthropic. Just requires a few lines of config to get started.&lt;/p&gt;

&lt;p&gt;One feature that I think is particularly nice is the inline assistant, where you can select some lines of code and use &lt;code&gt;ctrl-enter&lt;/code&gt; to trigger a request to be made to your code via the AI assistance configuration mentioned previously. If you like the results then you can confirm and keep coding.&lt;/p&gt;

&lt;h3&gt;Zed ≠ Neovim&lt;/h3&gt;
&lt;p&gt;You can probably gather so far that I’m a huge Zed fan, however I will say it’s not a 1/1 replacement to Neovim. What makes Neovim so special is that it’s native to the terminal. Whenever I need to edit a configuration file for an app or just edit something really quickly while I’m already navigating in a terminal, nothing beats the convenience of whipping out Neovim. Opening Zed for every single file like that would get exhausting, but for longer term sessions or projects it’s perfect. If you compare to motorcycles, Neovim is my dirt bike and Zed is my cruiser.&lt;/p&gt;
&lt;h2&gt;Making Zed Work for A Neovim User&lt;/h2&gt;
&lt;p&gt;While Zed can’t truly be a drop in replacement of Neovim, there are a lot of small configurations that really help improve the experience and make it familiar to a Neovim/Vim user.&lt;/p&gt;
&lt;h3&gt;Vim Mode &amp;amp; Keybindings&lt;/h3&gt;
&lt;p&gt;First one is obvious, turn on Vim mode. Zed has a great &lt;a href=&quot;https://zed.dev/docs/vim&quot; target=&quot;_blank&quot;&gt;docs page&lt;/a&gt; with loads of extra info, some default Vim bindings, and some extra configs available to make it work for you. By far my favorite piece that I mentioned earlier is the ability to do key bindings based on Vim modes. Here’s some examples:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; VimControl &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !menu&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space b&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::ToggleGitBlame&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-k&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::Hover&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space l f&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::Format&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space d&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;diagnostics::Deploy&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space f f&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;file_finder::Toggle&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space o&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tab_switcher::Toggle&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space e&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::ToggleLeftDock&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space /&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::NewSearch&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;n&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;search::SelectNextMatch&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-n&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;search::SelectPrevMatch&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space t&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::NewCenterTerminal&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;g b&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::ToggleComments&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;+ +&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::Save&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space c&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pane::CloseActiveItem&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; vim_mode == visual &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !VimObject&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-j&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::MoveLineDown&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-k&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::MoveLineUp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of these are pretty self explanatory, but the key is that the first set is in “Normal” mode, while the next set is “Visual.” There’s also small improvements or shortcuts they provide in the docs like this set of key bindings that allows you to switch panes similar to how most people have Neovim setup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Dock || Terminal || Editor&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-h&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Left&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-l&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Right&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-k&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Up&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-j&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Down&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Something else I would recommend to anyone who is trying to migrate from Vim/Neovim to Zed is checking out the &lt;a href=&quot;https://github.com/zed-industries/zed/blob/340a1d145ed15e39a4a27afc5a189851308fb91d/assets/keymaps/vim.json#L4&quot; target=&quot;_blank&quot;&gt;default Vim keymap&lt;/a&gt;. There’s so much there that acts as a helpful reference of what’s supported and what you may want to adjust!&lt;/p&gt;
&lt;h3&gt;Reduced UI&lt;/h3&gt;
&lt;p&gt;Zed already has a pretty nice minimal UI, but I prefer something closer to my Neovim setup. Thankfully Zed offers these options, such as disabling the tab bar, scroll bar, reduced toolbar, and relative line numbers&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cursor_blink&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;relative_line_numbers&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;scrollbar&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;show&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;never&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;vertical_scroll_margin&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;tab_bar&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;show&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;toolbar&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;breadcrumbs&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;quick_actions&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/zed-settings.25tQ2oFj_1Iu2Eh.webp&quot; alt=&quot;Zed settings&quot; loading=&quot;lazy&quot; width=&quot;3544&quot; height=&quot;2328&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Plugin Replacements&lt;/h3&gt;
&lt;p&gt;Since I don’t use the tab bar I wanted something similar to Telescope to navigate between buffers or files, and thankfully there is an option for that! This key binding will show currently open all buffers, which is separate from the file finder.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; VimControl &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !menu&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;space o&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tab_switcher::Toggle&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Speaking of Telescope, one big replacement is project wide search. While Zed doesn’t have a fuzzy find feature, the project wide search is excellent. It will show all results in a multibuffer view which is pretty slick, and allows you to jump between that view and the buffer itself pretty easily.&lt;/p&gt;

&lt;p&gt;The terminal toggle is pretty similar to something like VSCode but there are some other hidden ways to get a better terminal experience. One of them is a shortcut to toggle the bottom terminal to be full screen, but even better is opening a terminal as a buffer in the main editing view.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; VimControl &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !menu&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;space t&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::NewCenterTerminal&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One of the big things I had to leave behind was Tmux and switching projects. While it isn’t a perfect replacement, Zed has a “switch projects” feature which works really well and makes it pretty easy to switch contexts. You just won’t get the exact same control and layout setup that you can get with Tmux&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Workspace&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;cmd-k&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;projects::OpenRecent&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;create_new_window&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Should You Use Zed?&lt;/h2&gt;
&lt;p&gt;If you’re still on the fence of trying Zed I would say it’s at least worth giving it a shot for a few days. In my experience so far it’s a unique and capable text editor, but ultimately I vouch for anything that makes you more productive. That might end up being VS Code or Jetbrains or hell maybe even EMacs. Do what’s best for you, but don’t be too stubborn to try something new.&lt;/p&gt;
&lt;p&gt;Edit: Thank you for all the love on this post! If you want to see my full settings and keymaps I have them linked below for your convenience.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sipp.stevedylan.dev/s/AfQ6gyKPF7&quot; target=&quot;_blank&quot;&gt;settings.json&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sipp.stevedylan.dev/s/05l9VQyh92&quot; target=&quot;_blank&quot;&gt;keymap.json&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>My Developer Journey</title><link>https://stevedylan.dev/posts/my-developer-journey/</link><guid isPermaLink="true">https://stevedylan.dev/posts/my-developer-journey/</guid><description>How I learned web development and transitioned into the tech and Web3 space</description><pubDate>Tue, 28 Feb 2023 05:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;It Started with Clickbait&lt;/h2&gt;
&lt;p&gt;It was late September in 2020, our first son was just born and I was waiting in the car while my wife took him to the hospital for a checkup (becuase of the Covid-19 restrictions at the time only one of us could go in). I had spent the majority of my life doing multiple jobs in various fields. My college degree was in liberal arts so of course it only did so much good in the professional world, so I started small by working in the footwear department at Bass Pro Shops. From there I slowly worked up the chain and eventually ran the archery department.&lt;/p&gt;
&lt;p&gt;It was a fair job for three to four years but eventually the wear of retail grew on me and I got tired of working late hours. That’s when I transitioned into banking as a teller, as I heard it was a good out from retail. After working as a teller for about a year I moved to the back office customer service position. There I worked 8.5 hours a day taking phone calls and helping customers with online banking, debit card problems, or just checking a balance. It was a pretty nice gig since I got to help people and work with some pieces of tech, and later down the road I eventually helped managed the department. That position also helped me learn how to be productive, type faster, and operate a keyboard only interface quickly.&lt;/p&gt;
&lt;p&gt;As you would expect talking to people all day every day took a toll on my mental health after four years, and that’s about when my son was born. I had about three weeks of vacation and sick time off to help my wife before going back to work, and yeah I really didn’t want to go back after taking a good solid break. I sat in that hospital parking lot, scrolling through YouTube, when I came across a video. This video to be precise:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://youtu.be/nupkQD_Mnhg&quot; target=&quot;_blank&quot;&gt;How I Learned to Code - And Got a Job in Less Than 3 Months&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;The Grind&lt;/h2&gt;
&lt;p&gt;Of course the title is clickbait and I was hooked. I didn’t learn to code in three months, but I did get started. I bought &lt;a href=&quot;https://www.amazon.com/Head-First-HTML-CSS-Standards-Based/dp/0596159900&quot; target=&quot;_blank&quot;&gt;Head First: HTML with CSS and XHTML&lt;/a&gt; off eBay for $10 and blew through it in a weekend; I just couldn’t stop consuming knowledge about web development. This wasn’t programming just yet, but the magic was there because I watched text on a screen transform into something visual. I’ve had a creative background with music and photography, and the ability to create something with lines of code was fascinating. The next book was &lt;a href=&quot;https://www.amazon.com/Head-First-Java-Brain-Learners/dp/0596004656?keywords=head+first+java&amp;amp;qid=1677605428&amp;amp;sr=8-5&quot; target=&quot;_blank&quot;&gt;Head First Java&lt;/a&gt; which did not click with me at all. I barely grasped the basic programming principles. I couldn’t understand how they connected with web sites and made things work, and that was likely due to using 5-10 year old books. I switched up and went to YouTube again and found some web development roadmap videos which gave me a rough guideline of what I needed to learn.&lt;/p&gt;
&lt;p&gt;The next year was spent grinding through some coursed by &lt;a href=&quot;https://developedbyed.com/&quot; target=&quot;_blank&quot;&gt;Ed&lt;/a&gt;, starting with basic HTML, CSS, and Javascript, and eventually React. That was a rough period, because I was still working at the bank full time. I was helping take care of a difficult newborn baby, and learning something completely new. I would wake up at 5am most days, completely exhausted yet pushing through concept after concept and project after project. After work I would come home, help around the house, and later in the evening I would keep coding. While it was a lot of work, it was totally worth it.&lt;/p&gt;
&lt;h2&gt;The First Smart Contract&lt;/h2&gt;
&lt;p&gt;After about a year I was getting to a point where I was creating projects with the goal of having a portfolio I could use for applying to jobs. That’s when I stumbled upon Web3. I can’t remember how, but I found a project on &lt;a href=&quot;https://buildspace.so&quot; target=&quot;_blank&quot;&gt;Buildspace&lt;/a&gt; that introduced me to blockchain, Ethereum, and smart contracts. I’ll never forget the feeling of deploying my first smart contract and interacting with it from a front end website. This was it; I knew from there I wanted to work in this new internet and make it better. I built countless Web3 projects, some of them included minting NFTs, which is where I stumbled upon &lt;a href=&quot;https://pinata.cloud&quot; target=&quot;_blank&quot;&gt;Pinata&lt;/a&gt;. When I started to look for jobs I saw that Pinata was hiring a community manager, and even though I was looking to be a developer, I was fond of the idea that I could use some of my other skills like support and customer service in the industry. I applied for the job, and within a month I was hired!&lt;/p&gt;
&lt;p&gt;As the community manager then and head of community now, I’ve had another year of being able to learn technical products and help people use them and understand them. I still get to write code that demonstrates what Pinata can do and snippets to help make it easier to use, which I absolutely love. Pinata took a chance on some guy who used to fetch shoes and take phone calls, and because of that I’ve been able to relocate to a better city where I can raise my family for which I am incredibly grateful. Sitting down to work each day is exciting because I know that I can learn just about anything and I can teach it to others.&lt;/p&gt;
&lt;p&gt;I’m starting this blog to document more of what I’m learning in the Web3 and tech space in hopes that others find it beneficial. If you get anything from this post, let it be these words from the beloved Ratatouille that can apply to just about anything in life:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the past, I have made no secret of my disdain for Chef Gusteau’s famous motto, ‘Anyone can cook.’ But I realize, only now do I truly understand what he meant.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Not everyone can become a great artist; but a great artist &lt;strong&gt;can&lt;/strong&gt; come from &lt;strong&gt;anywhere&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Programmers on the Verge of Extinction</title><link>https://stevedylan.dev/posts/programmers-on-the-verge-of-extinction/</link><guid isPermaLink="true">https://stevedylan.dev/posts/programmers-on-the-verge-of-extinction/</guid><description>Examining the parallels between art, AI, and the existential threat to programmers</description><pubDate>Fri, 27 Feb 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/programmer-extinction.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Build a B2B SaaS, make no mistakes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Great meme, but in reality that’s how most of our time “programming” has gone. We tinker with multiple agents, git worktrees, markdown files in a dozen different folders, all to maximize our effectiveness with AI assisted coding. Sometimes it can be exciting, watching a computer spit out code that actually works, bringing your ideas to life at a rapid pace. I personally have been using Claude or OpenCode to build several projects, but to be honest, most of them have left me feeling rather empty. The end product is useful, the job gets done, but… I don’t really care for it? As in, I don’t feel an obligation to keep updating it or make it better. Such an odd and weird feeling that for at least me personally has stuck around since going heavy into AI programming. Why is that? Why does programming feel so empty?&lt;/p&gt;
&lt;h2&gt;Data Learning to Paint&lt;/h2&gt;
&lt;p&gt;I recently watched &lt;a href=&quot;https://youtu.be/mb3uK-_QkOo&quot; target=&quot;_blank&quot;&gt;a great keynote by Brian Sandersen&lt;/a&gt;, a sci-fi/fantasy author. In it he tries to answer the question of why AI generated art gives him such a bad taste in his mouth. He admits that his opinion can be counted among those in history who despised new technology and were left to eat their words, so he tries to go another level deeper by asking “what is art?” and “why do we make it?” To help answer this he turns to Star Trek: the Next Generation character Data.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/data-painting.jpg&quot; alt=&quot;data painting&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;He’s an android, and a lot of his character arcs were about exploring what it means to be human. One of the recurring themes in the show was his attempts to create art: painting, poetry, music, becoming a comedian. I rooted for Data, a synthetic being without emotions trying so hard to understand the human experience. I still do. I have no problem with Data creating art; if he were real, I’d applaud him.&lt;/p&gt;
&lt;p&gt;Why do I empathize with Data yet not the AI large language models?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As Brian goes down this rabbit hole of creating art, and recalling his own creative writing journey, he realizes that art is more about his journey, rather than the end product.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Data created art because he wanted to grow. He wanted to become something. He wanted to understand. Art is the means by which we become what we want to be.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The Need to Grow&lt;/h2&gt;
&lt;p&gt;You ever look back at some of your earlier projects or pieces of code? For most of us, especially myself, it can be pretty cringe. However, it can also be encouraging. That feeling stems from the root that you have grown since you last wrote that code. Your knowledge has grown and your skills have evolved, giving you the ability to write cleaner and more efficient code. Not only that, your problem solving skills have also advanced. Programming is mostly problem solving, and by pushing yourself further and further, you can solve harder problems as time goes on. At least, that’s how it used to be.&lt;/p&gt;
&lt;p&gt;Nowadays we make AI models solve those problems. You may counter with the fact that “oh well this is a problem I solved before,” and sure some stuff can be pretty trivial, but if you never stop to solve those problems, will you remember how to do it without an agent? I like to tell myself I’m not becoming dull, but I know better. What about the next generation of developers who haven’t solved that problem? I’m sure the agent will give them the answers, but now there’s an implementation that hasn’t been reviewed by someone who understands it.&lt;/p&gt;
&lt;p&gt;I think we can also argue that it’s ok to use AI for scaffolding code, saving time by not having to write boilerplate that we would have needed to write before. While that’s true, and it’s often something I do myself, I’ve started to wonder if this is part of why my code feels so empty. If I already knew the answers to the problem &lt;em&gt;and&lt;/em&gt; I didn’t write it, did I actually do anything? What did I accomplish outside prompting an agent?&lt;/p&gt;
&lt;p&gt;How did I grow?&lt;/p&gt;
&lt;h2&gt;The Need to Produce&lt;/h2&gt;
&lt;p&gt;In Sandersen’s keynote he also talks about how art is mostly useless in the grand scheme of things. Yes there is a case for art being consumed by others, but primarily art is about the intrinsic need to make it. It’s about the process, and how it changes you in the journey.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The book, the painting, the film script is not the only art. It’s important, but in a way it’s a receipt. It’s a diploma. The book you write, the painting you create, the music you compose is important and artistic, but it’s also a mark of proof that you have done the work to learn.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The argument could be made that programming is in many ways a type of art. It involves creative solutions to problems that can be solved numerous ways. It can evolve your perspective and strengthen your mind. In my personal experience I even find it therapeutic. However there is a key difference with this and what Sandersen draws: the product.&lt;/p&gt;
&lt;p&gt;Unlike most art, the product of programming is indeed useful, let alone profitable. For the majority it’s why the programming exists in the first place. The finished software is designed to be used, consumed, and often purchased. Those of us who do software development professionally are expected to produce, and to do it quickly. AI has only made this worse since agents bring the ability to write &lt;em&gt;lots&lt;/em&gt; of code at once. In the grand scheme of our programmer evolution, the need to deliver the product outweighs the need to grow.&lt;/p&gt;
&lt;h2&gt;Extinction&lt;/h2&gt;
&lt;p&gt;In the tug-of-war between growth and producing, programmers face an existential threat. Juniors are faced with the decision of switching careers because the industry tells them they are replaceable, even though the &lt;a href=&quot;https://www.theregister.com/2025/08/21/aws_ceo_entry_level_jobs_opinion/&quot; target=&quot;_blank&quot;&gt;CEO of AWS would say otherwise&lt;/a&gt;. If the next generation of programmers sticks around and they don’t experience the growing pains of non-AI assisted development, how will they be able to solve harder problems? The senior engineers will only be around for so long. For those juniors, AI can only take you so far, and you better hope that it doesn’t make mistakes in fields like medicine or security. When the machine built to give specific doses of radiation suddenly gives too much because of an obscure bug, they’re not gonna blame the AI agent that wrote it. An extreme example perhaps, but with how much our world depends on software, this type of tech debt adds up. Just look at how many times the internet has basically crashed over the last two years due to bugs from cloud providers.&lt;/p&gt;
&lt;p&gt;True programmers exist because they’re passionate about solving real and hard problems. They enjoy the satisfaction of solving a puzzle that helps people. By offloading that problem solving to an agent, we’re cheating ourselves of an experience that would give us a sense of fulfillment, and knowledge that we would otherwise not have. Not only that, but the desire to maintain that software down the road will be nonexistent. Why would we bother trying to fix bugs in something that cost us zero blood sweat and tears?&lt;/p&gt;
&lt;h2&gt;Moving Forward&lt;/h2&gt;
&lt;p&gt;It’s hard to say what the future looks like.  On one hand I like to be optimistic that traditional programming will continue to exist, but I also know plenty of programmers who don’t care how they get to the final product, and that’s their choice. There’s a chance none of us are writing code in a decade or possibly sooner, but I don’t live in the projections of the future. Instead I’ll be trying to find some kind of balance between using AI and doing the work manually, with the key objective of &lt;em&gt;enjoying&lt;/em&gt; the work I do. I want to solve the harder problems, problems that can’t be solved with vibe coding. I can’t do that if I let my mind become dull.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Therefore, we have the power here and not the machine, for it was created to try to make something useful. But it cannot admire what it made.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Programming Bowls</title><link>https://stevedylan.dev/posts/programming-bowls/</link><guid isPermaLink="true">https://stevedylan.dev/posts/programming-bowls/</guid><description>Realizing how much of the programming space is just bowls</description><pubDate>Thu, 16 Oct 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/diogenes.D606p03x_2b4qYE.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;630&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of my favorite videos I watched recently was by Pewdiepie, and while the title is &lt;a href=&quot;https://www.youtube.com/watch?v=n_Lv_mw6m6c&quot; target=&quot;_blank&quot;&gt;“help, Im going through a midlife crisis…”&lt;/a&gt;, you realize about 3/4 of the way through that it was just bait. The real topic of the video is minimalism, and a great story about the philosopher Diogenes and his bowl. Diogenes was known for only having a single possession to his name: a wooden bowl. One day as he was walking he saw a child by the river drinking the water by cupping his hands together. It was in this moment Diogenes realized that his bowl was useless since he had his hands, and he threw away his bowl. It was the ultimate commitment to the principles he believed, that true happiness was not found in wealth or possessions. You should watch the video because 1. Pewdiepie tells the story way better than I do and with a lot more humor, and 2. he articulates how he’s carried out the principles of minimalism in his own life and living by principle.&lt;/p&gt;
&lt;p&gt;I’m not going to argue the points of Diogenes in the grander scheme of life, however I will explore what this might look like in the world of programming. For a few months now I’ve been reconsidering how I approach and solve problems in software, especially in web development. I now am starting to ask myself “what are the bowls of programming?”, and in this post we’ll consider a few.&lt;/p&gt;
&lt;p&gt;Before we get started, I want to make it explicitly clear that these points are not absolute. This way of thinking should be seen as a spectrum, and depending on the context of what you’re building or solving, you may land on different levels of programming minimalism. What I would encourage is to dig deep and ask yourself with honesty where your solutions should land, knowing that sometimes it’s ok just to choose whatever you feel most comfortable in. It’s your life; do what you want, but I hope to argue a point of responsibility towards other programmers.&lt;/p&gt;
&lt;h2&gt;Unnecessary Complexity&lt;/h2&gt;
&lt;p&gt;While this point mostly applies to web development, it can apply elsewhere. For the sake of illustration we’ll look at the web dev space thanks to Javascript frameworks. As web apps started to take off and more and more people moved towards Javascript, there was a brief period where we were onto something. JAMStack, aka “Javascript, APIs, Markdown” was a simple yet brilliant way to standardize building apps or websites. This was the golden era of static site generators and simple client-server relationships, where if a hosting platform didn’t work out it was simple to move somewhere else. The frameworks that emerged were platform agnostic, people hosted their own stuff, and it created an open web.&lt;/p&gt;
&lt;p&gt;Years later we start to see complex SSR Javascript frameworks being pushed more and more by hosting providers. Next.js brings a new developer experience that a lot of people latch onto and quickly becomes one of the most commonly used frameworks in the ecosystem. The main problem is that it has become too complex and specially designed to work on Vercel. Yes you can “self host” Next.js, but it won’t work or perform the same way it does on Vercel. A whole initiative was started by Netlify and Cloudflare to create a fork called &lt;a href=&quot;opennext.js.org&quot;&gt;OpenNext&lt;/a&gt; to help make self hosting a fully featured Next.js app possible, and it’s still not meeting all the features Next.js offers.&lt;/p&gt;
&lt;p&gt;Perhaps the most frustrating part is that most developers don’t actually need Next.js 95% of the time when building web applications. We’ve even seen &lt;a href=&quot;https://x.com/joshtriedcoding/status/1922631724002902083&quot; target=&quot;_blank&quot;&gt;influencers give tips&lt;/a&gt; on how they can speed up their Next.js app by making all pages render as static except for API routes. It’s simply bad education for new developers who have no idea how simple it can be to spin up a backend API and use that in combination with a static app. Some don’t even know the difference between client and server architecture, and that’s ok because we were all there once, but I don’t want people to stay there. It’s one of the reasons I built &lt;a href=&quot;https://bhvr.dev&quot; target=&quot;_blank&quot;&gt;bhvr&lt;/a&gt; as I wanted people to know that you can have a solid DX while not risking platform lock-in. It can’t be used for every problem, and even I am willing to admit that an SSR stack like Next.js or TanStack Start is better suited for an ecommerce platform. What I am saying is people should know the options out there. You don’t need the complexity of an SSR stack.&lt;/p&gt;
&lt;p&gt;Next.js, by and large, is a bowl.&lt;/p&gt;
&lt;h2&gt;Dependencies&lt;/h2&gt;
&lt;p&gt;Another piece of the programming pie that could be argued as a bowl is dependencies. Just recently we’ve seen multiple &lt;a href=&quot;https://thehackernews.com/2025/09/40-npm-packages-compromised-in-supply.html&quot; target=&quot;_blank&quot;&gt;supply chain attacks on NPM and even a worm&lt;/a&gt;. We simply run &lt;code&gt;install&lt;/code&gt; with trusting ignorance that all the code we’re downloading is benevolent. The majority of it is, but it’s the reality that most of us don’t care that’s more disturbing. We’ve become lazy and would rather install a package that’s less than 100 lines of code rather than implement it ourselves and know for a fact that it’s not going to attack us. Dependencies also make us deal with clashing version interdependencies, where two packages require the same external package and they both require different versions. It’s an awful experience.&lt;/p&gt;
&lt;p&gt;While all of these things are true, we must admit the necessity of package managers and dependencies in modern software. Not everything can be written from scratch, some packages like cryptographic libraries need audits and should not be rolled by hand. There are always exceptions and we have to look at it with nuance. With that said, all of these things have made me stop and think, “do I really need to download this?” or “what am I downloading exactly?” For some apps we’re totally ok just installing twenty dependencies as it’s not a serious project, but what about the software we want to last longer than a year? What about web apps that people depend on and we want to keep them running five or even ten years from now? We should think about these things before we just run &lt;code&gt;install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dependencies, in many cases, are a bowl.&lt;/p&gt;
&lt;h2&gt;Languages and Development Environments&lt;/h2&gt;
&lt;p&gt;If I haven’t lost you yet then I’m probably about to with this last one. We’re getting into pretty deep water and areas that most (including myself) are not ready to tread. There are some who argue and live by the principle that most programming languages or development environments are bowls. A huge influence into this particular mindset is &lt;a href=&quot;https://100r.co&quot; target=&quot;_blank&quot;&gt;100 Rabbits&lt;/a&gt;, and one of my favorite blog posts on the subject is &lt;a href=&quot;https://100r.co/site/weathering_software_winter.html&quot; target=&quot;_blank&quot;&gt;Weathering Software Winter&lt;/a&gt;. In the post they discuss how their desire to live on a solar-powered sailboat greatly limited the kind of software they could download or build with, especially as designers. If you’ve ever touched an Adobe product you probably know exactly what they’re talking about; the need to constantly be connected to the internet to use a piece of software. Even when exploring building their own, they found limitations around tools like Xcode that constantly required heavy downloads or other languages that needed to install a bunch of packages. It’s these situations of limited network speed that we don’t think of often, and it’s exactly where 100 Rabbits found themselves.&lt;/p&gt;
&lt;p&gt;The solution? Perhaps a bit extreme, but they built their own VM and assembler. With a VM they could compile software to run on almost any device, no matter how old it is or what the OS is. It’s low level enough to accomplish most tasks, and it’s with this combination that they continue to build basic OS level applications to fulfill their design and everyday needs. Their goals were to have something so simple that they could fit the documentation on a t-shirt and reimplement it within a weekend. When you know your software so well that you also have a good idea of what it looks like in assembly, you can truly maintain and repair your tools. These tools are also built to last, using recycled hardware that is normally thrown out. A form of eco-minded software development that has labeled itself as permacomputing. Now of course this is not something we can all just pick up and start rewriting all software. The world is complex which means we have complex software running everywhere, but it also demonstrates that it doesn’t always have to be that way. There are ways we can build thoughtful, long lasting, and meaningful software to meet our own needs.&lt;/p&gt;
&lt;p&gt;For 100 Rabbits, most languages were a bowl.&lt;/p&gt;
&lt;h2&gt;Where Do We Land &amp;amp; Why Bother?&lt;/h2&gt;
&lt;p&gt;No matter how hard I might argue these principles, I hope my point of nuance comes across. None of these are absolutes. One could argue programming minimalism to the point of writing ones and zeros. The range of how far you can go with throwing out programming bowls depends on what you’re building. 100 Rabbits also pushes for more offline applications and depending less on the connected web. However there is a big difference between building simple desktop apps and an app like ICEBlock. Context decides how complex something needs to be. We don’t want to roll our own cryptography; it’s ok to trust libraries that have been audited and battle tested.&lt;/p&gt;
&lt;p&gt;A point that Pewdiepie makes in his video is that while he has thrown out a lot of the stuff that he doesn’t need, it doesn’t mean he throws out everything. There are plenty of things he owns simply because he enjoys them, like a random action figure. If it’s something that brings happiness then that’s great! I have a small rock that looks like an owl on my desk. It doesn’t do anything or serve any purpose; I just like it. I believe the same can go for software engineering. Sometimes I like how quickly I can spin up an app with Bun and Typescript. That’s actually &lt;a href=&quot;https://bearblog.stevedylan.dev/sippso-minimal-code-sharing/&quot; target=&quot;_blank&quot;&gt;what I did&lt;/a&gt; this weekend while drafting this post. If I really wanted to I could have used React to handle the client side logic, but I personally thought the app was simple enough that I didn’t need it. Again, if I wanted it, that would have been ok too. There are no absolutes in the decisions you make in programming as far as I’m concerned.&lt;/p&gt;
&lt;p&gt;If that’s the case, why bother? There is one important factor that we cannot forget as we make these decisions: people. My greatest aspiration in programming is to build software that advances principles such as privacy, security, freedom, and the ability to repair. ICEBlock was an iOS app that was &lt;a href=&quot;https://www.wired.com/story/apple-took-down-ice-tracking-apps-their-developers-arent-giving-up/&quot; target=&quot;_blank&quot;&gt;taken off the Apple App Store&lt;/a&gt;, and that is a chilling reminder that the stack decisions we make actually do matter. Yes a web app could also be taken down but it’s much harder to do. I could have used React in my simple code sharing tool but by choosing to use just html, css, js, and sqlite, it’s a lot easier for someone to use and alter long term without any potential dependency issues. We also have to consider if what we’re building is going to be used by other developers and in what ways. A great example is &lt;a href=&quot;https://github.com/paulmillr/noble-hashes&quot; target=&quot;_blank&quot;&gt;@noble/hashes&lt;/a&gt; where there is zero dependencies but acts as a foundational building block for cryptographic operations.&lt;/p&gt;
&lt;p&gt;In the end it comes down to how much we care. Not speed for the sake of speed, or minimalism for the sake of minimalism, but for privacy, security, freedom, and longevity for the sake of people who depend on it.&lt;/p&gt;
&lt;p&gt;Keep the bowl or throw it out, but at the very least think about it first.&lt;/p&gt;</content:encoded></item><item><title>Resurrect the Old Web</title><link>https://stevedylan.dev/posts/resurrect-the-old-web/</link><guid isPermaLink="true">https://stevedylan.dev/posts/resurrect-the-old-web/</guid><description>Let&apos;s go back to when social media was about people</description><pubDate>Tue, 23 Sep 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/bear-blog.BAYEJdDi_ZxDtpv.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;630&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recently a local news station in Maine reported a story of some middle schoolers calling their friends with landline telephones. Their parents thought they were too young for cell phones and wanted to hold off on that aspect of reality, so they got an old phone from the 90s, and soon their friends also got phones. It formed schedules of calls they would make to talk to each other, even creating a phone ring of contacts.&lt;/p&gt;
&lt;p&gt;I think I can confidently say that the majority of us aren’t happy with the state of social media. Back in its early days it was fresh and exciting, a fun way to connect with your friends that might be far away, or make new friends online. It was cozy. No ads, no feeds, no endless videos. Instead it was just people, the whole reason you started in the first place. Now it’s just noise and scary addicting and effective algorithms that keep you plugged in for hours on end. We build apps and products to help kill the monster, or perhaps we even delete some social media apps. Many of our friends we used to stay connected with seem so distant, as many of them too are tired and perhaps jumped off socials altogether. Well, what if I told you we could have the old web back?&lt;/p&gt;
&lt;p&gt;In my opinion the answer is honestly pretty simple: blogs and RSS feeds. This was how it was done for years before social media came into the scene. You would find someone’s blog, subscribe to their RSS feed, and anytime a new post came out it would pop up in your feed and you could read it. One important clarification is that when we say “blog” it can be pretty much whatever you want it to be. On my personal website I generally write more of my serious blogs, but on my bear blog I plan to be a bit more casual. It will be a place where I record short thoughts, ideas, musings, or cool things I find on the internet. Just sharing what I would normally share with my friends. That’s what made the web great, and that’s what I want to bring back.&lt;/p&gt;
&lt;p&gt;To do this, I am starting a &lt;a href=&quot;https://bearblog.dev&quot; target=&quot;_blank&quot;&gt;bear blog&lt;/a&gt; that will have a dedicated &lt;a href=&quot;https://stevedylandev.bearblog.dev/feeds&quot; target=&quot;_blank&quot;&gt;feeds page&lt;/a&gt; that will have all the other blogs I’m subscribing to. The beauty is that you don’t need a dedicated social network to make this work; just click on the links. Use whatever RSS reader you want! You don’t have to use bear blog either, just use whatever blog you want. The key is connection. I want to point to who I follow so that you might follow them too, and hopefully create a page on your own. In some ways it’s bringing back old web rings and simple networking through hyperlinks.&lt;/p&gt;
&lt;p&gt;To kick it off, here’s a few blogs I’m already subscribed to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://polluterofminds.bearblog.dev&quot; target=&quot;_blank&quot;&gt;polluterofminds bear blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sdv.bearblog.dev&quot; target=&quot;_blank&quot;&gt;syndicated debatable views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://0xgramajo.xyz&quot; target=&quot;_blank&quot;&gt;gramajo’s blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.vrypan.net&quot; target=&quot;_blank&quot;&gt;blog.vrypan.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vitalik.eth.limo&quot; target=&quot;_blank&quot;&gt;vitalik.eth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to join but not sure how, check out the video I recorded below:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;Best way to keep up with your feeds is to find yourself an RSS reader! There are a lot of options out there (although admittedly a bit old), so just find it on the platform that suits you best. &lt;a href=&quot;https://feeder.co/reader&quot; target=&quot;_blank&quot;&gt;Feeder.co&lt;/a&gt; has a pretty generous free plan, and if you’re a dev there’s hundreds of self hosted projects to choose from like &lt;a href=&quot;https://github.com/nkanaev/yarr&quot; target=&quot;_blank&quot;&gt;Yarr&lt;/a&gt;. Personally rocking &lt;a href=&quot;https://netnewswire.com&quot; target=&quot;_blank&quot;&gt;NetNewsWire&lt;/a&gt; for MacOS and iOS, and loving it so far!&lt;/p&gt;
&lt;p&gt;I have no idea if this will amount to anything or if it’s worthwhile, but I’m gonna give it a shot. The landline phones prove that we don’t have to buy into the social media dopamine machine. We have autonomy, and we have the freedom to choose how we interact with each other. I want to believe we can resurrect the old web, together.&lt;/p&gt;</content:encoded></item><item><title>Returning to Neovim</title><link>https://stevedylan.dev/posts/returning-to-neovim/</link><guid isPermaLink="true">https://stevedylan.dev/posts/returning-to-neovim/</guid><description>Once again coming back to the editor I can&apos;t shake</description><pubDate>Mon, 16 Mar 2026 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/return-to-neovim.DgyTSPPB_1oS4Ju.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of my more popular blog posts was how and why I switched to &lt;a href=&quot;https://stevedylan.dev/posts/leaving-neovim-for-zed/&quot; target=&quot;_blank&quot;&gt;Zed from Neovim&lt;/a&gt;. That was almost two years ago, and in that period Zed was my daily driver for programming. Every now and then I would still use Neovim to edit a config or make a quick edit, but outside of that, Zed was where I lived. It performed admirably, with minimal bugs that only irked me from time to time that would eventually be fixed. AI was still relatively minimal and I enjoyed using it. So why go back to Neovim?&lt;/p&gt;
&lt;h2&gt;What Happened&lt;/h2&gt;
&lt;p&gt;The real shift happened about a week ago when Zed updated its terms and policies, including a new age restriction of 18+. The Zed team clarified that this was in regards to their online services and platform used for AI assisted coding, but at least for me, it was still a bit unnerving. A few people had already made forks of Zed through the open source licenses that the editor is under, and I did try them, but it was clear that the Zed experience was not the same. Some stuff didn’t work, extensions had to be installed manually, just overall a horrible experience.&lt;/p&gt;
&lt;p&gt;That was the wake up call. I realized I couldn’t really trust Zed moving forward. I really think the team is awesome and what they’re building is perhaps the best editor alternative to VSCode, but I also understood that they have to make money somehow. When it comes to writing code, the last place I want to find myself in is being held hostage or being forced off the platform. I have to be able to write code productively without my flow being interrupted by the decisions of higher management. Neovim isn’t a perfect drop in replacement in this regard either, but I trust it way more as a community backed and managed project.&lt;/p&gt;
&lt;p&gt;Since switching back to Neovim full time, I’ve honestly had no regrets. I updated my config last year, and having the opportunity to daily drive it has proven how capable it truly is. There was a mental plan to adjust pieces to meet what I might have missed in Zed, but I haven’t had to make any changes yet. With that said I figured it would be a good time to share what my config looks like and how effective it is.&lt;/p&gt;
&lt;h2&gt;The Config&lt;/h2&gt;
&lt;p&gt;There’s generally two ways people end up configuring Neovim. One path is using a distro like &lt;a href=&quot;https://www.lazyvim.org/&quot; target=&quot;_blank&quot;&gt;LazyVim&lt;/a&gt;, the other is writing it from scratch. I’ve taken the distro path before and I think it’s great if you have no idea what you want, but eventually you might find yourself wanting to slim things down. If doing a config from scratch feels intimidating, I would highly recommend &lt;a href=&quot;https://www.youtube.com/playlist?list=PLsz00TDipIffreIaUNk64KxTIkQaGguqn&quot; target=&quot;_blank&quot;&gt;this series&lt;/a&gt; which goes over all the different aspects of a config. Below is a quick overview of my config structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;nvim&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── lazy-lock.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── plugins&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── ai-vim.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── treesitter.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── mini.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── tmux-navigator.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── colorschemes.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── config&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── options.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── keymaps.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── autocmds.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── core&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── lazy.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── lsp.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── lsp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── gopls.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── solc.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── asm-lsp.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── rust-analyzer.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── astro.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── html.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── json.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── lua_ls.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── tsserver.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── init.lua&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ll do my best to go over all of the different pieces I have here.&lt;/p&gt;
&lt;h3&gt;Plugin Manager&lt;/h3&gt;
&lt;p&gt;I’ve been using &lt;a href=&quot;https://github.com/folke/lazy.nvim&quot; target=&quot;_blank&quot;&gt;lazy.nvim&lt;/a&gt; for years (not to be confused with LazyVim, a distro that uses lazy.nvim), and it’s just solid. Always works, zero issues, and boy it can go &lt;em&gt;fast&lt;/em&gt; (will go over that later). There’s not much else to say due to how much of an industry standard it is. I might give the new native plugin manager coming in Neovim 12 a try, but I’ve already seen some people say it’s not as fast as lazy.nvim, so I’ll be keeping an eye on it for future development.&lt;/p&gt;
&lt;h3&gt;LSP&lt;/h3&gt;
&lt;p&gt;The Language Server Protocol (LSP) provides a standard for different languages to provide feedback in dev workflows. Common example would be writing an incorrect type in Typescript which would cause the compiler to fail. Instead of having to run it, the editor shows some red lines saying something is wrong. Many editors set this up behind the scenes, but that’s not the case for Neovim, and it can be one of the big things people struggle with. In the past I’ve used a few plugin combinations which were always a mess, but I was so excited that native LSP support came to Neovim last year! &lt;a href=&quot;https://youtu.be/IZnhl121yo0&quot; target=&quot;_blank&quot;&gt;This video&lt;/a&gt; does a fantastic job walking you through how to set it all up, but its really as simple as creating a dedicated &lt;code&gt;lsp&lt;/code&gt; folder with the different languages, then making a &lt;code&gt;lsp.lua&lt;/code&gt; config file. Here’s an example for Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	cmd&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;rust-analyzer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	filetypes&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;rust&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	root_markers&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;Cargo.toml&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;Cargo.lock&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;.git&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	settings&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		[&lt;/span&gt;&lt;span&gt;&quot;rust-analyzer&quot;&lt;/span&gt;&lt;span&gt;] = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			cargo&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				allFeatures&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				loadOutDirsFromCheck&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				runBuildScripts&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			-- Add other rust-analyzer specific settings here&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			checkOnSave&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			procMacro&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				enable&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				ignored&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					leptos_macro&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						-- &quot;component&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;server&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	single_file_support&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	log_level&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.lsp.protocol.MessageType.Warning,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Neovim what files to use the rust-analyzer LSP for, what files might indicate a project, and any other options we may want to add. We follow the same structure for all languages or frameworks that have an LSP. Then inside &lt;code&gt;lsp.lua&lt;/code&gt; we just add the following configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.lsp.&lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;astro&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;gopls&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;lua_ls&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;tsserver&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;rust-analyzer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;asm-lsp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;solc&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;html&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;json&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  virtual_lines&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -- virtual_text = true,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  underline&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  update_in_insert&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  severity_sort&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  float&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;rounded&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    source&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  signs&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    text&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.ERROR] = &lt;/span&gt;&lt;span&gt;&quot;󰅚 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.WARN] = &lt;/span&gt;&lt;span&gt;&quot;󰀪 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.INFO] = &lt;/span&gt;&lt;span&gt;&quot;󰋽 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.HINT] = &lt;/span&gt;&lt;span&gt;&quot;󰌶 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    numhl&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.ERROR] = &lt;/span&gt;&lt;span&gt;&quot;ErrorMsg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.WARN] = &lt;/span&gt;&lt;span&gt;&quot;WarningMsg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key is &lt;code&gt;vim.lsp.enable()&lt;/code&gt; where we pass in the names of all our files that have configs. Everything else is just some nicer configuration for looking at diagnostics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/nvim-diagnostics-icons.Dq8bigny_1cEBI4.webp&quot; alt=&quot;nvim diagnostics icons&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;644&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It’s that simple, and I absolutely love how minimal the experience is. Does require understanding what your LSPs are, where they live, and how to run them, but totally worth it.&lt;/p&gt;
&lt;h3&gt;Plugins&lt;/h3&gt;
&lt;p&gt;You might have noticed that I don’t have that many plugins, but it’s actually a bit deceiving.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ai-vim.lua&lt;/code&gt; - Small inline AI editing plugin which I don’t actually use much, will probably cut it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;treesitter.lua&lt;/code&gt; - Syntax highlighting, pretty standard.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tmux-navigator.lua&lt;/code&gt; - Lets me use &lt;code&gt;ctrl+h/j/k/l&lt;/code&gt; to switch between a Neovim session and another tmux pane.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;colorschemes.lua&lt;/code&gt; - Themes baby, currently on my own called &lt;a href=&quot;https://github.com/stevedylandev/darkmatter-nvim&quot; target=&quot;_blank&quot;&gt;Darkmatter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The one I didn’t list here is &lt;a href=&quot;https://github.com/nvim-mini/mini.nvim&quot; target=&quot;_blank&quot;&gt;mini.nvim&lt;/a&gt;, which is the true star of this config. mini.nvim is a collection of minimal plugins that are installed and setup through a single config. They’re all simple, functional, and they really help lighten up your config. Here’s a quick run down of some of my favorites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mini.completion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This would normally be handled through something heavier like coc.nvim, but it’s truly awesome to have a simple and lightweight option inside mini.nvim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mini.files&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A minimal file explorer thats a fun mix between oil and netrw.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mini.pick&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick anything. Actually though. In a lot of ways this replaces telescope and lets me fuzzy find files, buffers, you name it!&lt;/p&gt;
&lt;h3&gt;Other Bits&lt;/h3&gt;
&lt;p&gt;There are some smaller quality of life pieces I have that don’t really fit into any specific category, so here’s a few my favorites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VimEnter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I got this one from &lt;a href=&quot;https://www.youtube.com/@adibhanna&quot; target=&quot;_blank&quot;&gt;Adib Hanna&lt;/a&gt; a long time ago. Instead of showing a start screen when I open Neovim, instead I use &lt;code&gt;mini.pick&lt;/code&gt; to fuzzy find all files within the current directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.api.&lt;/span&gt;&lt;span&gt;nvim_create_autocmd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;VimEnter&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  callback&lt;/span&gt;&lt;span&gt; = function()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if &lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.fn.&lt;/span&gt;&lt;span&gt;argv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) == &lt;/span&gt;&lt;span&gt;&quot;&quot; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      vim&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;defer_fn&lt;/span&gt;&lt;span&gt;(function()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;mini.pick&quot;&lt;/span&gt;&lt;span&gt;).builtin.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      end, &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-- Wait 100ms&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  end,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Buffer Management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don’t have tabs setup in Neovim as I started to realize I don’t really need them. I can use my keyboard shortcuts to move between buffers horizontally,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-- Navigate buffers&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;S-l&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:bnext&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;S-h&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:bprevious&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or filter through them with &lt;code&gt;mini.pick&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;o&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick buffers&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I can just close them with the following keymap:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;c&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:bd&amp;lt;cr&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Diagnostics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nothing too fancy here but it is nice how versatile the experience can be. I can either use this keymap to do a hover diagnostic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;gl&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.open_float, &lt;/span&gt;&lt;span&gt;&quot;Open Diagnostic Float&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or I can view all diagnostics for the project with mini.pick:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;d&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick diagnostic&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Finding Stuff&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the most important things an editor needs is an easy way to find anything, and I’m quite pleased with what I have setup here. To start, if I wanted to search the entire codebase for a given string, I can use mini.pick with &lt;code&gt;live_grep&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;/&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick grep_live&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same goes for buffers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;o&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick buffers&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For files, I can either do a fuzzy find like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;f&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick files&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or I can use the file browser with mini.files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;e&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;lua MiniFiles.open()&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last but not least, you can also use Pick to get all the help manuals for Neovim or the plugins installed.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;hh&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick help&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Speed&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;025.359  000.037: BufEnter autocommands&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;025.363  000.004: editing files in windows&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;025.381  000.019: --- NVIM STARTED ---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration is incredibly fast. Startup times averages around ~25ms. A more detailed report can be found below.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sipp.stevedylan.dev/s/dJfiLGUx9u&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;.nvim-startup.txt&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Source&lt;/h3&gt;
&lt;p&gt;By all means feel free to checkout the whole config yourself in my &lt;a href=&quot;https://github.com/stevedylandev/dotfiles&quot; target=&quot;_blank&quot;&gt;dotfiles repo&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;I really do appreciate the time I had with Zed and it is a solid editor; I don’t fault anyone who uses it. Personally I’ve just been through this kind of thing too many times, where I thoroughly enjoy and depend on a tool, just for it to go sideways and absolutely cripple my productivity. The Arc Browser was a great example of this. I would much rather jump ship early, figure out a new workflow that is dependable, and stick with it. That’s exactly what I’ve done with Neovim, and it’s good to be back.&lt;/p&gt;</content:encoded></item><item><title>Spring Website Updates</title><link>https://stevedylan.dev/posts/spring-website-updates/</link><guid isPermaLink="true">https://stevedylan.dev/posts/spring-website-updates/</guid><description>Some fun updates I&apos;ve made to my personal website</description><pubDate>Sun, 26 Apr 2026 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/spring-updates.CIfQIAlE_7iQJK.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Earlier this week I quietly released an iOS app called &lt;a href=&quot;https://apps.apple.com/us/app/scout-smallnet-browser/id6762810515&quot; target=&quot;_blank&quot;&gt;scout&lt;/a&gt;, a Gemini protocol client. I built it quite a while ago but never published, and thought “why not?” Almost immediately after doing so, I read &lt;a href=&quot;https://xn--gckvb8fzb.com/gemini-is-solutionism-at-its-worst/&quot; target=&quot;_blank&quot;&gt;this post&lt;/a&gt; that really made me think about the Gemini approach in general. After some deliberation, I thought it would be good to pour more into my personal site and invest in HTTP. The results have been so much fun, and I haven’t been able to stop making changes all week! Here’s a few highlights.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/birds&quot;&gt;&lt;strong&gt;Birds&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/birds-demo.png&quot; alt=&quot;birds demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Yup, birds. I realized that Merlin Bird ID didn’t have a way to share all the birds on my lifelist, so I found my own way to do it. Involves downloading my data as a CSV then running a script to convert it into JSON that the site uses, so not as fluid as I would like but gets the job done. It’s so fun to scroll through all of them!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/library&quot;&gt;&lt;strong&gt;Library&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/library-demo.png&quot; alt=&quot;library demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I recently &lt;a href=&quot;/posts/building-personal-software-in-rust&quot;&gt;wrote a post&lt;/a&gt; about how I started building small apps that help me organize or publish different forms of content, and one that I didn’t have yet was a book tracker. Only took me 30 mins to spin one up and I absolutely love it. Nothing like seeing how many books you haven’t read to inspire you to read more lol.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/bookmarks&quot;&gt;&lt;strong&gt;Bookmarks&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/bookmarks-demo.png&quot; alt=&quot;bookmarks demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Another small app that simply lets me save links in different categories. The best part is some of the automations I’ve made with Apple Shortcuts. If you haven’t messed with them much, they are actually quite powerful. API calls with headers and JSON are totally doable, and you can pass in different variables too. Below is the main guts of the shortcut that adds a link to my bookmarks!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;┌─────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  Receive Input from Share Sheet     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  (Apps + 18 more)                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  If no input → Continue             │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└──────────────────┬──────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌──────────────────▼──────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  Get URLs from Shortcut Input       │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└──────────────────┬──────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌──────────────────▼──────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  Get Name of URLs                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└──────────────────┬──────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌──────────────────▼──────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  Text                               │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  [API Key — redacted]               │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└──────────────────┬──────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌──────────────────▼──────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  POST to:                           │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  bookmarks.stevedylan.dev/api/links │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│                                     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  Headers:                           │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│    x-api-key: [Text / API Key]      │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│                                     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  Body (JSON):                       │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│    title:    [Name]                 │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│    category: &quot;Read Later&quot;           │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│    url:      [URLs]                 │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└──────────────────┬──────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;┌──────────────────▼──────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  Show: Contents of URL              │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  (API response)                     │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└─────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;/cellar&quot;&gt;&lt;strong&gt;Cellar&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/cellar-demo-2.png&quot; alt=&quot;cellar demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This one isn’t too new as I’ve actually had a dedicated wine app as part of Andromeda, however I did recently add a public API to fetch the wines so I can display them on my website.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/git&quot;&gt;&lt;strong&gt;Git&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/ssh-git-demo.png&quot; alt=&quot;ssh git demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Nothing too crazy here, but I did want to have a dedicated page to help guide people to this cool terminal command :)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;ssh&lt;/span&gt;&lt;span&gt; git.stevedylan.dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;/edc&quot;&gt;&lt;strong&gt;EDC&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/edc-demo.png&quot; alt=&quot;edc demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I just love everyday carry stuff, and I decided it was about time to host some of mine. If you’re into that stuff and have thoughts about it &lt;a href=&quot;mailto:contact@stevedylan.dev?subject=EDC&quot;&gt;I’d love to hear about it&lt;/a&gt;! Especially if it’s stationary or watch related.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;It’s been so refreshing to work on this stuff and fulfill some of &lt;a href=&quot;/posts/2026-site-plans&quot;&gt;the goals&lt;/a&gt; I had at the beginning of the year. Day by day my site looks and feels more like me, and I love that. I cannot recommend building your own site enough. It doesn’t have to be self hosted either! I think &lt;a href=&quot;https://pagecord.com&quot; target=&quot;_blank&quot;&gt;Pagecord&lt;/a&gt; or &lt;a href=&quot;https://bearblog.dev&quot; target=&quot;_blank&quot;&gt;BearBlog&lt;/a&gt; serve as great blogging platforms with enough customization that you can add whatever content you want. The key is to just get started. You might end up finding how much you prefer a classic website over social media. While you’re at it, get an RSS reader and subscribe to some blogs and make sure your own blog has RSS too! Make a page and share the people you’re following like &lt;a href=&quot;/feeds&quot;&gt;this one&lt;/a&gt; so others can explore the small web.&lt;/p&gt;
&lt;p&gt;The internet doesn’t have to be terrible. It is what you make it, so go make it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’d like to make a quick acknowledgement to &lt;a href=&quot;https://xn--gckvb8fzb.com/&quot; target=&quot;_blank&quot;&gt;マリウス&lt;/a&gt; for some of the inspiration here as they have similar pages on their site; definitely give it a visit!.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Standard.site: the Publishing Gateway</title><link>https://stevedylan.dev/posts/standard-site-the-publishing-gateway/</link><guid isPermaLink="true">https://stevedylan.dev/posts/standard-site-the-publishing-gateway/</guid><description>Another deep exploration into ATProto and implementing lexicons</description><pubDate>Sun, 11 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/standard-site.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;/posts/using-atproto-for-posse/&quot;&gt;last post&lt;/a&gt; I discussed how I was moving my micro blogging practice to my personal website. I was previously using &lt;a href=&quot;https://bearblog.dev&quot; target=&quot;_blank&quot;&gt;Bear Blog&lt;/a&gt; (highly recommend btw) for small life updates that didn’t fit into full blog posts. I decided to use &lt;a href=&quot;https://atproto.org&quot; target=&quot;_blank&quot;&gt;ATProto&lt;/a&gt; as the means to make this happen, and ended up with a pretty good workflow that let me post through a ATProto client (Bluesky in this case) and the posts would end up on my &lt;a href=&quot;/now&quot;&gt;/now page&lt;/a&gt; as a feed. While this setup was decent, it still had some major flaws.&lt;/p&gt;
&lt;p&gt;For one, the post size cap for Bluesky feed posts. It worked for the most part, but there were many times I would want to make slightly longer posts or updates. I didn’t like not having this freedom. Another issue was using the Bluesky app itself. I made so many of these &lt;a href=&quot;/posts/resurrect-the-old-web/&quot;&gt;life changes&lt;/a&gt; to remove social media from my life, not add it back in. There was also the fact that these posts would be Bluesky posts, showing up in feeds and pointing back to the Bluesky origin. This was my biggest issue as it prevented me from truly adopting &lt;a href=&quot;https://indieweb.org/POSSE&quot; target=&quot;_blank&quot;&gt;POSSE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is when I discovered &lt;a href=&quot;https://standard.site&quot; target=&quot;_blank&quot;&gt;Standard.site&lt;/a&gt;, a new set of &lt;a href=&quot;https://atproto.com/guides/lexicon&quot; target=&quot;_blank&quot;&gt;lexicons&lt;/a&gt; designed especially for publishing. The schema had exactly what I was looking for: canonical URLs that point to my site. At the time I knew nothing about lexicons on ATProto or how they worked, but it sounded like the Standard.site lexicon for publishing could be a huge step towards aggregating original publishing platforms in a totally new way. I had to find out. What followed was a journey that significantly boosted my knowledge of ATProto and how Standard.site could be a new gateway for publishing content on the web.&lt;/p&gt;
&lt;p&gt;Before we go any further, I want to make it crystal clear that I’m not an expert at ATProto. I started digging into this stuff maybe a week or two ago. I’m sharing this story in hopes that others who are interested in a similar integration but are having a hard time finding the answers will get some here. If there are better ways to do some of this stuff, &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;From the very little I knew, it was clear that I would need to make these posts happen through my own client implementation. Since ATProto / PDS’ had OAuth support I thought this would be a great way to post from my own website. When using Bear Blog I loved the ability to post from any browser, and doing the same thing with ATProto on my site was attractive. Overall implementing OAuth took a fair bit of code, but was relatively seamless. My website uses Astro so I added a few React components to handle login, logout, and making a post. For the backend I used a Cloudflare Worker running Hono, as well as a KV instance for saving sessions. Signing into my PDS through my website was an exciting experience, and even more so when making a post worked like a charm!&lt;/p&gt;
&lt;p&gt;The only problem was that I was still posting to Bluesky, so now it was time to dig into Standard.site and lexicons. People kept talking about lexicons but I was pretty clueless to how they worked exactly. Once I looked closer at the code I was using for posts and what was dictated by Standard.site, it ended up being a lot simpler than I thought. Take this payload for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;documentRecord&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	repo&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;did&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	collection&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	record&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		$type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		site&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;`at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.publication/3mbykzswhqc2x`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		...(&lt;/span&gt;&lt;span&gt;normalizedPath&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;normalizedPath&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;() }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;markdownContent&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		textContent&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		publishedAt&lt;/span&gt;&lt;span&gt;: new &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key for using a lexicon is just designating the &lt;code&gt;collection&lt;/code&gt; and the &lt;code&gt;$type&lt;/code&gt;. That’s it. In order to be valid you just have to follow the schema dictated by the lexicon. Standard.site has two primary lexicons: &lt;code&gt;document&lt;/code&gt; for the actual content, and &lt;code&gt;publication&lt;/code&gt; for the source of the document. This made a lot more sense when I started using &lt;a href=&quot;https://pdsls.dev/at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.document/3mc4myqaca22x&quot; target=&quot;_blank&quot;&gt;pdsls.dev&lt;/a&gt; to examine the posts I was making.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/pdsls-example.png&quot; alt=&quot;pdsexample&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The presentation is brilliant. There is a clear order and hierarchy to how ATProto and data works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;andromeda.social&lt;/code&gt; - This is my PDS, the core where my data is stored&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stevedylan.dev (did:plc:ia2zdnhjaokf5lazhxrmj6eu)&lt;/code&gt; - My “account” or “DID” that is hosted on my PDS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site.standard.document&lt;/code&gt; - The collection where my post is, kinda like a folder&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3mc4myqaca22x&lt;/code&gt; - The record itself that has all the data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These pieces make up the AT URI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.document/3mc4myqaca22x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seeing lexicons as folders clicked for me. Not just folders, but standards that follow a schema. In order for my posts to follow the standard I simply needed to create an initial &lt;code&gt;publication&lt;/code&gt; record with my website info and then reference it in each &lt;code&gt;document&lt;/code&gt;. Once you have that setup you can actually click on the “Info” tab in pdsls.dev and validate the info to make sure it’s correct. You can also add other fields if you want to as long as the original and required fields are met, making it very flexible.&lt;/p&gt;
&lt;p&gt;With the lexicons in place I finally had what I wanted: posts that aren’t attached to Bluesky, stored on my PDS, and could be indexed and aggregated due to using Standard.site. The final piece I wanted to add was comments. It felt like the logical conclusion, and also would be weird to use ATProto for this concept yet only include “reply via email” at the bottom. The big decision was which lexicon to use. Since I was avoiding using Bluesky lexicons, a simple “reply” wouldn’t really work. Eventually I decided to build a lexicon on top of Standard.site: &lt;code&gt;site.standard.document.comment&lt;/code&gt;. It’s not technically a valid schema, but I figured it would work for now.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;commentRecord&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	repo&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;did&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	collection&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document.comment&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	record&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		$type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document.comment&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		parent&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			uri&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parentUri&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			cid&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;parentCid&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		root&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			uri&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parentUri&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			cid&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;parentCid&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		author&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			did&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;did&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			handle&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authorHandle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			...(&lt;/span&gt;&lt;span&gt;authorDisplayName&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;displayName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authorDisplayName&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			...(&lt;/span&gt;&lt;span&gt;authorAvatar&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;avatar&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authorAvatar&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		createdAt&lt;/span&gt;&lt;span&gt;: new &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In no time at all I had an OAuth flow for posting and creating comments for my update documents. On my site I had a component that would request the comments for a given document, but I quickly faced an issue. When I replied to a post from another account, it didn’t show up. I soon realized this was due to the other account being on the &lt;code&gt;bsky.social&lt;/code&gt; PDS, not my self hosted instance. This would be a problem because there might be loads of people who have their own PDS; I can’t limit it to just one. How do I get these lexicons across thousands of hosted instances?&lt;/p&gt;
&lt;p&gt;This was quite a wakeup call. How would Standard.site promise a future where you could aggregate any content that follows the standard? Thankfully there is an answer, and &lt;code&gt;huwcampbell.com&lt;/code&gt; &lt;a href=&quot;https://bsky.app/profile/huwcampbell.com/post/3mc2z6rbmv22d&quot; target=&quot;_blank&quot;&gt;helped me out&lt;/a&gt;. It turns out that &lt;a href=&quot;https://atproto.com/guides/glossary#relay&quot; target=&quot;_blank&quot;&gt;ATProto Relays&lt;/a&gt; help gather data from any PDS that makes a request to be scraped, and most PDS implementations make this request by default to &lt;code&gt;bsky.network&lt;/code&gt;. Then you can use a indexing tool called &lt;a href=&quot;https://github.com/bluesky-social/indigo/tree/main/cmd/tap&quot; target=&quot;_blank&quot;&gt;Tap&lt;/a&gt; to not only listen but backfill data for any given collection.&lt;/p&gt;
&lt;p&gt;We got a step closer to making comments happen. The one small issue I faced was the fact that Tap would only store minimal metadata in its DB regarding the records, primarily the URI. While I could fetch the URI and discover which post the comment belonged to, I didn’t like that data flow. Instead I just forked Tap, made some minimal changes, and made it also record the document URI that the comment was tied to. Then I added a simple API endpoint that could be used to fetch this data from the DB. Took a while to hook it all up, but with my site’s API and my tap instance running, we had comments working.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/comments-example.png&quot; alt=&quot;comments example&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At this point I started to question the value of all of this given the amount of work it took to get what I wanted, but in the end we had something special. Within a week, my site had actually turned ATProto into a CMS. I could make posts, update them, or delete them, and all the while these updates are broadcasted to a network that anyone could index. It was like an RSS feed that because it met a standard could be aggregated with a single tool. When I sign into my site using my own PDS and make posts, it’s totally independent. I have total control over everything. The hype of Standard.site was starting to click.&lt;/p&gt;
&lt;p&gt;Of course this idea does face its own special challenges. Standard.site lexicons will only work if they truly become the standard (clever domain by the way). If there are other competing standards that start to also gain traction then you will have issues getting everyone on the same page. With that said I think Standard.site stands a good chance: it’s well thought out, simple, and people already love it. Even if more competition arises, people will generally go with whatever is the most popular and give them the most distribution. Even still, there’s nothing stopping anyone from taking the same piece of content and creating multiple records to meet multiple standards to get as much attention as possible.&lt;/p&gt;
&lt;p&gt;The other challenge I can see is the Bluesky relay. You can run other relays and have your PDS make a request to them for scraping, but the reality is &lt;code&gt;bluesky.network&lt;/code&gt; is the most popular and comes predefined if you run the main PDS implementation. With that said I think it’s still early and not a huge concern right now. In truth we have a lot to thank from Bluesky with their work on ATProto and the promises it brings. If you’re interested in any of the code mentioned in this post, it’s all open source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/stevedylandev/stevedylan.dev&quot; target=&quot;_blank&quot;&gt;Personal Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/stevedylandev/indigo/tree/main/cmd/tap&quot; target=&quot;_blank&quot;&gt;Tap Fork&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also plan to include a link to this post as an update on my &lt;a href=&quot;/now&quot;&gt;/now&lt;/a&gt; page if you want to check out the implementation and leave a comment! While my main blog doesn’t have comments I did write a script that will now create documents on ATProto so they should be indexed as well.&lt;/p&gt;
&lt;p&gt;Wrapping up, this experience has been a great culmination of everything I’ve been prioritizing with the web, that being content publishing and sharing that content through open channels like RSS. Standard.site opens up a whole new world of possibilities for what this could look like in the near future, and takes us a step closer to a truly open web that I long to see.&lt;/p&gt;</content:encoded></item><item><title>The Meaning of Life</title><link>https://stevedylan.dev/posts/the-meaning-of-life/</link><guid isPermaLink="true">https://stevedylan.dev/posts/the-meaning-of-life/</guid><description>42? Sorta, but not exactly</description><pubDate>Fri, 15 Aug 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/42.BKWYK60v_ZV51mB.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;630&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Even if you haven’t read the book or seen the movie, you are likely familiar with the famous yet humorous answer to the meaning of life: 42. Just a number. The original author didn’t intend any deeper meaning and stresses that it means nothing. Ironically I think like everything in life, it holds an ounce of truth beyond what we see in it.&lt;/p&gt;
&lt;p&gt;People have searched far and wide to answer the question of why we’re here, or more personally what their purpose in life is. It’s something we either don’t like to talk about because we feel insecure about ourselves, or it’s something we boast about with great confidence due to our abilities. In my opinion the truth is neither, in that the meaning of your life is not within your own skills and abilities.&lt;/p&gt;
&lt;p&gt;In a 2020 Disney movie called Soul, the main character is convinced their life purpose is to play jazz piano. Through a series of events he slowly realizes that while jazz is his “spark” and perhaps his favorite part of being alive, it’s not his purpose. We all eventually die one day and generally history will not remember us at all, not even our distant descendants. We get the idea that our life needs some grand purpose because we see others in history being recognized for their achievements.. What we miss is the context. We only see from afar what their life looked like and then project that definition of purpose upon ourselves.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong, I think you should still have a purpose driven life. It’s good to have a desire and passion to do great things in the world; it’s in our nature. What I am saying is don’t confuse it with the meaning of your life. In an instant, your ability to chase goals or pursuits can be taken away. For those who have experienced it, the feeling of having no purpose is real.&lt;/p&gt;
&lt;p&gt;So what is the meaning of life? Well, I would be foolish to think I know the true answer, but I will tell you what it means for me now.&lt;/p&gt;
&lt;p&gt;The meaning of life, is to live.&lt;/p&gt;
&lt;p&gt;A pivotal scene in Soul is when he looks back through all the things he experienced in his life that were not playing jazz piano, and how they gave him life:&lt;/p&gt;
&lt;p&gt;Riding a bike in the summer&lt;/p&gt;
&lt;p&gt;Listening to music with his father&lt;/p&gt;
&lt;p&gt;Eating the best slice of pizza&lt;/p&gt;
&lt;p&gt;Teaching a kid to play the drums&lt;/p&gt;
&lt;p&gt;Eating a humble yet amazing slice of pecan pie&lt;/p&gt;
&lt;p&gt;Feet in the sand on a sunset beach&lt;/p&gt;
&lt;p&gt;Sure, I have a passion for trying to build a more safe and more free version of the internet, but I also love my kids, my wife, the sound of a stream in the woods, the taste of Italian wine, the feeling of a cool breeze, and the sight of a cloudy hill in a far away country.&lt;/p&gt;
&lt;p&gt;Life is so much more than what you might be passionate about, and if you’re not careful, you’ll get lost. So next time you find yourself taking a beating over finding meaning and purpose in life, think of the simple things that make you alive. Don’t lose site of what’s right in front of you.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are these two young fish swimming along and they happen to meet an older fish swimming the other way, who nods at them and says “Morning, boys. How’s the water?” And the two young fish swim on for a bit, and then eventually one of them looks over at the other and goes “What the hell is water?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;42&lt;/p&gt;</content:encoded></item><item><title>Turning Solidity NatSpec into Interactive Markdown UI</title><link>https://stevedylan.dev/posts/turning-natspec-into-markdown-ui/</link><guid isPermaLink="true">https://stevedylan.dev/posts/turning-natspec-into-markdown-ui/</guid><description>An exploration on how NatSpec could be used to not only maintain context but provide user interfaces</description><pubDate>Sun, 31 Aug 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/natspec-contract.BZiR10tF_ZoVRbW.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;2122&quot; height=&quot;1194&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of the most common problems encountered when building decentralized applications is the disconnect between the smart contract and the client. A normal flow might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solidity -&amp;gt; byte code &amp;amp; abi -&amp;gt; EVM &amp;lt;- byte code &amp;lt;- abi &amp;lt;- client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to the ABI (Application Binary Interface) that is generated at compile time we have instructions we can use in clients to interact with smart contracts. It’s been an essential piece for years as we’ve built apps that interact with smart contracts. More recently at OpenZeppelin we released an open source tool called the &lt;a href=&quot;https://builder.openzeppelin.com&quot; target=&quot;_blank&quot;&gt;Contracts UI Builder&lt;/a&gt; which makes it even easier to build UI forms for contracts. Despite how useful ABI has been, it does miss one important piece: context. An ABI field might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;function&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;setNumber&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;inputs&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;newNumber&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;uint256&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;internalType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;uint256&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;outputs&quot;&lt;/span&gt;&lt;span&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;stateMutability&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;nonpayable&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All we know about this function is it probably sets a new number, but why? What is the number for? We might be able to answer these questions by looking at the source code of the contract itself, but there are many cases where we have no idea what a paramter is used for. This is something that &lt;a href=&quot;https://farcaster.xyz/seb/0xf5694e6e&quot; target=&quot;_blank&quot;&gt;Seb brought up&lt;/a&gt; on Farcaster this weekend, stating “There should be some sort of universal markup language for every smart contract (that isn’t an ABI) that allows anyone to easily interact onchain.”&lt;/p&gt;
&lt;p&gt;This got me wondering if the NatSpec could be used to help solve this problem. If you’re not familiar with it, the &lt;a href=&quot;https://docs.soliditylang.org/en/latest/natspec-format.html&quot; target=&quot;_blank&quot;&gt;NatSpec&lt;/a&gt; works a lot like JSDoc where the developer can leave comments in a particular format that can be used by the compiler to create documentation or even SDKs and CLIs. It’s been in Solidity for years and has actually been used by OpenZeppelin’s documentation to generate API references. While most of the tags handle things like parameters or returns, the &lt;code&gt;@notice&lt;/code&gt; tag can be used as a general description and be filled with whatever we want to write, so why not markdown? It doesn’t stop there though. What if we could build entire UIs out of the NatSpec? Thanks to a new library / proposed standard called &lt;a href=&quot;https://markdown-ui.com/&quot; target=&quot;_blank&quot;&gt;Markdown UI&lt;/a&gt; I was able to build a &lt;a href=&quot;https://natspec-ui.orbiter.website&quot; target=&quot;_blank&quot;&gt;MVP&lt;/a&gt; of this idea, and in this post I’ll show you how it works!&lt;/p&gt;
&lt;p&gt;The first thing you need to do is write up the markdown as NatSpec in the smart contract.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// SPDX-License-Identifier: MIT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pragma solidity ^0.8.20;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/// @title Counter&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/// @notice A simple counter contract that allows incrementing and setting a number&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/// @dev This contract maintains a single uint256 state variable that can be modified&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;contract Counter {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// @notice The current counter value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// @dev Public state variable automatically generates a getter function&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uint256 public number;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// @notice Sets the counter to a specific value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// @dev Updates the number state variable to the provided value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// @param newNumber The new value to set the counter to \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ```markdown-ui-widget \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// { &quot;type&quot;: &quot;form&quot;, &quot;id&quot;: &quot;setNumber&quot;, &quot;submitLabel&quot;: &quot;Set Number&quot;, &quot;fields&quot;: [{ &quot;type&quot;: &quot;text-input&quot;, &quot;id&quot;: &quot;newValue&quot;, &quot;label&quot;: &quot;New Counter Value&quot;, &quot;placeholder&quot;: &quot;Enter number&quot;, &quot;default&quot;: &quot;42&quot; }] } \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ``` \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    function setNumber(uint256 newNumber) public {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        number = newNumber;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// @notice Increments the counter by 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// @dev Increases the number state variable by 1 using the increment operator \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ```markdown-ui-widget \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// { &quot;type&quot;: &quot;form&quot;, &quot;id&quot;: &quot;increment&quot;, &quot;submitLabel&quot;: &quot;Increment&quot;, &quot;fields&quot;: [] } \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ```\n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    function increment() public {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        number++;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might have noticed our one small twist: the Markdown UI component.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;`markdown-ui-widget&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{ &quot;type&quot;: &quot;form&quot;, &quot;id&quot;: &quot;increment&quot;, &quot;submitLabel&quot;: &quot;Increment&quot;, &quot;fields&quot;: [] }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what we can use in our front end to build interactive components along side the markdown describing how it works! When we compile this contract it’s going to include a json file with out generated &lt;code&gt;userdoc&lt;/code&gt; and &lt;code&gt;devdoc&lt;/code&gt;. In order to make it easier to share these files along with the ABI, we can verify the contract with &lt;a href=&quot;https://sourcify.dev/&quot; target=&quot;_blank&quot;&gt;Sourcify&lt;/a&gt; which will store our contract metadata for anyone to fetch via an API. That API response looks something like this when we use the query &lt;code&gt;?fields=devdoc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;devdoc&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;kind&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;dev&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Counter&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;This contract maintains a single uint256 state variable that can be modified&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;methods&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;increment()&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Increases the number state variable by 1 using the increment operator &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n ```markdown-ui-widget &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n { &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;form&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;increment&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;submitLabel&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Increment&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: [] } &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n ```&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;setNumber(uint256)&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;params&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;newNumber&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;The new value to set the counter to &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n  ```markdown-ui-widget &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n { &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;form&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;setNumber&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;submitLabel&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Set Number&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: [{ &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;text-input&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;newValue&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;New Counter Value&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;placeholder&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Enter number&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt; }] } &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n ``` &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Updates the number state variable to the provided value&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;stateVariables&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;number&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Public state variable automatically generates a getter function&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;matchId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;8931344&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;creationMatch&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;exact_match&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;runtimeMatch&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;exact_match&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;verifiedAt&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2025-08-31T16:03:47Z&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;match&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;exact_match&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;chainId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;11155111&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;address&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0xEeF9B4a84C3327860CD14E1E066D7D6762b9bC3F&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see we’re able to get all of the markdown we put in earlier. Now all we have to do is create a frontend client that can render it all!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;MarkdownUI&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@markdown-ui/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Marked&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;marked&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;markedUiExtension&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@markdown-ui/marked-ext&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;&quot;@markdown-ui/react/widgets.css&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  parseContractToMarkdown&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type &lt;/span&gt;&lt;span&gt;ContractResponse&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} from &lt;/span&gt;&lt;span&gt;&quot;./utils/contractParser&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;marked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Marked&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;markedUiExtension&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;CONTRACT_ADDRESS&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;0xEeF9B4a84C3327860CD14E1E066D7D6762b9bC3F&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;CHAIN_ID&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;11155111&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// Sepolia&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const [&lt;/span&gt;&lt;span&gt;contractHtml&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setContractHtml&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const [&lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const [&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setError&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;fetchContractData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          `https://sourcify.dev/server/v2/contract/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;CHAIN_ID&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;CONTRACT_ADDRESS&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?fields=devdoc`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          throw new &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`HTTP error! status: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ContractResponse&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;markdownContent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseContractToMarkdown&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;markdownContent&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;marked&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          markdownContent&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; &quot;# No markdown widgets found&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setContractHtml&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } catch (&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error fetching contract data:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt; instanceof &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Unknown error&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } finally {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchContractData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if (&lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Loading&lt;/span&gt;&lt;span&gt; contract&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text-red-500&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;MarkdownUI&lt;/span&gt;&lt;span&gt; html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;contractHtml&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a result we get a nice page that not only has markdown formatting but interactive UI components that are built in thanks to Markdown UI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/natspec-markdown-ui-2.Dvo1kpLl_Z1bBjSX.webp&quot; alt=&quot;natspec demo&quot; loading=&quot;lazy&quot; width=&quot;1460&quot; height=&quot;1954&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I took a little extra time to add in Wagmi to the app which resulted in a fully interactive contract, which you can check out &lt;a href=&quot;https://natspec-ui.orbiter.website&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;. In a sense we achievied the goal of a unified standard markup that can make user interactions with contracts easier. Of course we have to keep in mind the limitations here, primarily being it would require developers to make sure they include all of this markup in their contract and the Markdown UI standard isn’t even out of a beta stage, and for that reason I would highly recommend a professional solution like the Contracts UI Builder. Nevertheless it’s fun to see how extensible and open Markdown and Solidity have come in the past few years. Each day we’re getting closer to an internet that is not only safe, but user friendly as well.&lt;/p&gt;
&lt;p&gt;As always the code for this small project is open source and can be found &lt;a href=&quot;https://github.com/stevedylandev/natspec-markdown-ui&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title>ATProto, POSSE, and Personal Sites</title><link>https://stevedylan.dev/posts/using-atproto-for-posse/</link><guid isPermaLink="true">https://stevedylan.dev/posts/using-atproto-for-posse/</guid><description>My little weekend experiment to bring micro updates to my personal site</description><pubDate>Mon, 05 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/atproto.png&quot; alt=&quot;atproto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Last year after reading &lt;a href=&quot;https://herman.bearblog.dev/slow-social-media/&quot; target=&quot;_blank&quot;&gt;a post by Herman&lt;/a&gt; I realized I didn’t need social media; I could just be blogging. Thus started a new journey into &lt;a href=&quot;https://bearblog.stevedylan.dev/resurrect-the-old-web/&quot; target=&quot;_blank&quot;&gt;feeds&lt;/a&gt;, RSS, and deleting the majority of my social media accounts. I started by using &lt;a href=&quot;https://bearblog.dev&quot; target=&quot;_blank&quot;&gt;Bear Blog&lt;/a&gt; as a “side blog”, a place to post more casual thoughts or updates in my life. This approach has honestly been a great success in my opinion as I’ve been able to connect with so many wonderful people. Lately, however, I’ve been thinking more about &lt;a href=&quot;https://henry.codes/writing/a-website-to-destroy-all-websites/&quot; target=&quot;_blank&quot;&gt;personal sites&lt;/a&gt;, &lt;a href=&quot;https://indieweb.org/POSSE&quot; target=&quot;_blank&quot;&gt;POSSE&lt;/a&gt;(publish on your own site syndicate elsewhere), and how I could better adapt this concept of a side blog. While I did have my bear blog on a subdomain of my primary domain, I liked the idea of bringing it all back to my personal site and investing in it more.&lt;/p&gt;
&lt;p&gt;These ideas got me thinking about building a simple API + DB that would create, list, update, or delete small posts for my site. Not long after sketching out what this might look like, I realized it looked awful lot like a Personal Data Server (PDS) from &lt;a href=&quot;https://atproto.com/&quot; target=&quot;_blank&quot;&gt;ATProto&lt;/a&gt;. I got on Bluesky not long after it launched during the invite code frenzy, and I used it on and off but it didn’t stick much. I never looked too much into the protocol behind it, but my curiosity got the best of me. Within a couple of hours I already had my own PDS hosted and migrated my existing account over.&lt;/p&gt;
&lt;p&gt;In the process of setting up the PDS, I had a realization that it might not be possible or a &lt;a href=&quot;https://atproto.com/guides/going-to-production#domain-names&quot; target=&quot;_blank&quot;&gt;good idea&lt;/a&gt; to use my primary domain for the instance. This is when I started to question if I could truly implement POSSE with ATProto. On one hand the approach of using a self hosted PDS checks several boxes like ownership, reduce third party dependencies, etc. The primary missing piece is canonical URLs. If I make a post to my PDS through a client like Bluesky, there isn’t a direct URL that points back to my domain. At best it could maybe point to it’s location on my PDS. Due to this I’m sure it doesn’t count as true POSSE, but it did give me some ideas of how I could get pretty close.&lt;/p&gt;
&lt;p&gt;What I ended up doing is implementing a &lt;a href=&quot;/now&quot;&gt;now page&lt;/a&gt; for my site. This includes some general updates or bullets on what’s happening in my life right now, but it also includes a feed of posts coming directly from my PDS. I love this setup because it means I can let people view these updates without asking them to sign up for Bluesky and follow me; they can just visit my site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/now-updates.png&quot; alt=&quot;now page&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Naturally I turned this feed of posts into an RSS feed. This introduced another piece of the puzzle: a place to view individual posts. The RSS feed needs a canonical URL, and instead of using a Bluesky URL, I could setup my own! The URL is a bit ugly since I want to keep my site static, but I setup a page on my site that loads posts client side via a query param, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://stevedylan.dev/pds?rkey=3mbjwj62kak2u&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The page includes a share button that copies the link to the current page to the viewer’s clipboard. If we step back and consider what we have now, I think you could argue that it is a close second to POSSE. Someone can visit my &lt;code&gt;/now&lt;/code&gt; page, view a post, maybe subscribe to the RSS feed, and all of the URLs in that feed point to my site. Everything happens on a protocol layer, and yeah sure you could go on Bluesky and see all these posts, but it also enables a flow where anyone can view my updates with ease and build links pointing back to my site.&lt;/p&gt;
&lt;p&gt;While my PDS doesn’t share my personal domain, I still have my handle setup to be my domain. This is probably one of the best features behind ATProto because even if your content isn’t self hosted, the content follows your domain. I will admit that you could easily do this with an account hosted on the default &lt;code&gt;bsky.social&lt;/code&gt; PDS, but I love the fact that this is data I own and I have full control over it.&lt;/p&gt;
&lt;p&gt;By all means this little experiment and implementation is not perfected or complete, and that’s because it’s my personal site. I don’t think any personal site can be truly “complete;” it should be an ever evolving garden that you tend with care. Adding these small pieces is just me getting my hands dirty after a long period of neglect, and most important of all, I had fun.&lt;/p&gt;</content:encoded></item><item><title>Vibe Coding and Kodak Cameras</title><link>https://stevedylan.dev/posts/vibe-coding-and-kodak-cameras/</link><guid isPermaLink="true">https://stevedylan.dev/posts/vibe-coding-and-kodak-cameras/</guid><description>A perspective on the rise of AI coding and how it relates to technological shifts throughout history</description><pubDate>Sun, 30 Mar 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/vibe-coding-kodak.CQOBHr6X_Z1n3Naf.webp&quot; alt=&quot;cover&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;630&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I’m sure many who read this are familiar by now with the term “vibe coding,” a euphoric style of programming where you prompt AI models or IDEs to write software and just “vibe.” One of the more popular instances that made the practice takeoff was the indie hacker Levelsio building a flight simulator entirely in JavaScript and selling ad space within the game. Others have followed suit and even started businesses by vibe coding them into existence. It’s hard to deny the reality that AI has been changing much of the software ecosystem, and with any major shift in technology there are lots of opinions. I certainly have my own, but I thought it would be more productive to look at history repeat itself.&lt;/p&gt;
&lt;h2&gt;Evolution of Photography&lt;/h2&gt;
&lt;p&gt;Most people don’t know it, but there was a major controversy in the world of photography in the year 1900. Before that time photography was an art form protected by its sages, who poured their money, time, and practice into it. Not anyone could just take a picture, only those who had worked in the craft and mastered it. All of that changed in February of 1900 when Kodak released the Brownie camera. It wasn’t much to look at, a little cardboard box that could take a picture no bigger than 2.25 inches using 117mm film. What made it special was the service behind it. The Brownie only cost $1 (which would be $38 at the time of this post), and it included the cost to develop the film. All someone had to do was take a picture, send it off to Kodak, and they would return the picture. “You press the button—we do the rest.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/kodak-brownie-ad.DsGD0LkR_Z1t2Jr3.webp&quot; alt=&quot;bronwie ad&quot; loading=&quot;lazy&quot; width=&quot;1383&quot; height=&quot;2048&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Suddenly anyone could take photos: parents, grandparents, kids, truly anyone. It sold like crazy, and it upset the old photographer guild. There was great concern that there would now be a huge amount of “slop” photography and the art of photography would be washed away. Surely no one who took photos in such a way could be an artist… right? These artists were also concerned for their profession. If everyone had a camera, why would families pay for a photography session?&lt;/p&gt;
&lt;p&gt;Fast forward to the digital age of cellphones and photography once again accelerated into the unknown. Now you don’t have to even own a camera. Almost every person on earth has a cellphone, and the chances are that cellphone has a camera. In this evolution a photo could be taken at any time, not just when you remembered to bring a camera. Around the same time we saw storage become dirt cheap, and now there’s no limit to how many photos you can take. The artists and professionals once again voiced their concern of the art form being lost in the sea of latte art and dog photos.&lt;/p&gt;
&lt;h2&gt;Evolution of Programming and Computers&lt;/h2&gt;
&lt;p&gt;Programming and computers seem to have a similar story, where as technology advances, the wider it’s user base becomes. For hardware it was the home computer that made it possible for anyone to have access to one. The World Wide Web in its infancy was only accessible by universities or libraries, but soon grew to be used by everyone. Content creation on the web used to mean writing HTML and running a server, but thanks to the movement of social media, APIs, and hosted web apps, people could write and express freely. An explosion of thoughts, ideas, and memes filling thousands of servers, racing at unbelievable speeds.&lt;/p&gt;
&lt;p&gt;Pivoting to programming languages, these too have seen lots of technological advancements. In the beginning it was feeding paper into a machine the size of a large room, which slowly evolved into languages that could be typed directly into computers. Low level communication through assembly to modern stacks like C, eventually becoming more and more abstract from the bare metal we used to speak to. Now we use languages like Javascript and package management systems that allow you install everything, and many of us cringe at the thought of it being used in backends. Last but not least, we now have vibe coding which many suggest means we don’t need to learn how to code. Instead we’re reaching the longed for era of end user programming.&lt;/p&gt;
&lt;p&gt;As with any technological advance, I believe the technology itself to be neutral, but in the hands of people, we see the good, the bad, and the ugly.&lt;/p&gt;
&lt;h2&gt;The Good&lt;/h2&gt;
&lt;p&gt;The evolution of photography technology enabled plenty of bad photos and photographers, but it also created a whole new series of artists we would not have otherwise. One of my favorite examples of this is Vivian Maier. If you’re not familiar, Vivian Maier was an unknown nanny in the 1940’s and 50’s. It wasn’t until after her death that her life’s work as a photographer was discovered by a man who won it at an auction. To his surprise it was a stunning collection, hundreds of thousands of them, all taken by a nanny no one had ever heard of. She was passionate about photography, and her perspectives of the world at that time were unique.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/vivian-maier.DkTrkJ11_1LeUjI.webp&quot; alt=&quot;vivian maier&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; /&gt;&lt;/p&gt;
&lt;p&gt;They’re made possible thanks to the much later successor of a Brownie style camera that she could take everywhere and shoot roll after roll of film. If photography was still stuck in the dark ages of carrying around big pieces of equipment that only certain people could afford, we wouldn’t have the stunning work of Vivian Maier.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/vivian-maier-pigeons.DfrRQjge_nps4k.webp&quot; alt=&quot;photo by vivian maier&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;1100&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Another unrecognized photographer is one of my favorites, Joe Greer. Greer started his career on an app: Instagram. He didn’t take photography lessons, he didn’t have a nice camera, he just had his phone. The more and more he shot with his phone and posted his photos on Instagram, the more people liked them and the bigger it got. Eventually he did switch to professional cameras and continued his craft, but the key was his access to an art form that otherwise wouldn’t be available apart from cell phone cameras.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/joe-greer-image.BTmyD95w_1fhsar.webp&quot; alt=&quot;joe greer photo&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;1398&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the realm of programming, even in the evolution of languages, we see a similar pattern where mediocrity increases but so does the number of discovered programmers. Sure there’s a lot of Javascript slop out there, but thanks to Javascript there have been more and more people discovering programming and starting a wonderful journey. You don’t have to start in a lower language to find the joy of programming, and most people who do find it will experiment in many different languages.&lt;/p&gt;
&lt;p&gt;Even in vibe coding I believe we’ll see people who were not programmers before, but discover the beauty of watching code they wrote achieve a goal they had in mind. While end user programming has it’s negatives, the positives are that people are solving their own every day problems. Those who go beyond solving a problem might stumble into a new passion they can pour hours of learning into.&lt;/p&gt;
&lt;h2&gt;The Bad, and Perhaps the Ugly&lt;/h2&gt;
&lt;p&gt;Of course it’s not all sunshine and roses, and I truly believe we face some challenges ahead with the consequences of vibe coding. As with any technological advance things become easier, and when a challenge is taken away, there is less for us to learn. The old way of programming which involved hours of getting stuck, forcing you to read documentation, researching answers, asking wiser people, all of that slowly fades for the easy way out. Sure you solved your problem, but what did you learn? How much that question matters might depend on who you are: a software engineer who needs to hone their craft, or just some Joe on the street that wanted a fun app to play with.&lt;/p&gt;
&lt;p&gt;Even for the everyday man that uses AI to build software they can miss out on the side effects of programming, which include building problem solving skills, critical thinking, and finding ways to think outside the box. In some ways I think this is where vibe coding doesn’t necessarily achieve end user programming, because with true end user programming we have people who learn how to code in the same way they learn to read and spell. It would be much more beneficial for society if people had these basic skills, but I’m not sure we’ll get that with AI and vibe coding.&lt;/p&gt;
&lt;p&gt;There is also the question of sensitive data and how much code controls our modern society. With our photography example the impact was mostly limited to the photography industry and artistic expression. It’s a different story with programming as we depend on code to handle our banking, our hospital records, our information and privacy, everything we do depends so much on the security of our code. Giving someone an AI enabled code editor is like giving Peter Parker supernatural spider abilities. With too much power and a dismissal of responsibility we face potentially terrible consequences. We’ve already seen this in some cases where a fresh vibe coder doesn’t practice basic API key protection and security and had their platform destroyed by the internet. Without proper training and knowing the “how” behind the code that’s running we face a new world of software that can be vulnerable.&lt;/p&gt;
&lt;p&gt;Lastly there is the slow decay of information. We all know public open source code is being fed into AI model training, and we can only hope that it’s good code. As more and more AI generated code is put online, the more AI models feed on it, and we face the reality of “Ouroboros,” a snake eating it’s own tail. Suddenly our all powerful AI coding assistants can’t solve the problems they once did, and they start to struggle with the basics. You as a professional developer might have known how to fix these issues before, but your mind has grown soft from your dependence on AI and now you too are stuck. This is what I am afraid of the most, and what I try to keep in mind when using AI.&lt;/p&gt;
&lt;h2&gt;What Do We Do?&lt;/h2&gt;
&lt;p&gt;Just as we have seen with any other advance of technology, we cannot stop it. Vibe coding is here and it likely isn’t going to disappear anytime soon. While we may not be able to stop the march of progress, we can change how we react to it. Being a snob won’t help the journey of someone discovering programming through vibe coding. You don’t have to like AI or vibe coding and you can still show kindness to a new generation of programmers. Instead of discouraging them, why not show interest in what they’re building and offer our help? If they are truly falling in love with programming, we have the opportunity to encourage them towards competence. Instead of just achieving their goals we can show them the beauty of knowing how they did it outside of “AI made it work.”&lt;/p&gt;
&lt;p&gt;It’s for this reason I disagree with &lt;a href=&quot;https://x.com/amasad/status/1905103640089825788&quot; target=&quot;_blank&quot;&gt;Amjad&lt;/a&gt;; I do think you should still learn how to code. Perhaps it would be helpful to qualify “who” I think should still learn how to code. I would love everyone to learn, but more realistically I think those who find joy in programming should learn how to code, especially if your career depends on it. If we ever do face the Ouroboros then we need champions of the old way to write code that withstands the test of time. We need more people like Alejandro, who &lt;a href=&quot;https://x.com/apr/status/1828914554115436661&quot; target=&quot;_blank&quot;&gt;wrote his own program to manage his checkbook in the 90’s and still uses it to this day&lt;/a&gt;. We’ll still be writing code in five years for the same reasons people take photos: we enjoy doing it.&lt;/p&gt;</content:encoded></item><item><title>When AI Gives the &apos;Ick&apos;</title><link>https://stevedylan.dev/posts/when-ai-gives-the-ick/</link><guid isPermaLink="true">https://stevedylan.dev/posts/when-ai-gives-the-ick/</guid><description>Ever look at something made by an AI company that gives you the &apos;ick&apos;?</description><pubDate>Fri, 03 Oct 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/friend-1.BwOrgLl9_puPKL.webp&quot; alt=&quot;friend-ads&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;1536&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A few days ago OpenAI released Sora, which was not only the latest version of their video generation model, but also a social app. Humans sign up, but the only content is that generated by users through AI. If you’re like me, the very thought of an AI-only generated feed might have given you the “ick.” It’s not the first time either. The debut of Friend (an AI powered “friend” app / pendant you wear around your neck) got even more backlash recently with their NYC subway ads.&lt;/p&gt;
&lt;p&gt;Needless to say, people didn’t like it.&lt;/p&gt;
&lt;div&gt; &lt;div&gt; &lt;div&gt; &lt;img src=&quot;/_astro/friend-2.HIrP9Nau_FORlr.webp&quot; alt=&quot;Image 1&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; /&gt; &lt;/div&gt;&lt;div&gt; &lt;img src=&quot;/_astro/friend-3.DNXx70uL_2nyeFx.webp&quot; alt=&quot;Image 2&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;1574&quot; /&gt; &lt;/div&gt;&lt;div&gt; &lt;img src=&quot;/_astro/friend-4.Dpe1klT4_Z1It8dq.webp&quot; alt=&quot;Image 3&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;2048&quot; /&gt; &lt;/div&gt;&lt;div&gt; &lt;img src=&quot;/_astro/friend-5.Bgw3_v_v_Z1mcii5.webp&quot; alt=&quot;Image 4&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;2048&quot; /&gt; &lt;/div&gt; &lt;/div&gt; &lt;div&gt; &lt;img alt=&quot;&quot; /&gt; &lt;/div&gt; &lt;/div&gt;  
&lt;p&gt;It’s these kinds of AI innovations that give us the “ick,” but why? There’s plenty of AI tools we use all the time without much thought, but sometimes we get the feeling that the tech goes “too far.” To understand why, we need to step back and ask a few more questions.&lt;/p&gt;
&lt;h2&gt;Why did we build the web?&lt;/h2&gt;
&lt;p&gt;The answer to this one is quite simple: communication. We wanted to build infrastructure that would allow people to communicate with other people. It was publishing web pages, Email, IRC, AIM, Blogs, and eventually social media in the form of MySpace or Facebook. It was not only a place where people could keep up with those they already knew, but also a place to meet new people. It was a place to explore, to wonder, and to discover. Even back then ads helped keep the lights on, but something shifted with the release of the endless Feed.&lt;/p&gt;
&lt;p&gt;It was a revolutionary UX concept that allowed someone to keep scrolling. At first that feed might have been people you follow, but what if you ran out of posts? Thanks to algorithms and using people’s data, people could scroll and find new content that was calculated to be engaging. Bit by bit, tech giants found the value of keeping people on their screens, collecting the data they provided by stopping the feed, clicking on something, or sharing it. We now live in a world where social feeds are addictive sources of dopamine that make minds sick. We became the product.&lt;/p&gt;
&lt;p&gt;The web we built to communicate, discuss, and connect with other people seems much more distant and harder to find. Our programmatic feeds make sure we see the content we agree with, and for the content we don’t agree with, it’s in the worst place possible to have a civil discussion with proper context and humility. Thanks to the recent release of AI content feeds, perhaps we don’t need people at all. That’s one reason we start to feel the ick, because it’s unnatural for us to be in isolation. Our need for connection plays into the next question.&lt;/p&gt;
&lt;h2&gt;Why do we create?&lt;/h2&gt;
&lt;p&gt;I have to clarify that I’m not anti-AI. I use it all the time for getting helpful information or learning how to do something, but also the idea of using it to create certain pieces of my life gives me the ick. I recently found &lt;a href=&quot;https://files.stevedylan.dev/ai-love-poems.mp4&quot; target=&quot;_blank&quot;&gt;this video&lt;/a&gt; where a panel of people were addressing some of the issues with AI, and a person stated there are plenty of moments in their lives where they would choose to do it themselves even if AI could do it better. One example they gave was a love poem. The whole point of writing a love poem is that YOU wrote it, that YOU enjoyed the process and benefited from it. At the end of the video they say, “I think if we like living then we have the right to live even if we’re not good at it.”&lt;/p&gt;
&lt;p&gt;The truth is that people live to create, to explore, to feel. It’s not always about productivity. It’s the arts and our connection with other people that make us human. We read, write, think, discuss, debate, and problem solve for the experience of improving ourselves and our society. In some ways it’s antithetical to how tech is built today, where everything is about consumption. Everything is designed to make us the consumer. We sit on our phones and scroll through endless video feeds and provide little in return. It’s not who we’re supposed to be as humans. We should pursue the hard work of creating and thinking instead of being passive and consuming.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We don’t read and write poetry because it’s cute. We read and write poetry because we are members of the human race. And the human race is filled with passion. And medicine, law, business, engineering, these are noble pursuits and necessary to sustain life. But poetry, beauty, romance, love, these are what we stay alive for. To quote from Whitman, ‘O me! O life!… of the questions of these recurring; of the endless trains of the faithless… of cities filled with the foolish; what good amid these, O me, O life?’ Answer. That you are here - that life exists, and identity; that the powerful play goes on and you may contribute a verse. What will your verse be?
― N.H. Kleinbaum, Dead Poets Society&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What is at risk?&lt;/h2&gt;
&lt;p&gt;I know some will argue that there is still creativity and creation happening when using an app like Sora, but is that truly the case? Sure you have to prompt an idea, but does that count as creating? While this might be an area up to debate, I would argue that AI prompting hardly counts as creating in the traditional sense. There is a direct correlation between how much credit you can take for a piece of work based on how much work and influence you had in the process. If creativity is a spectrum, where on that spectrum did you contribute? How hard did you work for it? Does it channel your emotions in a way that causes others to think more deeply about the topic? Does it evoke emotions out of others? How did you feel or benefit from creating it? Can you truly call it “your work”? Of course I admit that sometimes it’s pure fun to use stuff like Sora. It’s not serious and not meant to be serious. That’s the current state, but have you checked Facebook lately? The danger is what Sora introduces.&lt;/p&gt;
&lt;p&gt;As humans create, we gain humanity. The process, the brainpower, the emotion, the connection with others, all these culminate into the very things that make us human. If we spend our time doing “creative” acts that don’t build these principles, then we risk losing our humanity.&lt;/p&gt;
&lt;h2&gt;What can we do?&lt;/h2&gt;
&lt;p&gt;The first thing we can do is recognize the problem: social media needs reform. Not necessarily policy reform, but how we approach it in our every day lives. Social media is engineered addiction where we offer our most precious resource: time. Every social platform is after your attention and keeping it as much as possible. Many have tried to combat this problem with apps that block your access to certain apps, but I believe that only taps the surface of the issue. Much like the dieting craze of the ’90s, eating less food doesn’t solve your health issues. You have to substitute junk food for real food and exercise. I’m not gonna say you can’t use AI tools or have fun; AI generated content can be fun! I also enjoy the occasional fast food. The issue is when it’s all you’re consuming, and you’re not substituting or getting enough real food. Endless feeds make this difficult, but not impossible. Even in my own experience I am feeling the pull of social media feeds less and less as I invest more time in “real food.”&lt;/p&gt;
&lt;p&gt;On the note of “real food,” I’m personally exploring alternative methods of how we might engage in social interactions online. My favorite so far is &lt;a href=&quot;https://blogfeeds.net&quot; target=&quot;_blank&quot;&gt;Blog Feeds&lt;/a&gt;. Rather than an endless feed of things to read, it’s just a short list of people you actually care about and accessing what they write on their blogs. It’s autonomous, not depending on any large tech corporation that’s after your time. It’s just writing on a blog like this one, subscribing to other people’s blogs, and sharing a list of the people you follow. That’s it. Blog Feeds take us back to why we built the web: communication. With this approach we can finally get back to exchanging ideas, seeing people as people, and finding common ground. Is it harder than prompting Sora or scrolling TikTok? Yes. Is the barrier high? Nope. It’s relatively simple and just about anyone can participate. It just requires being a human, and using your mind in ways that we’re slowly losing.&lt;/p&gt;
&lt;p&gt;In order to regain our humanity, we must change how we engage with the web, and how we connect with other people. Otherwise our minds will end up looking no different than the slop on Sora that gives us the ick.&lt;/p&gt;</content:encoded></item><item><title>Why I Learned Vim</title><link>https://stevedylan.dev/posts/why-i-learned-vim/</link><guid isPermaLink="true">https://stevedylan.dev/posts/why-i-learned-vim/</guid><description>A brief look at my history and how ordinary jobs lead to learning programming and Vim/Neovim</description><pubDate>Fri, 05 Jan 2024 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/alacritty.CNVMzApC_Z19oPQI.webp&quot; alt=&quot;image&quot; loading=&quot;lazy&quot; width=&quot;4498&quot; height=&quot;2590&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Something I see pretty consistently when the topic of Vim/Neovim comes up is the inevitable question “Why?” Thanks to years of memes about not being able to quit Vim, it has gained a cult popularity of being “elite,” “ancient,” or both. Most developers who are comfortable in their VSCode environment can’t imagine using something so unfriendly and perhaps ugly. So yeah, “why” is a fair question. I reflected on that question and I realized how my own life experience led me to learn Vim, and perhaps it would be helpful to share that story.&lt;/p&gt;
&lt;p&gt;To understand why I use Vim, we gotta go back to 2013. I just graduated college, and I needed a job. I got one at Bass Pro Shops as a footwear associate. It was a pretty standard retail job, I would fetch shoes from the back as people requested sizes, organize displays, and unload freight. Starting out, I was an “ok” employee, but thanks to my boss Chuck, that changed. Early on he would half joke “oh you’re finally done?” or “it’s about time.” It was strange combination of mocking and encouragement, hinting that I was not meeting my full potential. It spurred me to do better, to work smarter and faster. Chuck not only gave me a strong work ethic, he gave me the desire to be productive. Even in that footwear position I learned how I could be fast, accurate, and highly productive in ways that other people noticed.&lt;/p&gt;
&lt;p&gt;I worked up the ranks and eventually became a manager of the hunting department, doing stuff I never thought I would, like doing inventory stock requests and other computer-heavy tasks. I’m not sure about retail stores, but this one used an IBM system to handle their internal inventory and product info. It was the classic black screen with green text; all keyboard, no mouse. Here I learned to be fast thanks to keyboard shortcuts. I was able to apply what I had already learned, that fast and accurate resulted in productivity, to machines.&lt;/p&gt;
&lt;p&gt;Of course, my time in retail was limited. It’s a hard job to sustain and justify over a long period of time, and I wanted something more regular. I heard banking was a good transition from retail, so I got a position as a teller at a local bank. Just like retail, banks work with data. A lot of data. From day one I had to learn how to use their software to access all that data, and it was not the best experience at first. We used a web GUI nicknamed “white screen” where you had to do a whole bunch of clicking to navigate or get access to anything. It wasn’t the keyboard speed I was used to at my previous job. I found ways to get sort of fast, but nothing amazing.&lt;/p&gt;
&lt;p&gt;Then one day, a coworker showed me “black screen.” It was the exact same access to the data but through the familiar IBM style terminal UI, all driven by keyboard with no mouse! I was ecstatic, and quickly learned to master it. I was fast again, boosting productivity at incredible rates. I could pull up anything in a few key strokes faster than anyone. I loved it. Once again the hard work ethic and productivity paid off, and I climbed through the ranks of the bank and later became a manager in a back office support role. Through my speed and accuracy, I became the guy who knew a lot and was infamous for getting stuff done.&lt;/p&gt;
&lt;p&gt;Several years go by in that role and it started to take its toll on my mental health. It was 2020, my first son was just born, and while waiting in the car for my wife finish to a doctor’s appointment (no plus ones back then), I stumbled upon the opportunity of programming via YouTube. I wanted a career change, and programming sounded interesting. After just one weekend of deep diving into web development basics, I was hooked. This began my journey of becoming a self-taught developer, which you can read more about &lt;a href=&quot;https://stevedylan.dev/posts/my-developer-journey&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;. It was a rough but exciting time, as I would wake up early to learn and write code, go to work, help out my wife with our new son, then stay up late into the night to keep going.&lt;/p&gt;
&lt;p&gt;After a few months I was making pretty basic websites, using the ever popular editor VSCode. Even then I was prioritizing how I could be more productive by taking typing tests and working on improving my WPM. It was logical to see that being able to type faster would result in higher productivity in both writing code and just writing in general. However, it was around that time I saw a video about Vim from none other than &lt;a href=&quot;https://www.youtube.com/@ThePrimeagen&quot; target=&quot;_blank&quot;&gt;The Primeagen&lt;/a&gt;. Immediately I was blown away by the speed, and I knew I had to learn it. This was the next level of using the keyboard and special shortcuts to achieve the speeds I craved. I started with the classic Vim Tutor, slowly learning the keybindings and exploring the config options.&lt;/p&gt;
&lt;p&gt;That was about three years ago, and I had the advantage of learning Vim early on and it becoming a standard part of my workflow. Plus it was on my own pace; I was not programming professionally at the time so it’s not like I had to go through a period of not being able to push out code for a job. Nevertheless, I am so glad I took the time then to push through it. To this day Vim keybindings and Neovim have helped me become more productive and write code faster.&lt;/p&gt;
&lt;p&gt;Perhaps a question you might be asking yourself now is “should I learn Vim?” Honestly the answer might depend. I would say there needs to be an important distinction between Vim keybindings and Vim/Neovim itself. AKA “Vim Motions”, allow you to manipulate and “edit” text. You can write text in pretty much any application, but true editing lets you maneuver and mutate text in ways you never thought were possible. The keybindings are used in other applications including VSCode with the Vim extension, and in my opinion they are worth learning. You can at the very least toggle between them and practice them, and can help you if you ever find yourself on a server and need to edit a config file quickly.&lt;/p&gt;
&lt;p&gt;Neovim as a text editor is a different story, because at that point you are setting up your dev environment on a personal and custom level. The UI and features that you would see at startup in VSCode will likely not exist at all, and would require plugins to recreate pieces of functionality. Some might see this as a downside, however it gives you the opportunity to make something that works for you and won’t slow you down. Taking the reverse route in VSCode by removing UI elements is much more difficult in my opinion as I have tried it, and it just isn’t the same. Nothing beats a Neovim configuration specially built to handle the code you need to write.&lt;/p&gt;
&lt;p&gt;Is Vim worth learning? Perhaps a better question would be “how fast and productive do you want to be?” If you’re like me where you have an itch that can’t be scratched, always finding ways to automate, speed up, and maximize productivity, then you may want to consider trying it out. It does take time to learn, and it could initially hamper your productivity, but at least you gave it a shot. For others Vim is not the answer, and VSCode is actually what makes them more productive, but you never know until you try.&lt;/p&gt;
&lt;p&gt;My personal advice for those wanting to dip their toes into Vim would be using the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vscodevim.vim&quot; target=&quot;_blank&quot;&gt;Vim extension in VSCode&lt;/a&gt;. Try to use it as much as possible, grind through the pain, watch &lt;a href=&quot;https://youtube.com/playlist?list=PLm323Lc7iSW_wuxqmKx_xxNtJC_hJbQ7R&amp;amp;si=iaJI_NAzkQT-Kvnu&quot; target=&quot;_blank&quot;&gt;these videos&lt;/a&gt; to learn the keybindings and movements, and just push yourself to the max. It could take weeks or months to start feeling the results, but once you do, it’s hard to go back. When you feel pretty comfortable then you should try working in Neovim from the terminal and start building your configuration. There are plenty of distros that can give you a head start, but arguably the best is &lt;a href=&quot;https://github.com/nvim-lua/kickstart.nvim&quot; target=&quot;_blank&quot;&gt;kickstart&lt;/a&gt; as it teaches you how to build your config instead of handing you too much. Getting a solid config will help your speed too as it will help you access files faster, switch projects, handle git, and more.&lt;/p&gt;
&lt;p&gt;The goal of this post is not to convince you that you should learn Vim, but show you why I personally use it and why I think thousands of other developers enjoy it as well. In the end you should use what works best for you, but also keep an open mind to the possibilities.&lt;/p&gt;</content:encoded></item><item><title>Why You Should Learn jq in 2024</title><link>https://stevedylan.dev/posts/why-you-should-learn-jq-in-2024/</link><guid isPermaLink="true">https://stevedylan.dev/posts/why-you-should-learn-jq-in-2024/</guid><description>Discover why learning jq isn&apos;t just about boosting your productivity, it&apos;s about becoming a more curious developer</description><pubDate>Sat, 12 Oct 2024 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/_astro/jq-cover.DxEFIEGU_17fXCA.webp&quot; alt=&quot;header image&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The chances are that if you are a modern developer or if you’re starting out, you probably don’t know what &lt;code&gt;jq&lt;/code&gt; is, and that’s why I’m writing this post. It won’t take long to explain what &lt;code&gt;jq&lt;/code&gt; is, so let’s just get that out of the way.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; could be labeled a command line tool, but in truth it‘s a very “high-level lexically scoped functional programming language” (at least according to Wikipedia) that has been around for over a decade. The whole thing is based around JSON and helping you manipulate it in the terminal quickly, which may seem dull or not very useful to the untrained, but let me &lt;strong&gt;show you&lt;/strong&gt; why this goes so hard.&lt;/p&gt;
&lt;p&gt;A great way to experience &lt;code&gt;jq&lt;/code&gt; yourself is to follow their &lt;a href=&quot;https://jqlang.github.io/jq/tutorial/&quot; target=&quot;_blank&quot;&gt;short tutorial&lt;/a&gt; but I wanted to give a more personal example. Recently I built a &lt;a href=&quot;https://cli.pinata.cloud&quot; target=&quot;_blank&quot;&gt;CLI&lt;/a&gt; for Pinata that lets you upload, access, and manage your files from the terminal. One simple command you can run is &lt;code&gt;pinata files list -a 5&lt;/code&gt; to display a list of your most recent uploads (limiting the amount to 5 items for now), and the output looks like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;files&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01927e06-6f36-7208-adb9-8cdd53ce1c98&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafybeicvzjxfbo5a54q5jnteo52sbgqlvnvlcpqix24gnepow2ussmxbbq&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4262857&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/gzip&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;&lt;/span&gt;&lt;span&gt;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;group_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01921735-7dd6-746b-b091-170e204c03d4&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-10-12T00:00:04.998938Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;019278e0-1383-73b9-9051-4ec9db013927&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafybeic7agnfmxn7qktejq74xosobmurrg4yak7np2j2kfaivdxl3wt3pm&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4263052&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/gzip&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;&lt;/span&gt;&lt;span&gt;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;group_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01921735-7dd6-746b-b091-170e204c03d4&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-10-11T00:00:05.235771Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01927709-7dfd-7820-bd83-0e54b5f9ecd8&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;list&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafkreigsvbe22ir66kqiaabfa76eunkp4mnbs5jtyd2euy66zsab3uuvm4&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;443&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;&lt;/span&gt;&lt;span&gt;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-10-10T15:26:04.181151Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01927707-8a16-7d7e-a862-211b4145455d&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;zed-keybindings&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafkreicap37u35xrlrvzyhzgbn5p7mtbivfdda2qbh4wgzy4fhctoxbg5i&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;19212&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;&lt;/span&gt;&lt;span&gt;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-10-10T15:23:56.04911Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;019273b9-b556-7ea3-b793-847b69095a7a&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafybeia5kwvkyqwvqaifqmwmlxopysvyqs7hxvkqlzqv7jlnnv4lshx5xm&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4262964&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/gzip&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;&lt;/span&gt;&lt;span&gt;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;group_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01921735-7dd6-746b-b091-170e204c03d4&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-10-10T00:00:04.879244Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;next_page_token&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;MDE5MjczYjktYjU1Ni03ZWEzLWI3OTMtODQ3YjY5MDk1YTdh&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trying to view this in the terminal can be pretty rough; there’s a lot to look at here. That’s the case with most APIs, and this one isn’t even that complicated. If you were trying to fetch data from the Google books API, it will be almost impossible to navigate. That’s where &lt;code&gt;jq&lt;/code&gt; comes in.&lt;/p&gt;
&lt;p&gt;Let’s say I just wanted the first item in the &lt;code&gt;files[]&lt;/code&gt; array; I can do that with &lt;code&gt;jq&lt;/code&gt; by &lt;a href=&quot;https://www.geeksforgeeks.org/piping-in-unix-or-linux/&quot; target=&quot;_blank&quot;&gt;piping&lt;/a&gt; the results of the previous command into &lt;code&gt;jq&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list -a 5 | jq .files[0]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will select the array and particularly the zero index of the array, just one object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01927e06-6f36-7208-adb9-8cdd53ce1c98&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafybeicvzjxfbo5a54q5jnteo52sbgqlvnvlcpqix24gnepow2ussmxbbq&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4262857&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;number_of_files&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;mime_type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/gzip&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;keyvalues&quot;&lt;/span&gt;&lt;span&gt;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;group_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;01921735-7dd6-746b-b091-170e204c03d4&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;created_at&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-10-12T00:00:04.998938Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok big whoop, we just reduced our results to a single object. However if we stop and think through the syntax of what we just did, we can start to see what else is possible here. What if I just wanted to grab the &lt;code&gt;cid&lt;/code&gt; for the file that matches the name “pinnie”?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list -n pinnie | jq .files[0].cid&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What if I can to pipe that &lt;code&gt;cid&lt;/code&gt; into another command, perhaps open it in the browser?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list -n pinnie | jq .files[0].cid | xargs pinata gateways open&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might have noticed we slipped an extra tool in here called &lt;code&gt;xargs&lt;/code&gt; and while it’s outside the scope of this post I would highly recommend learning that tool as well. Back to &lt;code&gt;jq&lt;/code&gt;, let’s try something else. What if I need the file names for not just the last 5 results but 50 or 100. How do we go outside just one index of an array? Just a little change in our syntax.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list | jq [.files[].name]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will give us an array of all the file names like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;list&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;zed-keybindings&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;hello.txt&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;data.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;b68ad739-3664-4f9f-8921-49f30ad5d615.txt&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can pipe that into a list for later if we need it!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt; files&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;jq&lt;/span&gt;&lt;span&gt; [.files[].name] &amp;gt; files.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The possibilities are endless; we could continue piping commands, use a for loop in bash, etc. &lt;code&gt;jq&lt;/code&gt; is just one of those special tools that really enables you do to more from the terminal, and that brings us to the real point of this article: learning to use CLIs.&lt;/p&gt;
&lt;p&gt;Sadly there are many devs out their whose knowledge of the terminal stops at &lt;code&gt;npm run dev&lt;/code&gt; at the bottom of VSCode. Maybe they go a bit further and know some commands in &lt;code&gt;git&lt;/code&gt;, but generally people miss out on massive productivity gains that they could have had otherwise. Further, they miss out on a better understanding how all of this stuff works. I‘ve mentioned &lt;a href=&quot;https://stevedylan.dev/posts/why-i-learned-vim/&quot; target=&quot;_blank&quot;&gt;in previous posts&lt;/a&gt; that learning Neovim and building your config will give you a better low-level understanding of your tooling; this includes things like LSPs or syntax highlighting which are normally abstracted away from you.&lt;/p&gt;
&lt;p&gt;Not all abstraction is bad. For example, I would much rather use &lt;a href=&quot;https://pinata.cloud&quot; target=&quot;_blank&quot;&gt;Pinata&lt;/a&gt; than S3, but I won’t deny that it could be good to learn how to use S3. In today’s developer ecosystem it’s becoming a growing skill to learn what is worth abstracting and what isn’t. For example, I personally don’t find much value in abstracting &lt;code&gt;git&lt;/code&gt; into a formal gui or dedicated client, however I do find value in using a tui like &lt;a href=&quot;https://github.com/jesseduffield/lazygit&quot; target=&quot;_blank&quot;&gt;lazygit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Like most things in life, there are so many gray areas in our current developer environment. Most of this comes down to preference, and it could be argued from one side to the other. Sometimes it’s more important to ship a product, sometimes it’s more important to really understand what you’re doing. Ultimately it’s up to you how far you want to go down the rabbit hole, or perhaps even more important, how &lt;em&gt;open&lt;/em&gt; you are to the possibilities.&lt;/p&gt;
&lt;p&gt;In my opinion the best developer is a curious one. “How does this work? Why does this work? How could I use this elsewhere?” Obviously you can’t learn everything, and you don’t necessarily need to these days, but curiosity grows your knowledge. I don’t have to write in basic at all for my job, but that hasn’t kept me from at least being curious and exploring how it works. The magic of programming is the ability to control computers and data. As was said in a recent stream with DHH and The Primeagen, “It’s more fun to be competent,” and I couldn’t agree more.&lt;/p&gt;
&lt;p&gt;I don’t know about you, but I don’t want to settle for status quo; I want to be a wizard. So yeah, I think you should learn &lt;code&gt;jq&lt;/code&gt; in 2024.&lt;/p&gt;</content:encoded></item></channel></rss>