<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Words by Vernacchia</title><description>They may not always be right, but they&apos;re words...</description><link>https://words.byvernacchia.com/</link><language>en-us</language><item><title>Astro thus far...</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Hmmm, what do I think of Astro thus far??</description><pubDate>Mon, 27 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Redirect from &quot;../../components/ClientRedirect/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Redirect pubDate={frontmatter.pubDate} collection=&quot;blog&quot; /&amp;gt;&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>A New Blog? Why?</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Hmmm, why a new blog? I wonder...</description><pubDate>Tue, 31 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been working at &lt;a href=&quot;https://deliveroo.co.uk/&quot;&gt;Deliveroo&lt;/a&gt; for quite some time, just over 4 years in fact! (That&apos;s forever in tech years)&lt;/p&gt;
&lt;p&gt;When I was hired, my background was pretty spcific to FE-esque (frontend) things.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Know my way around a browser ✅&lt;/li&gt;
&lt;li&gt;Have set up and debugged too many webpack configs ✅&lt;/li&gt;
&lt;li&gt;Investigated various styles of testing (still like TDD for unit tests. fight me) ✅&lt;/li&gt;
&lt;li&gt;Kept up with Node.js, React, and other tech ✅&lt;/li&gt;
&lt;li&gt;Can debate endlessly about &lt;em&gt;shit&lt;/em&gt;... ✅&lt;/li&gt;
&lt;li&gt;Knew Backbone.js, Angular.js, and React + could pick up most FE stacks ✅&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unfortunately, I moved to a different team after about 6-8 months and they didn&apos;t need many of these skills. Instead, they needed me to get up to speed with BE technologies. So, I did. It&apos;s what my team needed.&lt;/p&gt;
&lt;p&gt;Over the years (yes, years) the teams I was/am responsible for improved vastly. Like, so amazing. Maybe that&apos;s another blog post, but I digress.&lt;/p&gt;
&lt;p&gt;During that time, I let my FE knowledge slip. All the flame wars between Svelte, Solid, React Suspense (still a thing like it was a while ago?!?!), XState, Remix, etc. = 🍿. I didn&apos;t have time to have a look. Hot takes were my sources of information.&lt;/p&gt;
&lt;p&gt;I am finally at a point where I can come back and investigate new FE tech! I&apos;ve been lucky enough to schedule a half day every week to do so. My manager even supports it. Hell, it was his idea.&lt;/p&gt;
&lt;p&gt;I&apos;m really excited to get back into the FE space, allowing myself to evaluate different options and potentially bring some back to Deliveroo.&lt;/p&gt;
&lt;p&gt;Anyway, I&apos;ll be blogging about my experiences on this site, which I built with Astro (awww yeahhhh, new and shiny).&lt;/p&gt;
&lt;p&gt;Topics I&apos;m looking at include, but are not limited to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chrome Extensions
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;../02/my-first-chrome-extension.md&quot;&gt;(post) My First Chrome Extension (Google Docs Default Zoom)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../03/introducing-google-workspace-zoom-default.md&quot;&gt;(post) Google Workspace Zoom Default (more support!)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../04/simulating-js-events.mdx&quot;&gt;(post) &quot;Simulating&quot; JS Events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../02/adding-comments-to-this-thing.md&quot;&gt;(post) Adding post comments via utterances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../02/how-do-search-this-thing.md&quot;&gt;(post) Different ways to do search on static sites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Frameworks / Meta-frameworks
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;../02/1-astro-thus-far.md&quot;&gt;(post) Astro thus far...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../05/astro-content-collection-month-and-year-pages.md&quot;&gt;(post) Astro Content Collection Month and Year Pages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remix.run/&quot;&gt;Remix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qwik.builder.io/&quot;&gt;qwik&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vuejs.org/&quot;&gt;Vue.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.solidjs.com/&quot;&gt;SolidJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://htmx.org/&quot;&gt;htmx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lit.dev/&quot;&gt;Lit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react-spectrum.adobe.com/react-aria/&quot;&gt;React Aria&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;../03/1-react-aria-exploration.md#usebutton-requires-a-ref-and-props&quot;&gt;(post) Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Deno&lt;/li&gt;
&lt;li&gt;React Server Components vs SRR vs everything&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-router&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;TanStack router&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-query&lt;/code&gt; (or tanstack query)&lt;/li&gt;
&lt;li&gt;What&apos;s the best way to build/package/transpile things these days - it&apos;s been a while&lt;/li&gt;
&lt;li&gt;Best way to have consistent types between backend/frontend + request validation
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://trpc.io/&quot;&gt;tRPC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CSS / CSS-in-JS
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vanilla-extract-css/vanilla-extract&quot;&gt;Vanilla Extract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; - Will give it a try again as it&apos;s likely improved since I tried it when it first came out&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Edge networks
&lt;ul&gt;
&lt;li&gt;Deno deploy&lt;/li&gt;
&lt;li&gt;cloudflare workers&lt;/li&gt;
&lt;li&gt;deployment to these using frameworks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;serverless&lt;/li&gt;
&lt;li&gt;lambda layers&lt;/li&gt;
&lt;li&gt;api gateways&lt;/li&gt;
&lt;li&gt;how to do React properly starting from fresh in 2022/23&quot;. I feel like a lot of what I know/we do as a company is based on React 14-ish ideas. Are we leveraging suspense? What about the shiny new SSR capabilities?&lt;/li&gt;
&lt;li&gt;E2e testing Frameworks&lt;/li&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;Vitest&lt;/li&gt;
&lt;li&gt;Cloudflare
&lt;ul&gt;
&lt;li&gt;R2&lt;/li&gt;
&lt;li&gt;D1&lt;/li&gt;
&lt;li&gt;Wrangler&lt;/li&gt;
&lt;li&gt;KV store&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;View transitions - Page transitions
&lt;ul&gt;
&lt;li&gt;new thing for MPAs and stuff like that&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Module federation&lt;/li&gt;
&lt;li&gt;Micro front-end&lt;/li&gt;
&lt;li&gt;React three journey&lt;/li&gt;
&lt;li&gt;Bundlers + tree shaking based out compile mode (js common)&lt;/li&gt;
&lt;li&gt;lambda layers&lt;/li&gt;
&lt;li&gt;Andy osmani online book&lt;/li&gt;
&lt;li&gt;OpenAI training for what you should focus on&lt;/li&gt;
&lt;li&gt;Service workers&lt;/li&gt;
&lt;li&gt;Framer&lt;/li&gt;
&lt;li&gt;Bun&lt;/li&gt;
&lt;li&gt;State Management
&lt;ul&gt;
&lt;li&gt;Xstate&lt;/li&gt;
&lt;li&gt;Redux / Redux Toolkit&lt;/li&gt;
&lt;li&gt;Zustand&lt;/li&gt;
&lt;li&gt;Jotai&lt;/li&gt;
&lt;li&gt;MobX&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Vanilla extract css&lt;/li&gt;
&lt;li&gt;three.js&lt;/li&gt;
&lt;li&gt;DynamoDB data modelling (not fe, but interesting to me and my role)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They are also in no order!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Astro thus far...</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Hmmm, what do I think of Astro thus far??</description><pubDate>Mon, 27 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the purposes of this blog (see &lt;a href=&quot;./a-new-blog-why.md&quot;&gt;why&lt;/a&gt;) was to explore new technology in the frontend space. I
wanted to blog about my learnings / progress (even if it was just me rereading things for that stats 📈).&lt;/p&gt;
&lt;p&gt;So I looked at &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;, something I&apos;d wanted to use for a while and went all in.&lt;/p&gt;
&lt;h2&gt;How it started&lt;/h2&gt;
&lt;p&gt;Starting it up was pretty simple. I used the yarn command from their website to scaffold the project, which I think used
the started template?!? Definitely copied a lot of it from somewhere haha&lt;/p&gt;
&lt;p&gt;I tried to edit the site a bit to tweak it to my needs and it was suprisingly easy. It took me a while to get used to the
&lt;code&gt;.astro&lt;/code&gt; files, which were all I used for a while (getting a feel for the framework). Only when I &lt;a href=&quot;./how-do-search-this-thing.md&quot;&gt;added search&lt;/a&gt;
did I use something else.&lt;/p&gt;
&lt;p&gt;The DX was very good. Maybe it&apos;s just me being jaded from the early versions of Next.js (yes, I&apos;m looking at you, the upgrade
from 7 to 8 or something along those lines), but it was really good. It felt like everything was set up,
so when I ran the dev comamnd, IT 👏 JUST 👏 Worked 👏.&lt;/p&gt;
&lt;p&gt;I had it publishing to Github pages (where this is hosted) after pushing to the main branch in no time. Either the
scaffolding took care of this or their docs were really good and made this set up super easy.&lt;/p&gt;
&lt;p&gt;I then went searching for ways to make it blog (i.e. publish static content). Since I started building this site a few
days after v2.0.0 was released I had access to &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/&quot;&gt;Content Collections&lt;/a&gt;.
Boy are these nice. Super simple way to access colletion/MD/MDX data (I have yet to look at MDX, but it&apos;s on the list).&lt;/p&gt;
&lt;p&gt;In the end, I had a blog up and running in only a couple days (in my spare time between actual work haha). I very much
&quot;borrowed&quot; some of the things from Astro&apos;s examples repostory. Only after it was all deployed did I really look at the
docs (typical dev haha).&lt;/p&gt;
&lt;p&gt;Then I started writing content (not the best content, but content). I was amazed at the speed I was able to do it as it
was just adding a Markdown file with some content (unpolished content 😉) and pushing the changes to Github.&lt;/p&gt;
&lt;h2&gt;How it&apos;s going&lt;/h2&gt;
&lt;p&gt;After writing a few blog posts, I wanted to add a few things, namely to help my non-existent SEO and/or discoverability.
Not like I wanted to get to the top of Google, but wanted to see how it easy it would be to integrate things like a
sitemap, &lt;code&gt;robots.txt&lt;/code&gt;, RSS feed, and social open graph (FB + Twitter stuff, but really, who uses FB still 🤷)...&lt;/p&gt;
&lt;p&gt;Turns out, for most of these things there are already prebuilt integrations that someone much smarter than me has taken
the time to open source.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sitemap via &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/sitemap/&quot;&gt;@astrojs/sitemap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;RSS Feed via &lt;a href=&quot;https://docs.astro.build/en/guides/rss/&quot;&gt;@astrojs/rss&lt;/a&gt; (not sure why it&apos;s not on the &lt;a href=&quot;https://astro.build/integrations/&quot;&gt;integrations&lt;/a&gt; page)&lt;/li&gt;
&lt;li&gt;Open Graph via &lt;a href=&quot;https://github.com/vernak2539/words-byvernacchia/blob/main/src/components/BaseHead.astro&quot;&gt;Custom Head Component&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Probably copied this from somewhere (thank you!)&lt;/li&gt;
&lt;li&gt;Easily added my own ability to designate images used for OG data via frontmatter (pretty nice, super easy)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Analytics via &lt;a href=&quot;https://github.com/vernak2539/words-byvernacchia/blob/main/src/components/Tracking.astro&quot;&gt;Custom Tracking Component&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Via &lt;a href=&quot;https://simpleanalytics.com&quot;&gt;Simple Analytics&lt;/a&gt; (because I care about you not being the product)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also added things like &lt;a href=&quot;./adding-comments-to-this-thing.md&quot;&gt;comments&lt;/a&gt;, and I used &lt;a href=&quot;https://github.com/natemoo-re/astro-icon#readme&quot;&gt;astro-icon&lt;/a&gt;
to add some &quot;flare&quot; to the blog entry metadata section (you&apos;re welcome).&lt;/p&gt;
&lt;p&gt;I thought I was done, but I really wanted to add tags to the site as well (have not added them OG stuff yet). I followed
&lt;a href=&quot;https://docs.astro.build/en/tutorial/5-astro-api/1/&quot;&gt;the tutorial&lt;/a&gt; on their site, and with a few changes (using Content
Collections), we were finished.&lt;/p&gt;
&lt;p&gt;Overall, I&apos;ve been super surprised at how easy it was to add all of this stuff. I actually wanted to do it instead of
slog my way through things. I&apos;ve used WordPress, Drupal, Jekyll, Gatsby (early days), Ghost, and Next.js (likely missed
some) but really like this more. I&apos;m not sure how to explain it...&lt;/p&gt;
&lt;h3&gt;Small nitpick&lt;/h3&gt;
&lt;p&gt;I didn&apos;t realise the links that I put in my Markdown files to other Markdown files via &lt;code&gt;[]()&lt;/code&gt; were not being translated
to generated paths. Turns out that is not supported yet, which I found out on a couple of issues&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/withastro/astro/issues/5682&quot;&gt;Issue #5682&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/withastro/astro/issues/5680&quot;&gt;Issue #5680&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/withastro/roadmap/discussions/424&quot;&gt;RFC #424&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hopefully there will be a fix issued soon, but in the meantime I&apos;ll likely just put the paths there instead of using
Markdown links (not looking to get into rehype or remark at the moment of writing this).&lt;/p&gt;
&lt;p&gt;🔥 &lt;strong&gt;Update:&lt;/strong&gt; I actually created a &lt;a href=&quot;https://github.com/rehypejs/rehype&quot;&gt;rehype&lt;/a&gt; plugin to do this specifically for Astro.
Read about it &lt;a href=&quot;../05/relative-markdown-paths-in-astro-with-rehype.md&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Future Ideas&lt;/h2&gt;
&lt;p&gt;I have an idea I want to explore in the future around displaying remote file content. Think something like embedding a
gist on a site.&lt;/p&gt;
&lt;p&gt;I want to be able to do this both from Markdown and an Astro component with the options of inlining it into the page on
build or displaying it dynamically on page load. I know there&apos;s some React/Vue/whatever framework you want to use component
out there that does this, but it&apos;s time to learn!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Adding Comments to this Thing</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Let&apos;s use utterances to add some comments to this shite... I mean site.</description><pubDate>Sun, 19 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hmm... so this site doesn&apos;t have comments. Let&apos;s fix that.&lt;/p&gt;
&lt;p&gt;Since I&apos;m pretty lazy, let&apos;s go for the easiest thing...&lt;/p&gt;
&lt;p&gt;This blog will likely be read by devs, so why not give &lt;a href=&quot;https://utteranc.es/&quot;&gt;utterances&lt;/a&gt; a try?!?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://utteranc.es/&quot;&gt;utterances&lt;/a&gt; is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A lightweight comments widget built on GitHub issues. Use GitHub issues for blog comments, wiki pages and more!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sounds perfect... Let&apos;s add it to the site (i.e. hopefully you&apos;ll see something at the bottom of the page).&lt;/p&gt;
&lt;h2&gt;Update&lt;/h2&gt;
&lt;p&gt;And there we have it. It seems to be added to the page.&lt;/p&gt;
&lt;p&gt;It was relatively simple as I used some info from &lt;a href=&quot;https://www.jamesperkins.dev/post/supercharge-your-astro-blog&quot;&gt;this blog post&lt;/a&gt;. Ended up
using standard JS instead of Svelte.&lt;/p&gt;
&lt;p&gt;~Hopefully it works when it&apos;s deployed!~ IT WORKED! I love the lazy solution!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>How do I search this thing?</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Where is everything?!?</description><pubDate>Thu, 23 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I want to add some search features to this static site. Not a huge thing, but would be a nice to have.&lt;/p&gt;
&lt;p&gt;This gives me the opportunity to investigate all the different ways to go about adding search to a statically
generate site.&lt;/p&gt;
&lt;h2&gt;My Options&lt;/h2&gt;
&lt;p&gt;What are my options?? Let&apos;s have a look.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://www.algolia.com/&quot;&gt;Algolia&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You&apos;ve probably seen this technology on so many documentation sites, I know I have. It looks pretty decent for
this site, as their free tier provides:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;10,000 search + 10,000 recommend requests/mo and 10,000 records/mo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&apos;s be honest, I&apos;m not going to hit that amount of search (this isn&apos;t a big blog). Let&apos;s have a look at how it&apos;s
integrated on sites.&lt;/p&gt;
&lt;p&gt;Seems like I have to sign up to get an API key and such. Maybe I&apos;ll try using &quot;React InstanceSearch Hooks&quot; tutorial to
get started.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://typesense.org/&quot;&gt;Typesense&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At first, it seems free and pretty decent. Then you see that you have to either set up your own backend or use their
cloud, both of which costs money.&lt;/p&gt;
&lt;p&gt;It does seem to be a decent product though, but it&apos;s not easy to set up (&lt;a href=&quot;https://aviyel.com/post/1006/adding-typesense-search-to-an-astro-static-generated-website&quot;&gt;tutorial&lt;/a&gt;)
/ I&apos;m lazy. Likely going to forgo this one.&lt;/p&gt;
&lt;h3&gt;Load from Astro + Local Search&lt;/h3&gt;
&lt;p&gt;Seems like you can use Astro to load all posts on the &quot;server&quot; and then pass it to a client component.&lt;/p&gt;
&lt;p&gt;It would look something like this (obviously not Astro format, but you get the gist):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Search from &quot;../components/Search.tsx&quot;;

const posts = await Astro.glob(&quot;../blog/*.md&quot;);
const searchablePosts = posts.filter((post) =&amp;gt; !post.frontmatter.draft); // or whatever filters

return &amp;lt;Search client:load searchList={searchablePosts} /&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then inside that, do some filtering/search. I&apos;ve been quite partial to &lt;a href=&quot;https://fusejs.io/&quot;&gt;Fuse.js&lt;/a&gt; (even if my team
didn&apos;t like it ha) and have used that many times.&lt;/p&gt;
&lt;p&gt;Definitely a viable option.&lt;/p&gt;
&lt;h3&gt;Others&lt;/h3&gt;
&lt;p&gt;Seems like a lot of the others require you to generate some form of base, serve that, then search through it. Seems
super custom. So likely not going to go with that.&lt;/p&gt;
&lt;h2&gt;My Choice&lt;/h2&gt;
&lt;p&gt;I think I&apos;m going to be lazy and try to use Algolia because it&apos;s &quot;easy.&quot; Hopefully those aren&apos;t famous last words.&lt;/p&gt;
&lt;h3&gt;Me trying to implement it&lt;/h3&gt;
&lt;p&gt;My god, this was a nightmare 😬.&lt;/p&gt;
&lt;p&gt;I thought it would be so easy since everyone seems to use Algolia. Just add a JS snippet and you&apos;re done. It does everything
else for you. Boy was I wrong.&lt;/p&gt;
&lt;h4&gt;Populating Search Indexes&lt;/h4&gt;
&lt;p&gt;It turns out you very much do have to prep data yourself. This involves parsing and uploading it to their site. It&apos;s almost
as if I described this in the &lt;a href=&quot;#others&quot;&gt;Others&lt;/a&gt; section before I choose my fate. Man, being lazy doens&apos;t pay off sometimes.&lt;/p&gt;
&lt;p&gt;So, I built a node script to do it. You can see it &lt;a href=&quot;https://github.com/vernak2539/words-byvernacchia/blob/main/bin/build-search.js&quot;&gt;here&lt;/a&gt;.
I was lazy here and stole a lot from &lt;a href=&quot;https://route360.dev/post/astro-algolia/#create-algoliajs&quot;&gt;this blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This runs on my Github workflow when I build and publish my blog, so it should have up-to-date information when new
content is released.&lt;/p&gt;
&lt;h4&gt;Implementing the Search&lt;/h4&gt;
&lt;p&gt;This was sooooooo difficult to figure out. I started at their website and the first thing you&apos;re confronted with is whether
to use InstantSearch, React InstanceSearch, React InstanceSearch Hooks, etc.&lt;/p&gt;
&lt;p&gt;Damnnnnnn, that&apos;s a lot of choices.&lt;/p&gt;
&lt;p&gt;So, I went for React InstanceSearch Hooks. I followed the instructions, trying to figure how to display results and how to
style it (had to install a separate NPM package for this then import it). I ran into problems 🤦‍&lt;/p&gt;
&lt;p&gt;Some of these problems were due to Astro and how I&apos;d not added &lt;code&gt;client:load&lt;/code&gt; to my React component. Even after doing that,
there were still problems.&lt;/p&gt;
&lt;p&gt;How do I style it? How do I move it to different parts of my sight? How do I get it show up as a modal?&lt;/p&gt;
&lt;p&gt;That last part was the problem. I couldn&apos;t figure out how to do that just by using React InstantSearch Hooks. Turns out
there&apos;s something called &lt;a href=&quot;https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/&quot;&gt;Autocomplete&lt;/a&gt;
that does exactly what I want.&lt;/p&gt;
&lt;p&gt;So, I abandoned everything I&apos;d tried and started fresh. The Autocomplete docs had examples (only JS, not TS 😓) and some
codesandbox examples. This was really helpful.&lt;/p&gt;
&lt;p&gt;I ended up being able to implement that in a couple of hours. I only ate up about 200 of my 10K queries haha.&lt;/p&gt;
&lt;h2&gt;The Final Outcome&lt;/h2&gt;
&lt;p&gt;Now I have search on my site that updates when I publish new content (or even change content). I very much over-engineered
it, driven by my want to be lazy. Maybe I should rethink that in the future 🤔?!?!&lt;/p&gt;
&lt;p&gt;Like I mentioned before, I used &lt;a href=&quot;https://route360.dev/post/astro-algolia&quot;&gt;this blog&lt;/a&gt; a lot at the start, but then moved
to Autocomplete.&lt;/p&gt;
&lt;p&gt;If you want to check out my code, here it is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vernak2539/words-byvernacchia/blob/main/bin/build-search.js&quot;&gt;Node.js Script to Build + Publish Search Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vernak2539/words-byvernacchia/tree/main/src/components/Search&quot;&gt;Search Components (UI)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>My First Chrome Extension</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Let&apos;s talk about creating &quot;Google Docs Default Zoom&quot;...</description><pubDate>Fri, 03 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import NewExtensionNameBanner from &quot;../../../../components/NewExtensionNameBanner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;NewExtensionNameBanner /&amp;gt;&lt;/p&gt;
&lt;p&gt;**&lt;strong&gt;Update:&lt;/strong&gt; See &lt;a href=&quot;../03/introducing-google-workspace-zoom-default.mdx&quot;&gt;Introducing Google Workspace Zoom Default&lt;/a&gt; for an update!**&lt;/p&gt;
&lt;p&gt;As much as I don&apos;t want to admit it, I&apos;m getting old.&lt;/p&gt;
&lt;p&gt;My job requires me to review a lot of Google Docs (😔), and, unfortunately, I am having trouble reading things when they are set to the zoom level of &quot;100%.&quot;&lt;/p&gt;
&lt;p&gt;I quickly ruled out changing the resolution settings on my monitors because I really like the space the higher resolutions afford.&lt;/p&gt;
&lt;p&gt;So, now that I accepted the fact that my eyes can&apos;t do &quot;100%&quot; any more, how do I change the default zoom level in Google Docs.&lt;/p&gt;
&lt;p&gt;After looking through all their setting and doing an extensive internet search, it turns out there is no native setting!!!&lt;/p&gt;
&lt;p&gt;Well, I embarked on making my own. As a start, I asked myself, &quot;how do you zoom in Google Docs?&quot; There&apos;s two ways (hit me up if there&apos;s more...):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use the &quot;Zoom&quot; feature in the Google Doc via the menu&lt;/li&gt;
&lt;li&gt;Use the browser&apos;s zoom function&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I don&apos;t like using the second option due to personal preference, which meant I was going with #1. Let&apos;s get to it!&lt;/p&gt;
&lt;p&gt;Enter Chrome Extesions. I needed to execute a script to do something on the page to change the zoom.&lt;/p&gt;
&lt;p&gt;But, bad news seemed to keep following me. Not only is there no way to set a default zoom level in Google Docs itself, there&apos;s no way to programmatically interact with the page. No variable on the &lt;code&gt;window&lt;/code&gt; object etc.&lt;/p&gt;
&lt;p&gt;This meant that I now have to simulate click and interactions on the page (see &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/blob/main/ui-helpers.ts#L6-L25&quot;&gt;here&lt;/a&gt;). Fun, right?!?!&lt;/p&gt;
&lt;p&gt;Even more fun is that the Google Docs use dynamic element IDs, so I had to figure out away around that (see &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/blob/main/contents/index.ts#L32-L35&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Okay, that&apos;s all a bit painful, but relatively easy. Now the difficult part, how does one create a Chrome Extension these days? I took to Twitter (is it still alive??) to find out...&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;
&amp;lt;p lang=&quot;qam&quot; dir=&quot;ltr&quot;&amp;gt;
&amp;lt;a href=&quot;https://twitter.com/plasmohq?ref_src=twsrc%5Etfw&quot;&amp;gt;@plasmohq&amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;
— Mitch Friedman (@mitchfriedman5){&quot; &quot;}
&amp;lt;a href=&quot;https://twitter.com/mitchfriedman5/status/1617959819595644930?ref_src=twsrc%5Etfw&quot;&amp;gt;
January 24, 2023
&amp;lt;/a&amp;gt;
&amp;lt;/blockquote&amp;gt;
&amp;lt;script
async
src=&quot;https://platform.twitter.com/widgets.js&quot;
charset=&quot;utf-8&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And a buddy who I used to work with came through in clutch. Enter &lt;a href=&quot;https://www.plasmo.com/&quot;&gt;Plasmo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tbh, I had no experience with Chrome extensions, but this framework make it super easy to get up to speed and actually ship something.&lt;/p&gt;
&lt;p&gt;My extension makes use of their plug-ins / modules / APIs for messaging and storage, saving me so much time to focus on what I wanted to do.&lt;/p&gt;
&lt;p&gt;They had a good guide on how to automate the publishing of it to the Chrome Web Store (had to pay 😬💸), which I&apos;ve since set up also. Makes it super simple for me to publish things when I release a new version via tagging.&lt;/p&gt;
&lt;p&gt;Plasmo is great. If you want to do Chrome Extensions (or just browser extensions) I would seriously check it out.&lt;/p&gt;
&lt;p&gt;Long story short, my extension is &lt;a href=&quot;https://chrome.google.com/webstore/detail/google-docs-zoom-default/nflkcdlimipkgbacnfnhfecjgmojhklo&quot;&gt;now published on the Chrome Web Store (Google Docs Zoom Default)&lt;/a&gt; and the source is available on &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are some bugs I&apos;d like to fix and features I&apos;d like to add. See below!&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ Allow for usage in Sheets, Slides, and other Google Workspace applications that have zoom features (&lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/issues/3&quot;&gt;issue&lt;/a&gt;)
&lt;ul&gt;
&lt;li&gt;Support for Docs + Sheets as of v0.2.0. Implementation for other applications is also easier now.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;✅ Get it to work for larger documents (&lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/issues/1&quot;&gt;issue&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add an active state to the pop up (&lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/issues/2&quot;&gt;issue&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;✅ Make it prettier 💅&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>React Aria Exploration (Pt. 1)</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Let&apos;s have a look at React Aria. I&apos;ve been wanting to do this for a while...</description><pubDate>Fri, 03 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I started following &lt;a href=&quot;https://twitter.com/devongovett&quot;&gt;Devon Govett&lt;/a&gt; on Twitter well before 2.0. I was looking at
alternatives to webpack that would allow me to set up sample projects in seconds, not days (sorry webpack 😓).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For those of you that don&apos;t know, he&apos;s the creator of &lt;a href=&quot;https://parceljs.org/&quot;&gt;Parcel&lt;/a&gt; and a slew of other
things that you likely use everyday in the JS community.&lt;/p&gt;
&lt;p&gt;So, I&apos;m trolling Twitter one day (lurker status), and I see him tweet about something called &quot;React Aria.&quot; I had an initial
look and thought, &quot;Damn, this would&apos;ve helped solve so many problems we previously had in our design system. How can
we potentially look at integrating this into ours so we don&apos;t have to roll our own??&quot;&lt;/p&gt;
&lt;p&gt;Fast forward ~2 years. I still hadn&apos;t looked at the library 😬. During that time I had been promoted (🕷️👨), added
a second team to my responsibilities, and, you know, had life things going on. Well here we are now. Let&apos;s give it the
good ole college try.&lt;/p&gt;
&lt;h2&gt;Accessibility Matters&lt;/h2&gt;
&lt;p&gt;Let me repeat, accessibility matters! I want everyone to be able to use the web. Nothing should stop someone from using
one of the greatest resources known to humankind.&lt;/p&gt;
&lt;p&gt;In order to keep true to that statement, as developers/engineers we need to do our best to ensure everyone can access
the things on which we&apos;re working.&lt;/p&gt;
&lt;p&gt;If you can&apos;t get behind the altruistic reasoning (sans benefits), then let&apos;s think about it this way. The more people
you have using your site, the more potential customers you have.&lt;/p&gt;
&lt;p&gt;Just because someone may have a bit more trouble using the web (browsers, phone, etc.) doesn&apos;t mean they don&apos;t want to
purchase a product (i.e. give you money).&lt;/p&gt;
&lt;p&gt;There are many groups of people looking to improve accessibility on the web. Have a look at the content below if you
want to learn more.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/&quot;&gt;W3C Web Accessibility Initiative (WAI)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Responsible for publishing standards like &lt;a href=&quot;https://www.w3.org/TR/WCAG21/&quot;&gt;WCAG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/&quot;&gt;Accessible Rich Internet Application (ARIA) Authoring Practices Guide (APG)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gov.uk/guidance/accessibility-requirements-for-public-sector-websites-and-apps&quot;&gt;UK Public Sector Accessibility Requirements&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Spoiler, based on the WCAG standards&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;React Spectrum&lt;/h2&gt;
&lt;p&gt;Let&apos;s start at the beginning. Adobe, yes the people responsible for Photoshop, have a design system called &lt;a href=&quot;https://spectrum.adobe.com/&quot;&gt;Spectrum&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They then have different implementations of the design system that include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://opensource.adobe.com/spectrum-css/&quot;&gt;Spectrum CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react-spectrum.adobe.com/index.html&quot;&gt;React Spectrum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opensource.adobe.com/spectrum-web-components/&quot;&gt;Spectrum Web Components&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can probably guess what each focuses on based on the name.&lt;/p&gt;
&lt;p&gt;I&apos;ll be discussing the React Spectrum here, which is &lt;em&gt;A React implementation of Spectrum, Adobe’s design system.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well, duhhh... But, the cool thing is that there&apos;s actually a group of libraries (React Spectrum Libraries) that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;help you build adaptive, accessible, and robust user experiences.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React Spectrum - design system itself (uses these other libraries below)&lt;/li&gt;
&lt;li&gt;React Aria - A library of React Hooks that provides accessible UI primitives for your design system.&lt;/li&gt;
&lt;li&gt;React Stately - A library of React Hooks that provides cross-platform state management for your design system.&lt;/li&gt;
&lt;li&gt;Internationalized - Two framework-agnostic internationalization libraries for the web.
&lt;ul&gt;
&lt;li&gt;Dates and times + Numbers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we know that, let&apos;s get a move on!&lt;/p&gt;
&lt;h2&gt;React Aria&lt;/h2&gt;
&lt;p&gt;React Aria is pretty cool IMHO (and the fact that Adobe open sourced it)! It&apos;s designed to be flexible and integrated into any
design system. So, that means you don&apos;t have to use React Spectrum to get the accessibility benefits if you don&apos;t want
to.&lt;/p&gt;
&lt;p&gt;🙌🏻 A huge step forward for accessibility on the web.&lt;/p&gt;
&lt;p&gt;So, let&apos;s get right into it. I got set up quite quickly using Vite and Storybook. I looked around for a bit to see what
the current, defacto way to set these things up, but got lost and settle on this. I&apos;ll write a whole other blog post on
that topic. I&apos;ve published my &lt;a href=&quot;https://github.com/vernak2539/react-component-playground&quot;&gt;repository to Github&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Creating a Component&lt;/h3&gt;
&lt;p&gt;I started with the &lt;em&gt;&quot;easiest&quot;&lt;/em&gt; component in any design system, the &lt;code&gt;Button&lt;/code&gt; (joking about the easy part).&lt;/p&gt;
&lt;p&gt;I used the &lt;a href=&quot;https://github.com/vernak2539/react-component-playground/blob/main/src/components/Button/standard.tsx&quot;&gt;default &lt;code&gt;Button&lt;/code&gt;&lt;/a&gt;
generated by Storybook when first initialising it. And then I went about creating an &lt;a href=&quot;https://github.com/vernak2539/react-component-playground/blob/main/src/components/Button/aria.tsx&quot;&gt;&quot;React Aria&quot; version&lt;/a&gt;
of it. A super simple one.&lt;/p&gt;
&lt;p&gt;If you have a look at them, they&apos;re pretty close to the same. Let&apos;s go through the differences...&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://react-spectrum.adobe.com/react-aria/useButton.html&quot;&gt;useButton&lt;/a&gt; requires a ref and props&lt;/h4&gt;
&lt;p&gt;No biggie, created the ref and initialised the default props a different way. This hook then returns props that you spread
over the component. They can be seen below:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;All good so far!&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://react-spectrum.adobe.com/react-aria/usePress.html&quot;&gt;usePress&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;So, you&apos;ve looked through the examples and you&apos;re noticing a major difference. The &lt;a href=&quot;https://github.com/vernak2539/react-component-playground/blob/main/src/components/Button/aria.tsx&quot;&gt;&quot;React Aria&quot; Button&lt;/a&gt;
doesn&apos;t have an &lt;code&gt;onClick&lt;/code&gt; method defined in the props/types. If you go looking a bit deeper, you&apos;ll realise that it&apos;s
not defined anywhere (not even on the &lt;code&gt;AriaButtonProps&lt;/code&gt; types).&lt;/p&gt;
&lt;p&gt;What gives??&lt;/p&gt;
&lt;p&gt;Well, this is where &lt;a href=&quot;https://react-spectrum.adobe.com/react-aria/usePress.html&quot;&gt;usePress&lt;/a&gt; comes in, which aims to make &quot;pressing something&quot; across loads of
environments easier. Instead of an &lt;code&gt;onClick&lt;/code&gt; method there is an &lt;code&gt;onPress&lt;/code&gt; method. This will used create the proper props
that the Button will use (&lt;a href=&quot;#usebutton-requires-a-ref-and-props&quot;&gt;see above&lt;/a&gt;).&lt;/p&gt;
&lt;h4&gt;Different events?&lt;/h4&gt;
&lt;p&gt;Now that we&apos;ve talked about how React Aria uses the &lt;code&gt;onPress&lt;/code&gt; handler (or some variation of it), let&apos;s talk about the events.&lt;/p&gt;
&lt;p&gt;Turns out, a standard &lt;code&gt;onClick&lt;/code&gt; handler and the &lt;code&gt;onPress&lt;/code&gt; handler receive different events. Hmmm....&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;onClick&lt;/code&gt; event handler will receive a React &lt;code&gt;SyntheticBaseEvent&lt;/code&gt;. That&apos;s what we&apos;re used to in React.&lt;/p&gt;
&lt;p&gt;But, the &lt;code&gt;onPress&lt;/code&gt; event handler will receive a &lt;a href=&quot;https://github.com/adobe/react-spectrum/blob/main/packages/%40react-types/shared/src/events.d.ts#L33-L48&quot;&gt;&lt;code&gt;PressEvent&lt;/code&gt;&lt;/a&gt; which looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export interface PressEvent {
    /** The type of press event being fired. */
    type: &quot;pressstart&quot; | &quot;pressend&quot; | &quot;pressup&quot; | &quot;press&quot;;
    /** The pointer type that triggered the press event. */
    pointerType: PointerType;
    /** The target element of the press event. */
    target: Element;
    /** Whether the shift keyboard modifier was held during the press event. */
    shiftKey: boolean;
    /** Whether the ctrl keyboard modifier was held during the press event. */
    ctrlKey: boolean;
    /** Whether the meta keyboard modifier was held during the press event. */
    metaKey: boolean;
    /** Whether the alt keyboard modifier was held during the press event. */
    altKey: boolean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the looks of it, this is just a paired down version of &lt;code&gt;SyntheticBaseEvent&lt;/code&gt; with some helpful things like &lt;code&gt;pointerType&lt;/code&gt;
and the &lt;code&gt;type&lt;/code&gt; (event type). Don&apos;t worry, the event target is still there and won&apos;t stop you from using &lt;code&gt;event.target.WHATEVER&lt;/code&gt;
in your code!&lt;/p&gt;
&lt;p&gt;You may also notice there are not a lot of callable events, like &lt;code&gt;preventDefault&lt;/code&gt;. Well React Aria handles this for you
as well. No need!&lt;/p&gt;
&lt;h3&gt;The Final Output&lt;/h3&gt;
&lt;p&gt;With this, we&apos;ve create a very generic button, with accessibility in mind, that can be used in a design system. We
will leave the testing across a myriad of devices and environments to the React Aria team (thank you!).&lt;/p&gt;
&lt;p&gt;To make sure we&apos;re following the standards and guidelines, we could add the accessibility extensions to Storybook
(maybe one for next time)!&lt;/p&gt;
&lt;p&gt;I&apos;m going to continue to explore it a bit more over the next couple weeks and will post more about it!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Introducing Google Workspace Zoom Default</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>I&apos;ve added new functionality and changed the name...</description><pubDate>Thu, 30 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import NewExtensionNameBanner from &quot;../../../../components/NewExtensionNameBanner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;NewExtensionNameBanner /&amp;gt;&lt;/p&gt;
&lt;p&gt;I previously &lt;a href=&quot;../02/my-first-chrome-extension.mdx&quot;&gt;talked about&lt;/a&gt; my experience building my first Chrome Extension,
&lt;em&gt;Google Docs Zoom Default&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Since then, I&apos;ve been hard at work enabling the functionality for more Google Workspace Applications. Thus, I&apos;m
introducing &lt;em&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/google-docs-zoom-default/nflkcdlimipkgbacnfnhfecjgmojhklo&quot;&gt;Google Workspace Zoom Default&lt;/a&gt;&lt;/em&gt; (hopefully the Web Store listing updates prior to me publishing
this).&lt;/p&gt;
&lt;p&gt;This is a pretty big release as it enables the same &quot;default zoom&quot; functionality in Google Sheets. This means that this
Chrome Extension supports &quot;default zoom&quot; functionality in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Docs&lt;/li&gt;
&lt;li&gt;Google Sheets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have a look at the functionality below!&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;youtubeWrapper&quot;&amp;gt;
&amp;lt;iframe
src=&quot;https://www.youtube.com/embed/ZbcpamEBEPU&quot;
title=&quot;YouTube video player&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
allowfullscreen
&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;The Future&lt;/h2&gt;
&lt;p&gt;It took a bit of time to implement this functionality, but I can promise you it was well worth it. I took the time to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set the base for other applications, such as Google Slides&lt;/li&gt;
&lt;li&gt;Set a strategy for docs where users have &quot;view only&quot; permissions&lt;/li&gt;
&lt;li&gt;Updated the extension&apos;s popup UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once again, &lt;a href=&quot;https://www.plasmo.com/&quot;&gt;Plasmo&lt;/a&gt; made it super easy to do these things.&lt;/p&gt;
&lt;p&gt;You can see all the code on &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom&quot;&gt;Github&lt;/a&gt; for the sake of transparency.&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>&quot;Simulating&quot; JS Events</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Can you simulate events with JS?? Let&apos;s find out...</description><pubDate>Wed, 26 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import SandpackEnv from &quot;../../../../components/Sandpack&quot;;
import Banner from &quot;../../../../components/Banner/index.astro&quot;;
import {
keyboardEventsDispatchEvents,
keyboardEventsInputDotValue,
keyboardEventsInputOnly,
mouseEventsButtonOnly,
mouseEventsDotClick,
mouseEventsEventsClick,
} from &quot;./_simulating-js-events-code.js&quot;;&lt;/p&gt;
&lt;p&gt;import NewExtensionNameBanner from &quot;../../../../components/NewExtensionNameBanner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;NewExtensionNameBanner /&amp;gt;&lt;/p&gt;
&lt;p&gt;I&apos;ve been working on my &lt;em&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/google-docs-zoom-default/nflkcdlimipkgbacnfnhfecjgmojhklo&quot;&gt;Google Workspace Zoom Default&lt;/a&gt;&lt;/em&gt; Chrome Extension to implement a
&lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/issues/15&quot;&gt;requested feature&lt;/a&gt; where users can use custom zoom values in addition to the predefined zoom values
that Google provides in their application&apos;s zoom dropdown.&lt;/p&gt;
&lt;p&gt;I thought to myself, &quot;I already simulate events. This should be easy.&quot; So, I started to try and figure it out. If you
want to skip ahead, feel free to go to the &lt;a href=&quot;#trusted-keyboard-events-with-chrome-debugger&quot;&gt;&quot;Implementing with &lt;code&gt;chrome.debugger&lt;/code&gt;&quot;&lt;/a&gt;
section.&lt;/p&gt;
&lt;p&gt;But first, let&apos;s have a look at how to &quot;simulate events.&quot;&lt;/p&gt;
&lt;h2&gt;Mouse Events&lt;/h2&gt;
&lt;p&gt;Simulating mouse events is pretty simple. Or, is it?&lt;/p&gt;
&lt;p&gt;Turns out there&apos;s many, many different ways to do it. Let&apos;s start by figuring out what events are
dispatched when a user clicks the button. Try it out in the example below.&lt;/p&gt;
&lt;p&gt;&amp;lt;SandpackEnv files={mouseEventsButtonOnly.files} client:idle /&amp;gt;&lt;/p&gt;
&lt;p&gt;As you can see, there are three events that trigger when you click once (we&apos;ll only handle a single click).&lt;/p&gt;
&lt;p&gt;These are (and executed in the following order):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mousedown&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well well, there&apos;s three events that trigger. Fair enough.&lt;/p&gt;
&lt;p&gt;What is interesting though is that &lt;code&gt;click&lt;/code&gt; is at the end instead of the events instead of in the middle of the down and up
events. Something to think about 😉&lt;/p&gt;
&lt;p&gt;Let&apos;s try to replicate this now using just JS.&lt;/p&gt;
&lt;h3&gt;Using &lt;code&gt;.click()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The first option you&apos;d probably reach for (and myself), is using the &lt;code&gt;click()&lt;/code&gt; method that exists on an &lt;code&gt;HTMLElement&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s give it a shot and see how it works.&lt;/p&gt;
&lt;p&gt;&amp;lt;SandpackEnv files={mouseEventsDotClick.files} client:visible /&amp;gt;&lt;/p&gt;
&lt;p&gt;After trying that, do you see the problem with trying to use &lt;code&gt;click()&lt;/code&gt; to simulate clicks with JS?&lt;/p&gt;
&lt;p&gt;In case you didn&apos;t, the problem is that when using the &lt;code&gt;click()&lt;/code&gt; method, the only event that is triggered when simulating
the click is the &quot;click&quot; event. We are missing out on the &quot;mousedown&quot; and &quot;mouseup&quot; events.&lt;/p&gt;
&lt;p&gt;This may be fine for some things, but may not work for everything.&lt;/p&gt;
&lt;h3&gt;Dispatching Events&lt;/h3&gt;
&lt;p&gt;Let&apos;s make this a bit more bulletproof. Instead of using the &lt;code&gt;click()&lt;/code&gt; method, we can actually go about dispatching events
on the element in question. To do this you use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent&quot;&gt;&lt;code&gt;EventTarget.dispatchEvent()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Something like below would dispatch a click event on the button.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const button = document.querySelector(&quot;button&quot;);
const event = new MouseEvent(&quot;click&quot;, {
    view: window,
    bubbles: true,
    cancelable: true,
    clientX: 0,
    clientY: 0,
    button: 0,
});
button.dispatchEvent(event);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, all we need to do is make into something that will execute all three events when a button is clicked. I&apos;ve put it
in the example below. Give it a shot.&lt;/p&gt;
&lt;p&gt;&amp;lt;SandpackEnv files={mouseEventsEventsClick.files} client:visible /&amp;gt;&lt;/p&gt;
&lt;p&gt;🤯 BOOMMMMMM... Now we have finally simulated a &quot;click&quot; using JS. Finally 🔥&lt;/p&gt;
&lt;h3&gt;Gotchas (still...)&lt;/h3&gt;
&lt;p&gt;There&apos;s still some potential gotchas when it comes to simulating these events.&lt;/p&gt;
&lt;p&gt;This is due to the pesky &lt;code&gt;isTrusted&lt;/code&gt; property on the events. TL;DR - some browsers / apps will only respond to trusted
events. From &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted&quot;&gt;MDN&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The isTrusted read-only property of the &lt;code&gt;Event&lt;/code&gt; interface is a boolean value that is true when the event was generated by a user action, and false when the event was created or modified by a script or dispatched via &lt;code&gt;EventTarget.dispatchEvent()&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This property cannot be faked, so when something won&apos;t happen due to this, you&apos;re pretty much out of luck (aside from
what I&apos;ll show you &lt;a href=&quot;#implementing-with-chrome-debugger&quot;&gt;below&lt;/a&gt;, which is a hack and &lt;strong&gt;only&lt;/strong&gt; available to Chrome
Extensions).&lt;/p&gt;
&lt;p&gt;While this isn&apos;t a problem (for the most part) when simulating Mouse Events, it does become a problem when trying to
simulate Keyboard Events, which I&apos;ll demonstrate in the next section.&lt;/p&gt;
&lt;h2&gt;Keyboard Events&lt;/h2&gt;
&lt;p&gt;Cool cool. Keyboard Events are going to be just as easy. You would&apos;ve thought...&lt;/p&gt;
&lt;p&gt;Let&apos;s see what triggers a Keyboard Event.&lt;/p&gt;
&lt;p&gt;&amp;lt;SandpackEnv files={keyboardEventsInputOnly.files} client:visible /&amp;gt;&lt;/p&gt;
&lt;p&gt;Depending on what key you hit the following events will fire(in order):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;keydown&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;keypress&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;keyup&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;or, if hitting a key like &quot;Shift&quot;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;keydown&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;keyup&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we&apos;ve gotten these events down, let&apos;s try to simulate them. Good thing that we have &lt;code&gt;KeyboardEvent&lt;/code&gt;s that can
be dispatched just like &lt;code&gt;MouseEvent&lt;/code&gt;s. Let&apos;s give it a shot.&lt;/p&gt;
&lt;p&gt;&amp;lt;SandpackEnv files={keyboardEventsDispatchEvents.files} client:visible /&amp;gt;&lt;/p&gt;
&lt;p&gt;Notice anything &lt;em&gt;interesting&lt;/em&gt;?? In case you were too fixated on the &quot;Console&quot; window, have a look at the input. Unfortunately,
while the events did fire there is nothing in the input box.&lt;/p&gt;
&lt;p&gt;Remember how I talked about the &lt;code&gt;isTrusted&lt;/code&gt; property on the &lt;code&gt;Event&lt;/code&gt; (see above)? Well, this is very much what is happening
here. The browser is being smart enough to not let scripts insert values in to input boxes. Basically, it&apos;s a security
concern and browsers are right in doing this.&lt;/p&gt;
&lt;p&gt;So, where do we go from here? I still want to simulate these events. Maybe we can change the input&apos;s value using JS.
Let&apos;s give it a shot.&lt;/p&gt;
&lt;p&gt;&amp;lt;SandpackEnv files={keyboardEventsInputDotValue.files} client:visible /&amp;gt;&lt;/p&gt;
&lt;p&gt;Perfect. It looks like my script is typing in the box. Before you celebrate, what is happening in the &quot;Console&quot; window?&lt;/p&gt;
&lt;p&gt;Turns out, nothing is happening. There are no events being triggered despite us having listeners on them. So, we are not
&quot;simulating&quot; events.&lt;/p&gt;
&lt;p&gt;Unfortunately, what we can do with &lt;code&gt;MouseEvent&lt;/code&gt;s is not possible with &lt;code&gt;KeyboardEvents&lt;/code&gt; due to browser security issues. We
can get pretty far in making it either:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Dispatch the correct events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OR...&lt;/strong&gt; emulate typing&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Trusted Keyboard Events with Chrome Debugger&lt;/h2&gt;
&lt;p&gt;So, if all that&apos;s not possible, how am I going to implement the functionality in &lt;em&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/google-docs-zoom-default/nflkcdlimipkgbacnfnhfecjgmojhklo&quot;&gt;Google Workspace Zoom Default&lt;/a&gt;&lt;/em&gt;
allowing my users to input custom zoom levels?&lt;/p&gt;
&lt;p&gt;Let&apos;s explore a &quot;workaround&quot; by using the Chrome Debugger available to Chrome Extensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;🚨 This only works with Chrome Extensions that have access to the &lt;a href=&quot;https://developer.chrome.com/docs/extensions/reference/debugger/&quot;&gt;&lt;code&gt;chrome.debugger&lt;/code&gt; API&lt;/a&gt; 🚨&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It turns out that Chrome Extensions can request access to the &lt;code&gt;chrome.debugger&lt;/code&gt; API.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As I have to ask for an elevated set of permissions to make this work, I will be releasing a separate extension containing
this functionality. I don&apos;t want to force people into using something with which they&apos;re not comfortable.&lt;/p&gt;
&lt;p&gt;To start, this approach has the content script message the Background Service Worker (BGSW). The BGSW then attaches the
debugger, generates the necessary commands, and detaches the debugger.&lt;/p&gt;
&lt;p&gt;It&apos;s relatively simple. It also shows a nice banner on your browser that shows what is happening (like it should).&lt;/p&gt;
&lt;p&gt;The code looks something like below. I use &lt;a href=&quot;https://docs.plasmo.com/framework/messaging&quot;&gt;Plasmo&lt;/a&gt; to implement this messaging, but it can be implemented via standard
&lt;a href=&quot;https://developer.chrome.com/docs/extensions/mv3/messaging/&quot;&gt;Message passing&lt;/a&gt;. Checkout &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/pull/20&quot;&gt;the PR&lt;/a&gt;
for more context on the changes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// `req` is the request sent from the content script to the BGSW
const target = { tabId: req.tabId };

chrome.debugger.attach(target, &quot;1.0&quot;);

chrome.debugger.sendCommand(target, &quot;Input.insertText&quot;, {
    text: req.body.zoomValue, // this value is sent from the content script
});

// The two events below have to be used together. I don&apos;t know why... but they do...
chrome.debugger.sendCommand(target, &quot;Input.dispatchKeyEvent&quot;, {
    type: &quot;rawKeyDown&quot;,
    code: &quot;Enter&quot;,
    key: &quot;Enter&quot;,
    macCharCode: 13,
    nativeVirtualKeyCode: 13,
    text: &quot;\r&quot;,
    unmodifiedText: &quot;\r&quot;,
    windowsVirtualKeyCode: 13,
});
chrome.debugger.sendCommand(target, &quot;Input.dispatchKeyEvent&quot;, {
    type: &quot;char&quot;,
    text: &quot;\r&quot;,
});

chrome.debugger.detach(target);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ll hopefully be releasing the new Chrome Extension with this functionality soon 🤞&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Astro Content Collection Month and Year Pages</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Shall we create pages to list posts by year and month?!? Yes, of course!</description><pubDate>Mon, 08 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;small style=&quot;display:block; text-align: center;&quot;&amp;gt;🚨 &lt;strong&gt;&lt;em&gt;I assume we&apos;re building a static site in this post&lt;/em&gt;&lt;/strong&gt; 🚨&amp;lt;/small&amp;gt;&lt;/p&gt;
&lt;p&gt;I thought I&apos;d finally gotten familiar with &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; (see my &lt;a href=&quot;/blog/tags/astro&quot;&gt;other posts&lt;/a&gt;), but I was wrong.&lt;/p&gt;
&lt;p&gt;It all started with me having trouble finding the post I wanted to update solely based on file name/slug. I thought more
structure could help with this, so I made a change.&lt;/p&gt;
&lt;p&gt;I switched from &quot;the wild west&quot; to organising my posts by year and month. As of now, my Content Collection&apos;s directory
structure looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/content/
└── blog
    └── 2023
        ├── 01
        │   └── a-new-blog-why.md
        ├── 02
        │   ├── 1-astro-thus-far.md
        │   ├── adding-comments-to-this-thing.md
        │   ├── how-do-search-this-thing.md
        │   └── my-first-chrome-extension.md
        ├── 03
        │   ├── 1-react-aria-exploration.md
        │   └── introducing-google-workspace-zoom-default.md
        ├── 04
        │   └── simulating-js-events.mdx
        └── 05
            └── astro-content-collection-month-and-year-pages.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I really liked the new version as it bring more structure, but I quickly noticed something wasn&apos;t quite right.&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When I navigated to https://words.byvernacchia.com/blog/2023 or https://words.byvernacchia.com/blog/2023/02, instead of seeing a
page that has all the posts for the year, or for the month of the year, I got a big ole &quot;404 Not Found.&quot; Lovely.&lt;/p&gt;
&lt;p&gt;I thought the &quot;magic&quot; of Astro would automatically create these pages for me. I was sorely mistaken 😔&lt;/p&gt;
&lt;p&gt;After scouring their docs and posting &lt;a href=&quot;https://discord.com/channels/830184174198718474/1097931916025544845/1097931916025544845&quot;&gt;a question on the Astro Discord&lt;/a&gt;,
I realised there wasn&apos;t much help out there. So, I got down to it.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Creating pages that display &quot;Posts by Year&quot; and &quot;Posts by Month of the Year&quot; both rely on the same underlying Astro APIs,
&lt;a href=&quot;https://docs.astro.build/en/core-concepts/routing/#dynamic-routes&quot;&gt;Dynamic Routes&lt;/a&gt; and
&lt;a href=&quot;https://docs.astro.build/en/reference/api-reference/#getstaticpaths&quot;&gt;&lt;code&gt;getStaticPaths()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s see how we can use these together to solve our problem.&lt;/p&gt;
&lt;h3&gt;Posts by Year&lt;/h3&gt;
&lt;p&gt;While we can use this strategy for any Content Collection, I will be using &quot;&lt;code&gt;blog&lt;/code&gt;&quot; as per my example above.&lt;/p&gt;
&lt;p&gt;Let&apos;s start by creating a page at &lt;code&gt;src/pages/blog/[year]/index.astro&lt;/code&gt;. This is our &quot;Dynamic Route&quot; that will generate
pages for every year. We can then use these pages to display the posts for that year. You can see &lt;code&gt;[year]&lt;/code&gt; directory,
which we&apos;ll use later.&lt;/p&gt;
&lt;p&gt;Next, we add our &lt;code&gt;getStaticPaths()&lt;/code&gt; function to the page we just created.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import path from &quot;path&quot;;
import fs from &quot;fs&quot;;

// Helper function that returns an array of directory
// names one level down from the given directory
const getDirsInDir = (dir: string) =&amp;gt; {
    return fs
        .readdirSync(dir, { withFileTypes: true })
        .filter((item) =&amp;gt; item.isDirectory())
        .map((item) =&amp;gt; item.name);
};

export async function getStaticPaths() {
    // Define the absolute path to the base directory of the Content Collection
    const collectionDir = path.resolve(process.cwd(), &quot;./src/content/blog/&quot;);

    // Create an array containing all top-level directories in the Content Collection (i.e. years)
    const yearsInDir = getDirsInDir(collectionDir);

    // Return an array of objects that Astro expects for it to build static paths
    // See https://docs.astro.build/en/reference/api-reference/#params
    return yearsInDir.map((yearDir) =&amp;gt; {
        return {
            params: { year: yearDir },
        };
    });
}
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After doing this, when running &lt;code&gt;astro build&lt;/code&gt; the output will show something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;▶ src/pages/blog/[year]/index.astro
  └─ /blog/2023/index.html (+7ms)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I were to add a &lt;code&gt;2024&lt;/code&gt; folder, I would generate a &lt;code&gt;/blog/2024/index.html&lt;/code&gt; page.&lt;/p&gt;
&lt;p&gt;One more thing to do. Let&apos;s display the posts for that year on the page. To do this, we can use Astro&apos;s
&lt;a href=&quot;https://docs.astro.build/en/reference/api-reference/#getcollection&quot;&gt;&lt;code&gt;getCollection&lt;/code&gt;&lt;/a&gt; (outside of &lt;code&gt;getStaticPaths()&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import { getCollection } from &quot;astro:content&quot;;

// ... prior getStaticPaths() definition

const posts = await getCollection(&quot;blog&quot;, ({ data }) =&amp;gt; {
    // Dynamic route value (from the &quot;[year]&quot; directory)
    const { year } = Astro.params;

    // Filter posts based on &quot;pubDate&quot; value and &quot;year&quot; param
    return data.pubDate.getFullYear().toString() === year;
});
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the filtering of posts by year uses the post&apos;s frontmatter data (i.e. &lt;code&gt;pubDate&lt;/code&gt;), but it will likely
be different for you.&lt;/p&gt;
&lt;h3&gt;Posts by Month of the Year&lt;/h3&gt;
&lt;p&gt;Displaying Posts by Month of the Year is basically the same, but with some extra steps.&lt;/p&gt;
&lt;p&gt;Let&apos;s start by creating a page at &lt;code&gt;src/pages/blog/[year]/[month]/index.astro&lt;/code&gt;. This time, instead of just &lt;code&gt;[year]&lt;/code&gt; we&apos;ve
added &lt;code&gt;[month]&lt;/code&gt;. We&apos;ll use both of these &quot;dynamic routes&quot; later.&lt;/p&gt;
&lt;p&gt;Just like before, we not add our &lt;code&gt;getStaticPaths()&lt;/code&gt; function to the page we just created.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import path from &quot;path&quot;;
import fs from &quot;fs&quot;;

// Helper function that returns an array of directory
// names one level down from the given directory
const getDirsInDir = (dir: string) =&amp;gt; {
    return fs
        .readdirSync(dir, { withFileTypes: true })
        .filter((item) =&amp;gt; item.isDirectory())
        .map((item) =&amp;gt; item.name);
};

export async function getStaticPaths() {
    // Define the absolute path to the base directory of the Content Collection
    const collectionDir = path.resolve(process.cwd(), &quot;./src/content/blog/&quot;);

    // Create an array containing all top-level directories in the Content Collection (i.e. years)
    const yearsInDir = getDirsInDir(collectionDir);

    // Iterate via `flatMap` (to combine return values) through each &quot;year&quot; directory
    const paths = yearsInDir.flatMap((yearDiretory) =&amp;gt; {
        // Create the absolute path to the directory
        const yearDir = path.resolve(
            process.cwd(),
            &quot;./src/content/blog/&quot;,
            yearDiretory,
        );

        // Create an array containing all the top-level directories in the directory (i.e. months)
        const monthsInYearDir = getDirsInDir(yearDir);

        // Create and return objects that Astro expects for it to build static paths using year and month
        // See https://docs.astro.build/en/reference/api-reference/#params
        const fullPaths = monthsInYearDir.map((monthDirectory) =&amp;gt; {
            return {
                params: { year: yearDiretory, month: monthDirectory },
            };
        });

        return fullPaths;
    });

    // Return the array of objects
    return paths;
}
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks quite familiar to the &lt;code&gt;getStaticPaths()&lt;/code&gt; function we created to list our posts for each year, doesn&apos;t it?&lt;/p&gt;
&lt;p&gt;After doing this, when running &lt;code&gt;astro build&lt;/code&gt; the output will show something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;▶ src/pages/blog/[year]/[month]/index.astro
  ├─ /blog/2023/01/index.html (+3ms)
  ├─ /blog/2023/02/index.html (+6ms)
  ├─ /blog/2023/03/index.html (+9ms)
  ├─ /blog/2023/04/index.html (+12ms)
  └─ /blog/2023/05/index.html (+15ms)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I were to add some posts in January 2024, Astro would then generate a &lt;code&gt;/blog/2024/01/index.html&lt;/code&gt; page.&lt;/p&gt;
&lt;p&gt;Lastly, we have to display the posts for said year and month on the page.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import { getCollection } from &quot;astro:content&quot;;

// ... prior getStaticPaths() definition

const posts = await getCollection(&quot;blog&quot;, ({ data }) =&amp;gt; {
    // Dynamic route value (from the &quot;[year]&quot; and &quot;[month]&quot; directories)
    const { year, month } = Astro.params;

    // Check if post matches current year of page
    const isYear = data.pubDate.getFullYear().toString() === year;

    // Check if post matches current month of page
    // 🤔 Remember, `Date.prototype.getMonth()` is zero-based (where zero indicates the first month of the year).
    const isMonth = data.pubDate.getMonth() === parseInt(month, 10) - 1;

    // Return boolean indicating if post is of year and month
    return isMonth &amp;amp;&amp;amp; isYear;
});
---
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;And There We Have It!&lt;/h2&gt;
&lt;p&gt;My blog (or your blog) now creates pages that will display a lists of posts by year and/or month of the year! To confirm,
you can check out https://words.byvernacchia.com/blog/2023 or https://words.byvernacchia.com/blog/2023/02 (now they work).&lt;/p&gt;
&lt;p&gt;Maybe I do have the hang of Astro!&lt;/p&gt;
&lt;p&gt;You can use my blog as an example for the full integration. Or, post a comment below, and we can have a discusion!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vernak2539/words-byvernacchia/blob/main/src/pages/blog/%5Byear%5D/index.astro&quot;&gt;Posts by year&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vernak2539/words-byvernacchia/blob/main/src/pages/blog/%5Byear%5D/%5Bmonth%5D/index.astro&quot;&gt;Posts by month of the year&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you&apos;re developing with Windows, you&apos;ll have to join your paths differently in some of the functions above (i.e. with &lt;code&gt;path.sep&lt;/code&gt;).&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>JS Framework Evaluations</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>I&apos;m going to use this time to define the frameworks I&apos;ll be looking at and requirements...</description><pubDate>Thu, 18 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I stated in &lt;a href=&quot;../01/a-new-blog-why.md&quot;&gt;my original post&lt;/a&gt;, I want to evaluate the frontend frameworks that have been
created &quot;recently.&quot;&lt;/p&gt;
&lt;p&gt;I plan to delve into:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.solidjs.com/&quot;&gt;SolidJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vuejs.org/&quot;&gt;Vue.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qwik.builder.io/&quot;&gt;Qwik&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://htmx.org/&quot;&gt;htmx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lit.dev/&quot;&gt;Lit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; &lt;em&gt;(kind already started haha)&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In addition, I&apos;m also planning to explore &quot;higher-level frameworks&quot; that supplement these, such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remix.run/&quot;&gt;Remix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kit.svelte.dev/&quot;&gt;SvelteKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://start.solidjs.com/&quot;&gt;SolidStart&lt;/a&gt; &lt;em&gt;(May wait until this one is out of beta, or may not 🤷)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nuxtjs.org/&quot;&gt;Nuxt&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;** &lt;strong&gt;Update&lt;/strong&gt; **&lt;/p&gt;
&lt;p&gt;Instead of building a Pokédex, I&apos;ll be building a &lt;em&gt;Github Issue Viewer&lt;/em&gt; (and creator). This will allow
for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Login (via Github)&lt;/li&gt;
&lt;li&gt;Playing around with APIs&lt;/li&gt;
&lt;li&gt;Handling form submissions (basic issue creation)&lt;/li&gt;
&lt;li&gt;Page transitions&lt;/li&gt;
&lt;li&gt;Filtering&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;m going to be exploring some technologies to make life easier and develop more quickly. They include things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RadixUI&lt;/li&gt;
&lt;li&gt;shadcn components&lt;/li&gt;
&lt;li&gt;React Aria Components&lt;/li&gt;
&lt;li&gt;etc...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;ll update you as I go!&lt;/p&gt;
&lt;p&gt;** &lt;strong&gt;Old (not being done anymore)&lt;/strong&gt; **&lt;/p&gt;
&lt;p&gt;Alright, so I&apos;m not about to dive into this without a game plan. I&apos;ve got a project in mind to make this exploration a
bit more fun. Let&apos;s build a Pokédex, using the &lt;a href=&quot;https://pokeapi.co/&quot;&gt;PokéAPI&lt;/a&gt;! You&apos;ll be able to check out different
Pokémon and even whip up your own. This will allow me to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Play around with APIs&lt;/li&gt;
&lt;li&gt;Handle form submissions&lt;/li&gt;
&lt;li&gt;Understand pages transitions&lt;/li&gt;
&lt;li&gt;And, who knows, maybe throw in a few more bells and whistles along the way!&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Relative Markdown Paths in Astro via Rehype</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Want to use relative paths in md/mdx files to link to other md/mdx files? Now you can!</description><pubDate>Thu, 11 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I stared using Astro and it blew my mind. It did everything I needed and even things I didn&apos;t know I needed. It upped
my static site/SSG game very quickly.&lt;/p&gt;
&lt;p&gt;After a couple blog posts I noticed something weird. When I clicked on a link, it gave me a 404. What gives??&lt;/p&gt;
&lt;p&gt;I investigated further and realised that Astro doesn&apos;t modify the MD/MDX content of a Content Collection. This meant
when I had a Markdown link that looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[relative link](./other-markdown.md)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would be rendered exactly the same:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;./other-markdown.md&quot;&amp;gt;relative link&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I assumed that Astro would transform these paths when it built the site. Unfortunately, I made an ass out of me (not you
though, you&apos;re perfect).&lt;/p&gt;
&lt;p&gt;Turns out I wasn&apos;t the only one surprised by this &quot;missing&quot; functionality. There&apos;s multiple issues
(&lt;a href=&quot;https://github.com/withastro/astro/issues/5682&quot;&gt;#5682&lt;/a&gt;, &lt;a href=&quot;https://github.com/withastro/astro/issues/5680&quot;&gt;#5680&lt;/a&gt;) and
an RFC (&lt;a href=&quot;https://github.com/withastro/roadmap/discussions/424&quot;&gt;#424&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;After reviewing all the issues and discussions it didn&apos;t seem like the Astro team would be focusing on fixing this
problem anytime soon, so I decided to try my hand at it.&lt;/p&gt;
&lt;p&gt;🚨 &lt;strong&gt;Update:&lt;/strong&gt; The Astro team may be solving this using &lt;a href=&quot;https://docs.astro.build/en/guides/assets/&quot;&gt;Assets&lt;/a&gt;. It&apos;s still experimental, but it&apos;s worth
trying it out! I still learned a lot!&lt;/p&gt;
&lt;p&gt;🚨 &lt;strong&gt;Update 2:&lt;/strong&gt; I tried the experimental Assets feature out, but it didn&apos;t seem to work (I also misunderstood). Maybe
we&apos;ll see if it can go directly in Astro 🤷&lt;/p&gt;
&lt;h2&gt;Enter rehype (and remark)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/remarkjs/remark&quot;&gt;remark&lt;/a&gt; and &lt;a href=&quot;https://github.com/rehypejs/rehype&quot;&gt;reyhpe&lt;/a&gt; are part of the &lt;a href=&quot;https://unifiedjs.com/&quot;&gt;@unifiedjs collective&lt;/a&gt; and are used to
transform Markdown and HTML respectively. They can even be used in combination.&lt;/p&gt;
&lt;p&gt;rehype:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...is an ecosystem of plugins that work with HTML as structured data, specifically ASTs (abstract syntax trees). ASTs make it easy for programs to deal with HTML. We call those programs plugins. Plugins inspect and change trees.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;remark:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...is a tool that transforms markdown with plugins. These plugins can inspect and change your markup.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;TL;DR - rehype = HTML transforms. remark = Markdown transforms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As I write this, I realise the plugin I created could be a Remark plugin as well. I&apos;ll explore that in the
&lt;em&gt;&lt;a href=&quot;#rehype-vs-remark&quot;&gt;rehype vs remark&lt;/a&gt;&lt;/em&gt; section below.&lt;/p&gt;
&lt;p&gt;Anyway, these are some of the best frameworks that support modifying of Markdown and HTML via AST transforms, which is
exactly what I needed to do.&lt;/p&gt;
&lt;p&gt;Astro also supports both rehype and remark plugins! We&apos;re in luck, so let&apos;s get started.&lt;/p&gt;
&lt;h2&gt;Enter &lt;code&gt;astro-rehype-relative-markdown-links&lt;/code&gt; (my plugin)&lt;/h2&gt;
&lt;p&gt;🚨 I built this for Astro exclusively and made a couple assumptions. They are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You are rendering a static site (i.e. not using SSR)&lt;/li&gt;
&lt;li&gt;You have a content collection residing at &lt;code&gt;src/content/&amp;lt;content_collection&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You have a page that renders the above content collection at &lt;code&gt;src/pages/&amp;lt;content_collection&amp;gt;/[...slug].astro&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt; &lt;a href=&quot;https://github.com/vernak2539/astro-rehype-relative-markdown-links&quot;&gt;Github&lt;/a&gt; • &lt;a href=&quot;https://www.npmjs.com/package/astro-rehype-relative-markdown-links&quot;&gt;NPM&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Essentially, this plugin will look through the AST created by rehype and look for the &lt;code&gt;href&lt;/code&gt; nodes. Once it finds these
nodes, it will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check if the path is a candidate to be transformed. The following criteria have to be met:
&lt;ul&gt;
&lt;li&gt;The path is not empty&lt;/li&gt;
&lt;li&gt;The path does not represent an absolute path&lt;/li&gt;
&lt;li&gt;The file has an extension of &lt;code&gt;.md&lt;/code&gt; or &lt;code&gt;.mdx&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Check to see if the path is a valid one (i.e. the relative file exists in relation to the current file)&lt;/li&gt;
&lt;li&gt;Determine if the relative file has a &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/#defining-custom-slugs&quot;&gt;custom slug&lt;/a&gt;
defined in the frontmatter, and use it if available&lt;/li&gt;
&lt;li&gt;Build and return the final path that is compatible with the final HTML generated by Astro&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I didn&apos;t go too in-depth, but feel free to check out &lt;a href=&quot;https://github.com/vernak2539/astro-rehype-relative-markdown-links&quot;&gt;the code&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Usage&lt;/h3&gt;
&lt;p&gt;If you want to try it out, simply install the plugin and follow the configuration steps below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add astro-rehype-relative-markdown-links
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;astro.config.mjs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import rehypeAstroRelativeMarkdownLinks from &quot;astro-rehype-relative-markdown-links&quot;;

// ...everything else

export default defineConfig({
    // ...everything else
    markdown: {
        rehypePlugins: [rehypeAstroRelativeMarkdownLinks],
    },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After integrating the plugin, the following link that is inside a &lt;code&gt;src/content/blog/post.md&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[relative link](./other-markdown.md)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Would be rendered as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;/blog/other-markdown&quot;&amp;gt;relative link&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;rehype vs remark&lt;/h2&gt;
&lt;p&gt;Like I mentioned above, should this plugin be a rehype or remark plugin? As it stands, &lt;code&gt;astro-rehype-relative-markdown-links&lt;/code&gt;
is a rehype plugin. Did I make the right choice??? 🤔&lt;/p&gt;
&lt;p&gt;I think I did. Let me explain.&lt;/p&gt;
&lt;p&gt;remark is used to transform markdown files, while rehype transforms HTML. It really could&apos;ve gone either way (i.e.
transform path in Markdown or HTML), but I choose to transform the link (i.e. &lt;code&gt;href&lt;/code&gt;) when converting the AST to the
final HTML output. Thus, rehype was the choice.&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Google Workspace Zoom Default - Extended</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>A &quot;new&quot; extension with custom zoom levels...</description><pubDate>Thu, 08 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import NewExtensionNameBanner from &quot;../../../../components/NewExtensionNameBanner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;NewExtensionNameBanner /&amp;gt;&lt;/p&gt;
&lt;p&gt;Following my blog post where I explore &lt;a href=&quot;../04/simulating-js-events.mdx&quot;&gt;&lt;em&gt;&quot;Simulating&quot; JS Events&lt;/em&gt;&lt;/a&gt;, I finally did
the work to implement custom zoom levels in my Chrome Extension, &lt;a href=&quot;https://chrome.google.com/webstore/detail/google-docs-zoom-default/nflkcdlimipkgbacnfnhfecjgmojhklo&quot;&gt;&lt;em&gt;Google Workspace Zoom Default&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But, I actually didn&apos;t implement the functionality it in my existing extension. Why? You may ask.&lt;/p&gt;
&lt;p&gt;Well, in order to use the &lt;a href=&quot;https://developer.chrome.com/docs/extensions/reference/debugger/&quot;&gt;&lt;code&gt;chrome.debugger&lt;/code&gt; API&lt;/a&gt;, I
have to request elevated permissions from the person using my extension. If I were to do this in my existing extension,
all users would have to explicitly accept the new permissions I&apos;ve requested.&lt;/p&gt;
&lt;p&gt;This gives users the following options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the extension and re-enable it, accepting new permissions&lt;/li&gt;
&lt;li&gt;Open the extension and actively choose &lt;em&gt;not&lt;/em&gt; to re-enable it due to new permissions&lt;/li&gt;
&lt;li&gt;Not notice the extension has stopped working and it stays disabled&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, #2 and #3 are the most likely situation. Obviously, these are the worst possible outcomes for the people using
my extension, and I want to enable as many people as possible to use the functionality I provide.&lt;/p&gt;
&lt;p&gt;Based on this, I introduce the &lt;a href=&quot;https://chrome.google.com/webstore/detail/google-workspace-zoom-def/mdgikencgfhineaememjagpkiclbdkka&quot;&gt;&lt;em&gt;Google Workspace Zoom Default - Extended&lt;/em&gt;&lt;/a&gt; extension. This extension
includes all the functionality of the regular extension &lt;strong&gt;AND&lt;/strong&gt; allows for custom zoom values.&lt;/p&gt;
&lt;p&gt;I&apos;ve updated my YouTube video to demostrate the new functionality.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;youtubeWrapper&quot;&amp;gt;
&amp;lt;iframe
width=&quot;560&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube.com/embed/WYmmMaQXE7Y?start=17.5&quot;
title=&quot;YouTube video player&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
allowfullscreen
&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;Hope this helps someone!&lt;/p&gt;
&lt;h2&gt;Past Context&lt;/h2&gt;
&lt;p&gt;I&apos;ve written about building these Chrome Extensions previously! Feel free to have a look to get some context if you want!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;../02/my-first-chrome-extension.mdx&quot;&gt;My First Chrome Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../03/introducing-google-workspace-zoom-default.mdx&quot;&gt;Introducing Google Workspace Zoom Default&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As always, you can see all the code on &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom&quot;&gt;Github&lt;/a&gt; for the sake of transparency.&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>View-only Default Zoom</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Let&apos;s talk about view-only permissions in Google Workspace...</description><pubDate>Tue, 08 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import NewExtensionNameBanner from &quot;../../../../components/NewExtensionNameBanner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;NewExtensionNameBanner /&amp;gt;&lt;/p&gt;
&lt;p&gt;One of the long-standing pieces of functionality I&apos;ve wanted to implement in the &quot;Google Workspace Zoom Default&quot; extension
(&lt;a href=&quot;https://chrome.google.com/webstore/detail/google-docs-zoom-default/nflkcdlimipkgbacnfnhfecjgmojhklo&quot;&gt;web store&lt;/a&gt;, &lt;a href=&quot;https://chrome.google.com/webstore/detail/google-workspace-zoom-def/mdgikencgfhineaememjagpkiclbdkka&quot;&gt;web store Extended&lt;/a&gt;) is the ability to use it in view-only documents.&lt;/p&gt;
&lt;p&gt;Finally, I&apos;ve submitted a new version to the web store (&lt;code&gt;0.6.1&lt;/code&gt;), which should be rolling out quite soon (pending an approval)!&lt;/p&gt;
&lt;h2&gt;The Problems&lt;/h2&gt;
&lt;p&gt;Docs is the only Workspace application where the default implementation doesn&apos;t work when the viewer has view-only permissions.
This is because there is no select element shown to enable changing the zoom value.&lt;/p&gt;
&lt;p&gt;Instead, a user has to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click into the &quot;View&quot; tab in the menu bar&lt;/li&gt;
&lt;li&gt;Click/hover over the zoom option&lt;/li&gt;
&lt;li&gt;Click the desired zoom value&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&apos;s all good clicking the &quot;View&quot; tab in the menu bar as it has an ID on the HTML element. But, this is where it starts
to get a bit more wonky.&lt;/p&gt;
&lt;p&gt;After clicking the tab, a dropdown appears. There is no indication of what dropdown applies to what tab, like there is
elsewhere (it&apos;s essentially what allowed me to create the main functionality).&lt;/p&gt;
&lt;p&gt;The same situation that happens when you hover or click the zoom option. It shows another dropdown with no indication
to what it relates.&lt;/p&gt;
&lt;p&gt;To address this lacking linkage, I have to locate the dropdowns in the least optimal way: by identifying text in an ARIA
attribute. There are so many problems with this approach, I don&apos;t even know where to begin. But, here we are, playing
with the hand we were dealt by the Google team.&lt;/p&gt;
&lt;h2&gt;It&apos;s Experimental&lt;/h2&gt;
&lt;p&gt;Given the problems outlined above, I didn&apos;t feel confident enough to release this as a default feature. Instead, I&apos;ve put
it behind an experimental flag. This means that it&apos;s disabled by default, and users have to enable it in the options page.&lt;/p&gt;
&lt;p&gt;This approach gives me a bit more freedom to experiment with the implementation in the future while allowing users to
disable it if it doesn&apos;t work for them.&lt;/p&gt;
&lt;h2&gt;Extras&lt;/h2&gt;
&lt;p&gt;I also did some extra work, which was nice, to revamp how I use Sentry. I ended up separating Sentry usage into two projects,
one for each extension. This will help me troubleshoot errors much better!&lt;/p&gt;
&lt;h2&gt;Past Context&lt;/h2&gt;
&lt;p&gt;I&apos;ve written about building these Chrome Extensions previously! Feel free to have a look to get some context if you want!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;../02/my-first-chrome-extension.mdx&quot;&gt;My First Chrome Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../03/introducing-google-workspace-zoom-default.mdx&quot;&gt;Introducing Google Workspace Zoom Default&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../06/google-workspace-zoom-default-extended.mdx&quot;&gt;Google Workspace Zoom Default - Extended&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As always, you can see all the code on &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom&quot;&gt;Github&lt;/a&gt; for the sake of transparency.&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Optimising CI/CD Processes - Caching (all of it)</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Hmmm, one of the hardest things in software engineering (other than naming things)</description><pubDate>Thu, 07 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ℹ️ &lt;strong&gt;&lt;em&gt;This is a multipart series exploring CI/CD optimisations&lt;/em&gt;&lt;/strong&gt; ℹ️&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-processes.md&quot;&gt;Intro!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-identifying-common-steps.md&quot;&gt;Identifying Common Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Caching + Docker (and layer caching)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-test-parallelization.md&quot;&gt;Test Parallelization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-hopper-upgrades.md&quot;&gt;Hopper Configuration Upgrade (Deliveroo-specific)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Let&apos;s get on the same page!&lt;/h2&gt;
&lt;p&gt;A few things before we get started (I know you may have already read this. If so, skip this section).&lt;/p&gt;
&lt;p&gt;First, I&apos;m going to talk very specifically here about JS/TS projects, but these ideas can be applied to all CI/CD
processes.&lt;/p&gt;
&lt;p&gt;Second, I&apos;m going to use &lt;a href=&quot;https://circleci.com/&quot;&gt;CircleCI&lt;/a&gt; as the CI/CD platform when talking through examples. These
concepts can likely apply to other CI/CD platforms.&lt;/p&gt;
&lt;p&gt;Lastly, some definitions so we’re all on the same page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step&lt;/strong&gt;: A step is a single unit of work in a CI/CD process
&lt;ul&gt;
&lt;li&gt;For example, installing dependencies, setting up environment variables, initiating commands, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job&lt;/strong&gt;: A collection of steps
&lt;ul&gt;
&lt;li&gt;For example, running tests, linting code, building a Docker image, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build Pipeline&lt;/strong&gt;: A collection of &lt;em&gt;jobs&lt;/em&gt; that represents all work&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Caching&lt;/h2&gt;
&lt;p&gt;Our build pipeline is identical between code changes (unless explicitly changed).&lt;/p&gt;
&lt;p&gt;This means we can, and should, try our best to reuse outputs from previous builds to speed up the next build.&lt;/p&gt;
&lt;p&gt;You know where I&apos;m going! I&apos;m saying we should cache outputs of our build pipeline for later!&lt;/p&gt;
&lt;p&gt;Some common examples in the frontend world include node modules and Next.js&apos; build output (likely loads more).&lt;/p&gt;
&lt;p&gt;CircleCI provides an easy way to configure cacahing for your build pipeline (&lt;a href=&quot;https://circleci.com/docs/caching/#basic-example-of-dependency-caching&quot;&gt;docs&lt;/a&gt;). The gist is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- restore_cache:
      name: Restore Next cache
      keys:
          # To force update the next cache, simply bump the value of the NEXTJS_CACHE_VERSION in CircleCI and re-run your build
          - nextjs-cache-v1.0-{{ .Environment.NEXTJS_CACHE_VERSION }}-{{ checksum &quot;package-lock.json&quot; }}-{{ checksum &quot;.nvmrc&quot; }}
- save_cache:
      name: Save Next cache
      # To force update the next cache, simply bump the value of the NEXTJS_CACHE_VERSION in CircleCI and re-run your build
      key: nextjs-cache-v1.0-{{ .Environment.NEXTJS_CACHE_VERSION }}-{{ checksum &quot;package-lock.json&quot; }}-{{ checksum &quot;.nvmrc&quot; }}
      paths:
          - ./.next/cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IMO the most important part in the configuration above is the value for the &lt;code&gt;key&lt;/code&gt; option. CircleCI uses this to determine if it can
reuse a previously generated cache or not.&lt;/p&gt;
&lt;p&gt;You can see from the configuration above that I&apos;m using dynamic values:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;{{ checksum &quot;package-lock.json&quot; }}&lt;/code&gt; - Don&apos;t use the cache if dependencies have changed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{ checksum &quot;.nvmrc&quot; }}&lt;/code&gt; - Don&apos;t use the cache if the version of Node.js has changed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{ .Environment.NEXTJS_CACHE_VERSION }}&lt;/code&gt; - Don&apos;t use the cache if the CircleCI environment variable has been updated
&lt;ul&gt;
&lt;li&gt;This is really helpful if you want to cache bust without releasing code.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TL;DR - If the &lt;code&gt;key&lt;/code&gt; is the same between build pipelines, then the cache will be loaded and reused, speeding up builds.&lt;/p&gt;
&lt;p&gt;We use the &lt;a href=&quot;https://circleci.com/developer/orbs/orb/circleci/node&quot;&gt;&lt;code&gt;circleci/node&lt;/code&gt; orb&lt;/a&gt; to cache our &lt;code&gt;node_modules&lt;/code&gt; directory. The configuration looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;install_deps: &amp;amp;install_deps
    steps:
        - checkout
        - node/install-packages:
              cache-version: node-modules-cache-v1.0-{{ .Environment.NODE_MODULES_CACHE_VERSION }}-{{ checksum &quot;package-lock.json&quot; }}-{{ checksum &quot;.nvmrc&quot; }}
              cache-path: ~/project/node_modules
              override-ci-command: &quot;[ ! -d node_modules ] &amp;amp;&amp;amp; npm ci --no-fund --no-audit || echo &apos;Using cached node_modules directory&apos;&quot;
              include-branch-in-cache-key: false
        - persist_to_workspace:
              root: .
              paths:
                  - node_modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reduced the installation time of node modules from ~4-5 minute to ~2 minutes consistently.&lt;/p&gt;
&lt;p&gt;When the cache needs to be regenerated (i.e. new dependencies are installed, Node.js version updated, etc.), the build
pipeline will take more time. But, after the first time this is done, we go back to ~2 minutes.&lt;/p&gt;
&lt;p&gt;Jobs that depend on this cache can then attach the workspace, not needing to install dependencies themselves!&lt;/p&gt;
&lt;h2&gt;Docker (and layer caching)&lt;/h2&gt;
&lt;p&gt;CircleCI provides a paid feature called &lt;a href=&quot;https://circleci.com/docs/docker-layer-caching/&quot;&gt;Docker Layer Caching&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Essentially, if you&apos;re using their &lt;a href=&quot;https://circleci.com/docs/building-docker-images/&quot;&gt;remote Docker environment&lt;/a&gt; to
build your Docker images, you can have benefits local layer caching in a remote environment.&lt;/p&gt;
&lt;p&gt;I&apos;m going to quickly break it down here.&lt;/p&gt;
&lt;p&gt;When building a Docker image locally for the first time, all necessary steps are executed (i.e. fresh &quot;build&quot;).&lt;/p&gt;
&lt;p&gt;When you build the image a second time, Docker is smart enough to understand what has and hasn&apos;t changed between builds.&lt;/p&gt;
&lt;p&gt;It will only execute the necessary &quot;fresh&quot; steps, using the cached outputs from previous builds when it can.&lt;/p&gt;
&lt;p&gt;This is what you get in CircleCI.&lt;/p&gt;
&lt;p&gt;For this reason, it&apos;s important to understand how Docker layer caching works at a fundamental level. Let&apos;s use the two configurations
below as examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Config 1&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM node:20.9.0-alpine
ENV NODE_ENV=production

# Copy the built app
COPY . .

# Only install production dependencies
RUN npm ci --omit=dev --ignore-scripts

# Start app
CMD npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Config 2&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM node:20.9.0-alpine
ENV NODE_ENV=production

# Only install production dependencies
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts

# Copy the built app
COPY . .

# Start app
CMD npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Config 1&lt;/strong&gt; uses a base Node.js image and:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copies the application code&lt;/li&gt;
&lt;li&gt;Installs the necessary dependencies&lt;/li&gt;
&lt;li&gt;Creates the start command&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Config 2&lt;/strong&gt; uses a base Node.js image and:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copies the &lt;code&gt;package.json&lt;/code&gt; (and lockfile)&lt;/li&gt;
&lt;li&gt;Installs the necessary dependencies&lt;/li&gt;
&lt;li&gt;Copies the application code&lt;/li&gt;
&lt;li&gt;Creates the start command&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You&apos;re probably looking at these and asking, how do the slight differences in these configurations affect the outcome??
Or, you&apos;re a smart cookie and already know!&lt;/p&gt;
&lt;p&gt;Thought about it?!?&lt;/p&gt;
&lt;p&gt;Assuming you build a Docker image whenever application code changes, &lt;strong&gt;Config 1&lt;/strong&gt; will never use layer caching when
installing dependencies, but &lt;strong&gt;Config 2&lt;/strong&gt; will (if you&apos;ve not added any new dependencies)!&lt;/p&gt;
&lt;p&gt;Why?!?!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Config 1&lt;/strong&gt; copies the application code before dependencies are installed. Because of this, the only way the layer
executing &quot;&lt;code&gt;npm ci&lt;/code&gt;&quot; would be cached is if nothing in the application code changed. Since a Docker image is built when
application code changes, this will never happen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Config 2&lt;/strong&gt; installs dependencies prior to copying the application code, allowing Docker to use its cool caching!
If new dependencies are installed no caching will be used, rather Docker will run the command and output a cached layer
for the next build.&lt;/p&gt;
&lt;p&gt;Pretty cool right?!!?&lt;/p&gt;
&lt;p&gt;Be sure to have a look at the other posts (links at the top) in this series about optimising CI/CD processes!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Optimising CI/CD Processes - Hopper Configuration Upgrade</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Staying current is always a good idea!</description><pubDate>Thu, 07 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ℹ️ &lt;strong&gt;&lt;em&gt;This is a multipart series exploring CI/CD optimisations&lt;/em&gt;&lt;/strong&gt; ℹ️&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-processes.md&quot;&gt;Intro!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-identifying-common-steps.md&quot;&gt;Identifying Common Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-caching.md&quot;&gt;Caching + Docker (and layer caching)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-test-parallelization.md&quot;&gt;Test Parallelization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hopper Configuration Upgrade (Deliveroo-specific)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Hopper configuration upgrade&lt;/h2&gt;
&lt;p&gt;Hopper is the deployment software we use at Deliveroo. It deploys our services and Lambdas to AWS. It&apos;s really cool, tbh,
but, I&apos;m slightly biased 😉.&lt;/p&gt;
&lt;p&gt;The team responsible for Hopper recently released a v2 Configuration. There&apos;s a lot of things that come with it, but mostly
it aims to streamline processes and provides additional, helpful configuration options.&lt;/p&gt;
&lt;p&gt;The service I was working on was, unfortunately, not using this new configuration.&lt;/p&gt;
&lt;p&gt;After upgrading to v2 (which the team made super simple), we saw our deployments go from between 6-11 minutes to less
than 5 minutes consistently.&lt;/p&gt;
&lt;p&gt;Lesson here is that it always pays to stay up to date! There&apos;s loads of places this lesson can be applied, but I&apos;ll leave
you to figure them out!&lt;/p&gt;
&lt;p&gt;Be sure to have a look at the other posts (links at the top) in this series about optimising CI/CD processes!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Optimising CI/CD Processes - Identifying Common Steps</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>What?!?! We can reuse stuff??</description><pubDate>Thu, 07 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ℹ️ &lt;strong&gt;&lt;em&gt;This is a multipart series exploring CI/CD optimisations&lt;/em&gt;&lt;/strong&gt; ℹ️&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-processes.md&quot;&gt;Intro!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Identifying Common Steps&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-caching.md&quot;&gt;Caching + Docker (and layer caching)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-test-parallelization.md&quot;&gt;Test Parallelization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-hopper-upgrades.md&quot;&gt;Hopper Configuration Upgrade (Deliveroo-specific)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Let&apos;s get on the same page!&lt;/h2&gt;
&lt;p&gt;A few things before we get started (I know you may have already read this. If so, skip this section).&lt;/p&gt;
&lt;p&gt;First, I&apos;m going to talk very specifically here about JS/TS projects, but these ideas can be applied to all CI/CD
processes.&lt;/p&gt;
&lt;p&gt;Second, I&apos;m going to use &lt;a href=&quot;https://circleci.com/&quot;&gt;CircleCI&lt;/a&gt; as the CI/CD platform when talking through examples. These
concepts can likely apply to other CI/CD platforms.&lt;/p&gt;
&lt;p&gt;Lastly, some definitions so we&apos;re all on the same page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step&lt;/strong&gt;: A step is a single unit of work in a CI/CD process
&lt;ul&gt;
&lt;li&gt;For example, installing dependencies, setting up environment variables, initiating commands, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job&lt;/strong&gt;: A collection of steps
&lt;ul&gt;
&lt;li&gt;For example, running tests, linting code, building a Docker image, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build Pipeline&lt;/strong&gt;: A collection of &lt;em&gt;jobs&lt;/em&gt; that represents all work&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Identifying common steps&lt;/h2&gt;
&lt;p&gt;Ask yourself, &lt;em&gt;&quot;are there any steps that are common across multiple jobs?&quot;&lt;/em&gt; or &lt;em&gt;&quot;does my build pipeline have multiple
jobs with similar steps?&quot;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you answered, &lt;em&gt;&quot;Yes,&quot;&lt;/em&gt; you can have a go at extracting these similar steps into a single job, which can then be used by other jobs.&lt;/p&gt;
&lt;p&gt;Let&apos;s look at a concrete example.&lt;/p&gt;
&lt;p&gt;We have two jobs, &lt;strong&gt;test&lt;/strong&gt; and &lt;strong&gt;build&lt;/strong&gt;. Both of these jobs need the same dependencies to be installed, thus they
both have an &quot;Install Dependencies&quot; step.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    id1([Install Dependencies]) --&amp;gt; id2([&quot;Run Tests&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    id1([Install Dependencies]) --&amp;gt; id2([&quot;Build App&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &quot;Install Dependencies&quot; step is common to both. It&apos;s a prime candidate to be extracted to its own job!&lt;/p&gt;
&lt;p&gt;In the newly extracted job, we can install the dependencies and save them as an artifact, allowing for other jobs in the
Build Pipeline to use them! I&apos;ll talk more about this in &lt;a href=&quot;./optimising-ci-cd-caching.md&quot;&gt;my post about caching&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once we&apos;ve extracted the job our Build Pipeline would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    id1([Install Dependencies]) --&amp;gt; id2([&quot;Run Tests&quot;]) &amp;amp; id3([&quot;Build App&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps in many ways.&lt;/p&gt;
&lt;p&gt;First, we cut down on wasted time, and likely money, by not doing the exact same thing twice in two different
places.&lt;/p&gt;
&lt;p&gt;Second, it introduces consistency. While we should be installing the exact same dependencies everywhere (based on our
lockfile), sometimes this may not be the case.&lt;/p&gt;
&lt;p&gt;By installing dependencies in one place and reusing them, we can ensure the exact same dependencies are used for building, testing,
linting, and running our application.&lt;/p&gt;
&lt;p&gt;Be sure to have a look at the other posts (links at the top) in this series about optimising CI/CD processes!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Optimising CI/CD Processes - The Beginning</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Optimising  CI/CD processes you say?? So, what?!</description><pubDate>Fri, 08 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ℹ️ &lt;strong&gt;&lt;em&gt;This is a multipart series exploring CI/CD optimisations&lt;/em&gt;&lt;/strong&gt; ℹ️&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Intro!&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-identifying-common-steps.md&quot;&gt;Identifying Common Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-caching.md&quot;&gt;Caching + Docker (and layer caching)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-test-parallelization.md&quot;&gt;Test Parallelization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-hopper-upgrades.md&quot;&gt;Hopper Configuration Upgrade (Deliveroo-specific)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;I&apos;ll be talking about some Deliveroo-specific things in this series. When I do, I&apos;ll be sure to provide as much context
as possible.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I recently moved to a new team at &lt;a href=&quot;https://deliveroo.co.uk&quot;&gt;Deliveroo&lt;/a&gt;. I started contributing as much as possible to
one of primary codebases used in the area to get up to speed more quickly. Getting down and dirty is the best form of
learning, right?&lt;/p&gt;
&lt;p&gt;From the beginning, I noticed extremely long feedback loops when implementing changes. The process of releasing changes
to production are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Develop and test change locally&lt;/li&gt;
&lt;li&gt;Run a subset of the CI/CD process (no deployments) against the feature branch &lt;strong&gt;&lt;em&gt;(~17 minutes)&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Deployed to the staging environment
&lt;ul&gt;
&lt;li&gt;CI/CD &lt;strong&gt;&lt;em&gt;(~23 minutes)&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Deployment &lt;strong&gt;&lt;em&gt;(~6 minutes)&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Test in staging&lt;/li&gt;
&lt;li&gt;Deployed to the production environment
&lt;ul&gt;
&lt;li&gt;CI/CD &lt;strong&gt;&lt;em&gt;(~23 minutes)&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Deployment &lt;strong&gt;&lt;em&gt;(~6 minutes)&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Test in production&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&apos;ve put the durations of each step (p95) above. Excluding development and manual testing, if everything went perfectly
(does it ever?!), the whole process would take a &lt;strong&gt;minimum of ~75 minutes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;DAMNNNNN, that&apos;s a long time, huh?!?!&lt;/p&gt;
&lt;p&gt;But, this begs the question, is the CI/CD time the right thing to be measuring?&lt;/p&gt;
&lt;p&gt;I would say no. IMO (which is opinionated), we should be measuring the time it takes a team to deliver value to customers.
To put it more simply, how long does it take to get a change to production, from the moment work begins to
the moment a customer starts using it?&lt;/p&gt;
&lt;p&gt;For those of you following along, you may know where I&apos;m going next.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance&quot;&gt;Lead Time for Changes&lt;/a&gt;,
a &lt;a href=&quot;https://dora.dev/&quot;&gt;DORA&lt;/a&gt; metric, is a great metric to track, and the one I&apos;m going to focus on in this post. Disagree
with me in the comments (or don&apos;t, up to you).&lt;/p&gt;
&lt;p&gt;Lead Time for Changes encompasses much more than just CI/CD (again IMO). For example, how long it takes for a commit to
get into production also depends on our interactions with Product Manager/Owners, Designers, other engineers (via
reviews), etc.&lt;/p&gt;
&lt;p&gt;At Deliveroo, our deployment system, Hopper, tracks this metric automagically for us, which is amazing.&lt;/p&gt;
&lt;p&gt;Before I started the work I describe in this post, we had quite a high Lead Time to Change (can&apos;t really say the actual
number...).&lt;/p&gt;
&lt;p&gt;After these optimisations were implemented, we saw around a 50% reduction. More can be done to reduce this number outside
the CI/CD process.&lt;/p&gt;
&lt;p&gt;🚨 I&apos;m not saying that all the reduction is attributed to these optimisations, but after gathering qualitative
feedback from myself (so unbiased) and the teams I work with, I can say they were definitely helpful!&lt;/p&gt;
&lt;h2&gt;Let&apos;s get on the same page!&lt;/h2&gt;
&lt;p&gt;A few things before we get started.&lt;/p&gt;
&lt;p&gt;First, I&apos;m going to talk very specifically here about JS/TS projects, but these ideas can be applied to all CI/CD
processes.&lt;/p&gt;
&lt;p&gt;Second, I&apos;m going to use &lt;a href=&quot;https://circleci.com/&quot;&gt;CircleCI&lt;/a&gt; as the CI/CD platform when talking through examples. These
concepts can likely apply to other CI/CD platforms.&lt;/p&gt;
&lt;p&gt;Lastly, some definitions so we&apos;re all on the same page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step&lt;/strong&gt;: A step is a single unit of work in a CI/CD process
&lt;ul&gt;
&lt;li&gt;For example, installing dependencies, setting up environment variables, initiating commands, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job&lt;/strong&gt;: A collection of steps
&lt;ul&gt;
&lt;li&gt;For example, running tests, linting code, building a Docker image, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build Pipeline&lt;/strong&gt;: A collection of &lt;em&gt;jobs&lt;/em&gt; that represents all work&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Go learn!&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-identifying-common-steps.md&quot;&gt;Identifying Common Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-caching.md&quot;&gt;Caching + Docker (and layer caching)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-test-parallelization.md&quot;&gt;Test Parallelization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-hopper-upgrades.md&quot;&gt;Hopper Configuration Upgrade (Deliveroo-specific)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Optimising CI/CD Processes - Test Parallelization</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Let&apos;s look at how running things in parallel can help</description><pubDate>Thu, 07 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ℹ️ &lt;strong&gt;&lt;em&gt;This is a multipart series exploring CI/CD optimisations&lt;/em&gt;&lt;/strong&gt; ℹ️&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-processes.md&quot;&gt;Intro!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-identifying-common-steps.md&quot;&gt;Identifying Common Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-caching.md&quot;&gt;Caching + Docker (and layer caching)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Test Parallelization&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;./optimising-ci-cd-hopper-upgrades.md&quot;&gt;Hopper Configuration Upgrade (Deliveroo-specific)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Test parallelization&lt;/h2&gt;
&lt;p&gt;Running tests is one of the things that usually takes a fair amount of time (at least in JS/TS projects).&lt;/p&gt;
&lt;p&gt;The amount of time it takes to run tests depends on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How many tests you have&lt;/li&gt;
&lt;li&gt;What executes them&lt;/li&gt;
&lt;li&gt;Where and how they are executed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means it&apos;s important to write the right tests (maybe try TDD) and use a performant test framework.&lt;/p&gt;
&lt;p&gt;Sometimes we unfortunately can&apos;t adhere to all of these. Or, we did, once upon a time, and we&apos;re now stuck.&lt;/p&gt;
&lt;p&gt;For example, we are &lt;em&gt;&quot;stuck&quot;&lt;/em&gt; with &lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt; and will not be updating to something like &lt;a href=&quot;https://vitest.dev/&quot;&gt;vitest&lt;/a&gt; (which is fast,
but won&apos;t solve all your problems).&lt;/p&gt;
&lt;p&gt;This means we have to try other things to solve our slow running of tests. One of those being the execution of tests
in parallel.&lt;/p&gt;
&lt;h3&gt;Jest&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt; is used quite a lot across the industry, and it&apos;s rare that you find anything else in established companies
(maybe Mocha if you&apos;re lucky 😉).&lt;/p&gt;
&lt;p&gt;Jest, by default, runs tests in parallel when using single run mode:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In single run mode, this defaults to the number of the cores available on your machine minus one for the main thread.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When watching for changes, it&apos;s slightly different:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...this defaults to half of the available cores on your machine to ensure Jest is unobtrusive and does not grind your
machine to a halt.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&apos;s even recommend to use a max worker threads value of 50% in environments where CPU cores vary (i.e. CI/CD platforms):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For environments with variable CPUs available, you can use percentage based configuration: --maxWorkers=50%&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cool, that&apos;s out the of way... But, this can only get you so far.&lt;/p&gt;
&lt;p&gt;Jest also provides you the ability to shard your tests across different build agents using the &lt;a href=&quot;https://jestjs.io/docs/cli#--shard&quot;&gt;&lt;code&gt;--shard&lt;/code&gt; flag&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CircleCI allows for multiple build agents by adding a &lt;a href=&quot;https://circleci.com/docs/parallelism-faster-jobs/&quot;&gt;&lt;code&gt;parallelism: XXX&lt;/code&gt; configuration option&lt;/a&gt;
to your job. You can then update your testing step to be something along the lines of:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# npm test=&quot;jest&quot;
npm run test -- --shard=$(expr $CIRCLE_NODE_INDEX + 1)/$CIRCLE_NODE_TOTAL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will divide the tests into groups and execute them across the agents.&lt;/p&gt;
&lt;p&gt;To make the extremely clear, let&apos;s use the following example.&lt;/p&gt;
&lt;p&gt;I&apos;ve set up my CircleCI job with a &lt;code&gt;parallelism: 3&lt;/code&gt;. I have a test suite with 900 tests. CircleCI will then run
300 tests across each build agent in parallel.&lt;/p&gt;
&lt;p&gt;You can see how this could cut execution time down!&lt;/p&gt;
&lt;h3&gt;Cypress&lt;/h3&gt;
&lt;p&gt;We use &lt;a href=&quot;https://www.cypress.io/&quot;&gt;Cypress&lt;/a&gt; for our end-to-end tests. It&apos;s a great tool, but it can be slow to run tests
(even using the latest version of Cypress on Node.js v20).&lt;/p&gt;
&lt;p&gt;Thus, I went searching for ways to run Cypress tests in parallel, just like our Jest unit tests.&lt;/p&gt;
&lt;p&gt;The first thing I found is their paid product, &lt;a href=&quot;https://www.cypress.io/cloud&quot;&gt;Cypress Cloud&lt;/a&gt;. It offers exactly what I
want, parallelization (and optimisation of said parallelization).&lt;/p&gt;
&lt;p&gt;But, I&apos;m not in a position to onboard a new third party for my company, so I had to look elsewhere.&lt;/p&gt;
&lt;p&gt;After a bunch of digging, I found a simple way to do it!&lt;/p&gt;
&lt;p&gt;First, we have to &lt;a href=&quot;https://circleci.com/docs/parallelism-faster-jobs/&quot;&gt;enable &lt;code&gt;parallelism: XXX&lt;/code&gt; in our CircleCI job&lt;/a&gt; (like our Jest unit tests).&lt;/p&gt;
&lt;p&gt;Next, we have to find a way to divide our tests into groups. CircleCI makes this super simple using their command line
tool (it took me a while to find this actually).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TESTS=$(circleci tests glob &quot;cypress/e2e/**/*.cy.ts&quot; | circleci tests split | paste -sd &apos;,&apos;)

# npm e2e=&quot;cypress run&quot;
npm run e2e -- --spec &quot;$TESTS&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s break it down.&lt;/p&gt;
&lt;p&gt;First, we run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;circleci tests glob &quot;cypress/e2e/**/*.cy.ts&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generates a full list of our tests based on the glob, like below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cypress/e2e/analytics/Home.cy.ts
cypress/e2e/analytics/ViewMetrics.cy.ts
cypress/e2e/deliveryAreas/FleetSwitching.cy.ts
cypress/e2e/deliveryAreas/ViewAreasAndFees.cy.ts
cypress/e2e/liveOrders/FleetSwitching.cy.ts
cypress/e2e/liveOrders/OrderNotification.cy.ts
cypress/e2e/marketer/CancelOffer.cy.ts
cypress/e2e/marketer/CreateOfferBasket.cy.ts
cypress/e2e/marketer/CreateOfferFlashDeal.cy.ts
cypress/e2e/marketer/CreateOfferItem.cy.ts
cypress/e2e/marketer/CreateOneOffCampaign.cy.ts
cypress/e2e/marketer/CreateOverlappingOffer.cy.ts
cypress/e2e/marketer/CreateWeeklyCampaign.cy.ts
cypress/e2e/marketer/Home.cy.ts
cypress/e2e/marketer/ViewOffer.cy.ts
cypress/e2e/reportingPlatform/CreateReport.cy.ts
cypress/e2e/reportingPlatform/CreateScheduledReport.cy.ts
cypress/e2e/sales/Refunds.cy.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we pipe that response into the &lt;code&gt;circleci tests split&lt;/code&gt; command, which will split the tests into groups based on the
number of build agents we&apos;ve defined.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cypress/e2e/analytics/Home.cy.ts
cypress/e2e/deliveryAreas/ViewAreasAndFees.cy.ts
cypress/e2e/marketer/CancelOffer.cy.ts
cypress/e2e/marketer/CreateOfferItem.cy.ts
cypress/e2e/marketer/CreateWeeklyCampaign.cy.ts
cypress/e2e/reportingPlatform/CreateReport.cy.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, the response is piped to the &lt;code&gt;paste -sd &apos;,&apos;&lt;/code&gt; command, which will join the tests together with a comma.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cypress/e2e/analytics/Home.cy.ts,cypress/e2e/deliveryAreas/ViewAreasAndFees.cy.ts,cypress/e2e/marketer/CancelOffer.cy.ts,cypress/e2e/marketer/CreateOfferItem.cy.ts,cypress/e2e/marketer/CreateWeeklyCampaign.cy.ts,cypress/e2e/reportingPlatform/CreateReport.cy.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is then used by our Cypress command to run only a subset of tests.&lt;/p&gt;
&lt;h4&gt;A cheeky optimization&lt;/h4&gt;
&lt;p&gt;CircleCI has a bit of a cheeky optimisation that can be used here, which will help us shave off some time by optimising
how the tests are split. We&apos;d need to update our command like below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- circleci tests glob &quot;cypress/e2e/**/*.cy.ts&quot; | circleci tests split | paste -sd &apos;,&apos;
+ circleci tests glob &quot;cypress/e2e/**/*.cy.ts&quot; | circleci tests split --split-by=timings | paste -sd &apos;,&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, I use the &lt;a href=&quot;https://circleci.com/docs/parallelism-faster-jobs/#the-tests-run-command&quot;&gt;&lt;code&gt;--split-by&lt;/code&gt;&lt;/a&gt; argument
with a value of &lt;code&gt;timings&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You need to have the &lt;a href=&quot;https://docs.cypress.io/guides/tooling/reporters&quot;&gt;JUnit reporter&lt;/a&gt; enabled for this to
work, as that is what provides the necessary timing information to CircleCI.&lt;/p&gt;
&lt;p&gt;This is actually really cool! Since CircleCI consumes your testing framework&apos;s JUnit report, it knows how long it takes
to run individual tests.&lt;/p&gt;
&lt;p&gt;This means that when it splits them into groups for parallellization, it can do so in a way
that optimizes for the fastest execution time.&lt;/p&gt;
&lt;p&gt;For example, we have two build agents and four tests to run across them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test 1: 30 sec
Test 2: 35 sec
Test 3: 1 min
Test 4: 55 sec
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Without&lt;/strong&gt; the &lt;code&gt;--split-by=timings&lt;/code&gt; argument, the tests could be split in any fashion. This means we could end up with one
build agent taking &lt;code&gt;1m55s&lt;/code&gt; to complete, while the other takes &lt;code&gt;55s&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With&lt;/strong&gt; the &lt;code&gt;--split-by=timings&lt;/code&gt; argument, the tests will be split into groups that take the same approximate time. In
this case, we&apos;d usually get tests split so both build agents would run tests for &lt;code&gt;1m30s&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I absolutely love this, and I definitely wouldn&apos;t have thought about this myself!&lt;/p&gt;
&lt;h2&gt;Local Testing&lt;/h2&gt;
&lt;p&gt;All things related to testing, linting, building images, and any other steps in your Build Pipeline should be first class
citizen in your local development environment.&lt;/p&gt;
&lt;p&gt;To put it more bluntly, doing all these things in your local environment is essential. CI/CD should not be used as
substitute for local testing, which I&apos;ve seen many times before.&lt;/p&gt;
&lt;p&gt;Be sure to have a look at the other posts (links at the top) in this series about optimising CI/CD processes!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Goodbye Enzyme</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>You won&apos;t be missed...</description><pubDate>Fri, 19 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;style&amp;gt;
{&lt;code&gt;.twitter-tweet {     margin-left: auto;     margin-right: auto; &lt;/code&gt;}
&amp;lt;/style&amp;gt;&lt;/p&gt;
&lt;p&gt;import { Tweet } from &quot;@astro-community/astro-embed-twitter&quot;;
import Banner from &quot;../../../../components/Banner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;I&apos;ve showed my age in the &lt;a href=&quot;./snapshot-testing-sucks.md&quot;&gt;previous post&lt;/a&gt;, so I won&apos;t do it again. But, for the
purposes of this post, I will say that I&apos;ve been a professional in the industry prior to &lt;a href=&quot;https://enzymejs.github.io/enzyme/&quot;&gt;Enzyme&lt;/a&gt;,
during its height, and after it (i.e. now).&lt;/p&gt;
&lt;p&gt;This leads me to say, using my &lt;em&gt;expert&lt;/em&gt; opinion, &quot;Goodbye Enzyme, and good riddance. You will not be missed.&quot;&lt;/p&gt;
&lt;p&gt;That may seem harsh, but I stand behind it. I&apos;ve recently removed Enzyme from multiple, highly used codebases at my work
and have see the horrors caused by Enzyme. I&apos;ll cover some of these in this article, comparing it to &lt;a href=&quot;(https://testing-library.com/docs/react-testing-library/intro/).&quot;&gt;React Testing Library&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Enzyme? React Testing Library (RTL)? What are they?&lt;/h2&gt;
&lt;p&gt;They are both testing frameworks aiming to make it easier to test React components. They both have their own philosophies
and ways of doing things.&lt;/p&gt;
&lt;h3&gt;Enzyme&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components&apos; output.
You can also manipulate, traverse, and in some ways simulate runtime given the output.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;RTL&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;You want to write maintainable tests that give you high confidence that your components are working for your users.
As a part of this goal, you want your tests to avoid including implementation details so refactors of your components
(changes to implementation but not functionality) don&apos;t break your tests and slow you and your team down.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/kentcdodds/status/977018512689455106&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;These differing philosophies are the main reason why I prefer RTL over Enzyme. Enzyme is more focused on the &quot;output&quot; of
a component. Additionally, they talk about traversing, manipulating, and simulating the output, which does not align
with how actual users would be interacting with it.&lt;/p&gt;
&lt;p&gt;On the other hand, RTL is more focused on the &quot;functionality&quot; of a component. It is more concerned with how a user would
interact with the component, and how the component would respond to that interaction. ❤️&lt;/p&gt;
&lt;p&gt;&amp;lt;Banner type=&quot;info&quot;&amp;gt;
I&apos;m going to use personal anecdote. These may not match your experience.
&amp;lt;/Banner&amp;gt;&lt;/p&gt;
&lt;h2&gt;Problems with Enzyme&lt;/h2&gt;
&lt;h3&gt;Testing async scenarios is difficult&lt;/h3&gt;
&lt;p&gt;One of the biggest issues I&apos;ve had with Enzyme is testing async scenarios. Unless you&apos;re purely working on a presentational component, most components will have some form of async natur. Something like data loading, form input and validation, etc.&lt;/p&gt;
&lt;p&gt;You&apos;ll need to perform an action, wait for the async action to complete, then move on with your test.&lt;/p&gt;
&lt;p&gt;This is a nightmare to do in Enzyme. Code using &lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;setInterval&lt;/code&gt;, &lt;code&gt;setImmediate&lt;/code&gt;, &lt;code&gt;await Promise.resolve()&lt;/code&gt; or similar is likely trying to account for these async actions. It&apos;s HACKY... and I never liked doing it, even though there was no other way.&lt;/p&gt;
&lt;p&gt;RTL handles this much better in two ways, maybe more.&lt;/p&gt;
&lt;p&gt;First, RTL provides a &lt;code&gt;waitFor&lt;/code&gt; utility. It allows you to wait for a condition to be true before moving on with the test. This can be used in a sync or async manner. Using it in an async manner will ensure that the async action has completed before moving on.&lt;/p&gt;
&lt;p&gt;Second, the &lt;a href=&quot;https://testing-library.com/docs/user-event/intro/&quot;&gt;&lt;code&gt;@testing-library/user-event&lt;/code&gt; package&lt;/a&gt; provides utility methods to help deal with user interactions. When a user interaction triggers an async action via a command like &lt;code&gt;click&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt;, etc., the command can be awaited to ensure the async action has completed.&lt;/p&gt;
&lt;p&gt;This functionality is only in the newer versions of &lt;code&gt;user-event&lt;/code&gt;, but it&apos;s already proved amazingly helpful.&lt;/p&gt;
&lt;h3&gt;Simulating interactions&lt;/h3&gt;
&lt;p&gt;When testing user interactions, Enzyme simulates events in a programmatic way by dispatching DOM events.&lt;/p&gt;
&lt;p&gt;IMO, this doesn&apos;t mimic real user interaction as there may be multiple events that can fire in a single interaction.&lt;/p&gt;
&lt;p&gt;For example, how many events fire when clicking a button? Well, there&apos;s quite a few:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;mouseover&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousedown&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click&lt;/code&gt; (interesting that this is after &lt;code&gt;mouseup&lt;/code&gt;... hmmm haha)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseout&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&apos;re only simulating a &quot;click&quot; you&apos;re missing out on a lot of the events that would happen during an interaction. This leads
to less confidence in your tests (IMO).&lt;/p&gt;
&lt;p&gt;&amp;lt;Banner type=&quot;info&quot;&amp;gt;
Checkout my in-depth post about &lt;a href=&quot;../../2023/04/simulating-js-events.mdx&quot;&gt;Simulating
Events&lt;/a&gt; for more context!
&amp;lt;/Banner&amp;gt;&lt;/p&gt;
&lt;p&gt;Or, if we take an example directly from their &lt;a href=&quot;https://testing-library.com/docs/user-event/intro/&quot;&gt;website&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For example, when a user types into a text box, the element has to be focused, and then keyboard and input events are fired and the selection and value on the element are manipulated as they type.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;RTL, on the other hand, uses the &lt;code&gt;@testing-library/user-event&lt;/code&gt; package to simulate user interactions (you can still use the
&lt;code&gt;fireEvent&lt;/code&gt; method though if you really want to). This takes into account the multitude events that could happen when a user
interacts with a component.&lt;/p&gt;
&lt;h3&gt;Having access to props&lt;/h3&gt;
&lt;p&gt;This is a killer... and I hate it. I&apos;ve seen it abused so many times.&lt;/p&gt;
&lt;p&gt;You can see a basic form below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const Component = ({ onSubmit }) =&amp;gt; {
    return (
        &amp;lt;form onSubmit={onSubmit}&amp;gt;
            &amp;lt;input type=&quot;text&quot; name=&quot;input1&quot; /&amp;gt;
            &amp;lt;input type=&quot;text&quot; name=&quot;input2&quot; /&amp;gt;
            &amp;lt;button type=&quot;submit&quot; onSubmit={onSubmit}&amp;gt;
                Submit
            &amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
    );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s likely there&apos;s some form of validation on the inputs and once the form is submitted some action has to take place.&lt;/p&gt;
&lt;p&gt;In Enzyme, you can access the props of a component and call the &lt;code&gt;onSubmit&lt;/code&gt; function directly, allowing something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;it(&quot;should submit form&quot;, () =&amp;gt; {
    const someAsyncAction = jest.fn();
    const onSubmit = () =&amp;gt; {
        someAsyncAction();
    };
    const wrapper = shallow(&amp;lt;Component onSubmit={onSubmit} /&amp;gt;);

    wrapper.find(&quot;form&quot;).props(&quot;onSubmit&quot;)();

    expect(someAsyncAction).toHaveBeenCalled();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is hard to look at. There&apos;s no user interaction and no validity checks. We&apos;re just calling a function directly (yes,
I have seen this in real life).&lt;/p&gt;
&lt;p&gt;While this is a simple example, you can see how it would open the door for lazy testing (and it does).&lt;/p&gt;
&lt;h2&gt;/endrant&lt;/h2&gt;
&lt;p&gt;While Enzyme was an absolute &lt;em&gt;killer&lt;/em&gt; when it first came out, enabling the testing of UI components like we&apos;d not seen before, it&apos;s
not fit for purpose anymore.&lt;/p&gt;
&lt;p&gt;It actively encourages bad practices, can&apos;t be used past v16 of React (with an official adapter),
isn&apos;t maintained, and has a philosophy that doesn&apos;t align with how users interact with components.&lt;/p&gt;
&lt;p&gt;Please, please consider using &lt;a href=&quot;(https://testing-library.com/docs/react-testing-library/intro/).&quot;&gt;RTL&lt;/a&gt; for your testing needs. It&apos;s a much better framework that encourages better testing.&lt;/p&gt;
&lt;p&gt;Goodbye Enzyme, and good riddance. You will not be missed.&lt;/p&gt;
&lt;p&gt;&amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Snapshot Testing Sucks</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Stop, just stop...</description><pubDate>Mon, 01 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I remember a time when jQuery was &lt;em&gt;the framework&lt;/em&gt; and WordPress was almighty.&lt;/p&gt;
&lt;p&gt;Eventually, &lt;a href=&quot;https://backbonejs.org/&quot;&gt;Backbone.js&lt;/a&gt; and &lt;a href=&quot;https://angularjs.org/&quot;&gt;Angular&lt;/a&gt; became the big players on the block. &lt;a href=&quot;https://knockoutjs.com/&quot;&gt;Knockout.js&lt;/a&gt; had just made it onto the scene. React was still only being used internally at Facebook. RequireJS was a cause of many headaches.&lt;/p&gt;
&lt;p&gt;If you wrote tests, you used &lt;a href=&quot;https://mochajs.org/&quot;&gt;Mocha&lt;/a&gt;. &lt;a href=&quot;https://karma-runner.github.io/latest/index.html&quot;&gt;Karma&lt;/a&gt; came along and made it &lt;em&gt;&quot;easy&quot;&lt;/em&gt; to run tests in an actual browser.&lt;/p&gt;
&lt;p&gt;Have I shown my age enough yet??&lt;/p&gt;
&lt;p&gt;But, React hit the scene and &lt;a href=&quot;https://enzymejs.github.io/enzyme/&quot;&gt;Enzyme&lt;/a&gt; emerged as the default testing framework. It allowed you to test components pretty easily. Tbh, I&apos;m not sure what else I would&apos;ve used to
test React components back then. It was great, but it definitely was a &lt;a href=&quot;https://en.wiktionary.org/wiki/footgun&quot;&gt;footgun&lt;/a&gt; if left unchecked.&lt;/p&gt;
&lt;p&gt;I&apos;ve recently removed Enzyme from multiple, highly used codebases at my work (replacing it with &lt;a href=&quot;https://testing-library.com/docs/react-testing-library/intro/&quot;&gt;React Testing Library&lt;/a&gt;). I&apos;ve seen it all, it&apos;s mostly bad.&lt;/p&gt;
&lt;p&gt;I&apos;ll write more about the other bad things in another post, but this post will focus on the bane of my existance, &lt;a href=&quot;https://jestjs.io/docs/snapshot-testing&quot;&gt;Snapshot Testing&lt;/a&gt;, which, IMO, Enzyme proliferated with its &lt;code&gt;enzyme-to-json&lt;/code&gt; serialiser.&lt;/p&gt;
&lt;h2&gt;What is Snapshot Testing?&lt;/h2&gt;
&lt;p&gt;After talking to iOS/Android engineers, I realise Snapshot Testing has multiple meanings. So, let&apos;s define Snapshot Testing.&lt;/p&gt;
&lt;p&gt;When I say &quot;Snapshot Testing,&quot; I&apos;m referring to the practice of rendering a component, saving its output (i.e. the HTML/Enzyme structure), and comparing it to a previous version of that rendered output (i.e. &quot;snapshot&quot;). If the snapshots are different, the test fails.&lt;/p&gt;
&lt;p&gt;I&apos;m &lt;strong&gt;NOT&lt;/strong&gt; talking about visual regression testing, which is what I&apos;ve heard iOS/Android engineers define as their &quot;Snapshot Testing.&quot;&lt;/p&gt;
&lt;h2&gt;Why Snapshot Testing sucks&lt;/h2&gt;
&lt;h3&gt;The implicit nature of the tests&lt;/h3&gt;
&lt;p&gt;Snapshot Testing is implicit. If you&apos;re not the original author, it&apos;s extremely difficult to know what they were thinking when writing the test. Like, what were they actually testing? What&apos;s important? What does &quot;render correctly&quot; (the title of a lot of tests) mean?&lt;/p&gt;
&lt;p&gt;IMO, extremely explicit test are soooo much better. You should be testing that the correct content is rendered and the interactions are what you&apos;d expect.&lt;/p&gt;
&lt;p&gt;When you&apos;re explicit, it lets others who come across your code in the future know what&apos;s important. Essentially, it&apos;s a form of documentation.&lt;/p&gt;
&lt;p&gt;I know snapshot do have the &quot;content&quot; when rendered, but it&apos;s really lazy... Again, it&apos;s about what content is important.&lt;/p&gt;
&lt;h3&gt;Is the visual output actually the same after your change?&lt;/h3&gt;
&lt;p&gt;Snapshot Testing only tests the output of the component&apos;s HTML structure. It doesn&apos;t test the visual output.&lt;/p&gt;
&lt;p&gt;I can change the CSS, which will change the visual output. Yet, the test will still pass because the structure is the same.&lt;/p&gt;
&lt;p&gt;Is that a good test? I don&apos;t think so.&lt;/p&gt;
&lt;h3&gt;Nobody looks at the changes...&lt;/h3&gt;
&lt;p&gt;In my experience, nobody actually looks at the snapshot diffs before they update their tests. There&apos;s either too many changes or we &quot;know&quot; our new changes are &quot;what it should be.&quot;&lt;/p&gt;
&lt;p&gt;It&apos;s like the snapshot/test was written for the first, and only the first, iteration of the component.&lt;/p&gt;
&lt;p&gt;This is the typical process I&apos;ve observed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make the changes to the component&lt;/li&gt;
&lt;li&gt;Assume whatever output is correct&lt;/li&gt;
&lt;li&gt;Update the snapshot&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is just lazy. Something could be missed if you do this.&lt;/p&gt;
&lt;p&gt;But, I understand why it happens. I&apos;ve been guilty of this I hate to admit. It&apos;s done because most are not the original author of the code. They lack context. So, they make changes, test them somehow, and then update the snapshot.&lt;/p&gt;
&lt;p&gt;This begs the question, &quot;why even have the snapshot test in the first place?&quot;&lt;/p&gt;
&lt;h3&gt;Updating common components... sucks&lt;/h3&gt;
&lt;p&gt;Let&apos;s take a moment to imagine something.&lt;/p&gt;
&lt;p&gt;First, you have nine teams (30+ people) working on a single codebase. They each own their individual areas of the codebase. The way CODEOWNERS has been set up requires approvals by each team when their files are changed.&lt;/p&gt;
&lt;p&gt;Second, common components/depenenceis are used across the entire codebase. These could be something like a design system, or a framework, like React.&lt;/p&gt;
&lt;p&gt;When these are updated, all snapshots in the codebase need to be updated. This happened when React changed how Fragments are rendered (or Enzyme did, or whatever, you get the point).&lt;/p&gt;
&lt;p&gt;Now you need to get sign-off from the all teams. In my experience, it takes a long time and can result in a lot of merge conflicts if not done quickly.&lt;/p&gt;
&lt;h2&gt;/endrant&lt;/h2&gt;
&lt;p&gt;Snapshot Testing was cool at the start, especially when working on small projects. But, as the project grows, it becomes a burden and, IMO, encourages bad practices.&lt;/p&gt;
&lt;p&gt;So, I beg you, please don&apos;t use Snapshot Testing and remove it from your codebase if you can. You&apos;ll be better off (insert plug for &lt;a href=&quot;https://testing-library.com/docs/react-testing-library/intro/&quot;&gt;React Testing Library&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;But, hey, what do I know? I&apos;m just some random guy on the interwebs...&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>The Importance of Linting</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Yes, you should lint your code*...</description><pubDate>Sat, 27 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;style&amp;gt;
{&lt;code&gt;.twitter-tweet {     margin-left: auto;     margin-right: auto; &lt;/code&gt;}
&amp;lt;/style&amp;gt;&lt;/p&gt;
&lt;p&gt;import { Tweet } from &quot;@astro-community/astro-embed-twitter&quot;;
import Banner from &quot;../../../../components/Banner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;Yes, you should lint your code*.&lt;/p&gt;
&lt;p&gt;Showing my age again, I remember when there were so many linters for JS. There was &lt;a href=&quot;https://www.jslint.com/&quot;&gt;JSLint&lt;/a&gt;, &lt;a href=&quot;https://jshint.com/&quot;&gt;JSHint&lt;/a&gt;, &lt;a href=&quot;https://eslint.org/&quot;&gt;ESLint&lt;/a&gt;, and likely many more.&lt;/p&gt;
&lt;p&gt;Finally, ESLint emerged as the default linter, with almost all companies adopting it.&lt;/p&gt;
&lt;p&gt;I was on Twitter (yes, it&apos;s &quot;&lt;em&gt;Twitter&lt;/em&gt;&quot;) and saw this tweet from &lt;a href=&quot;https://twitter.com/mjackson&quot;&gt;Michael Jackson&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/mjackson/status/1782974201001038316&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;After I got over my initial rage of someone suggesting that linting was not important, I realised it is all situational.&lt;/p&gt;
&lt;p&gt;Before we get too far, let&apos;s talk about &lt;a href=&quot;https://prettier.io&quot;&gt;Prettier&lt;/a&gt;. Prettier is an opinonated code formatter, not a linter, with the main goal of &lt;em&gt;enforcing consistent code style&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is super different from a linter, which is more about &lt;em&gt;finding and fixing problems in your code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Now that we got that cleared up, we&apos;ll be focusing fully on linters!&lt;/p&gt;
&lt;h2&gt;Should you lint your code?&lt;/h2&gt;
&lt;p&gt;This is a great question. I usually ask myself a few questions before deciding when to lint my code.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How big is the project?&lt;/li&gt;
&lt;li&gt;How many people are working on the project?&lt;/li&gt;
&lt;li&gt;Is the project a personal, work, or OSS project?&lt;/li&gt;
&lt;li&gt;Is the project a PoC or a production-ready project?&lt;/li&gt;
&lt;li&gt;Do I care about errors??? (a bit of a joke, but also serious)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Yes, I should lint my code!&lt;/h3&gt;
&lt;p&gt;I&apos;d lint my code if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&apos;s a big project is and a lot of people working on it&lt;/li&gt;
&lt;li&gt;A lot of people contribute to the project&lt;/li&gt;
&lt;li&gt;The project will be used in production&lt;/li&gt;
&lt;li&gt;I do care about catching errors before they happen&lt;/li&gt;
&lt;li&gt;It&apos;s a work project&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Nah, I can get away without...&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I know it&apos;s a work PoC, and it will be thrown away after (is this really ever the case though?)&lt;/li&gt;
&lt;li&gt;It&apos;s a personal project, and I&apos;m the only one working on it&lt;/li&gt;
&lt;li&gt;I&apos;m only working on it, and I want to move fast (I&apos;ll still use a formatter)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Yeah, but has it really saved you??&lt;/h2&gt;
&lt;p&gt;Yes... There&apos;s been so many things that linters have caught at my places of work.&lt;/p&gt;
&lt;p&gt;One that helps all the time is &lt;a href=&quot;https://github.com/import-js/eslint-plugin-import&quot;&gt;&lt;code&gt;import/resolver&lt;/code&gt;&lt;/a&gt;. This is less helpful now that we have amazing IDEs that do a lot of these things automatically, but it still catches things!&lt;/p&gt;
&lt;p&gt;Essentially, this catches incorrect imports. If you try to import something from a file that doesn&apos;t exist, BOOM, error.&lt;/p&gt;
&lt;h2&gt;Does it always save you??&lt;/h2&gt;
&lt;p&gt;And no, it doesn&apos;t always save you. We recently had a problem where the linter didn&apos;t throw an error when an undefined function was called.&lt;/p&gt;
&lt;p&gt;The function was supposed to be imported into the file, but, unfortunately it wasn&apos;t. This caused a runtime error.&lt;/p&gt;
&lt;p&gt;After some investigation, it turns out &lt;a href=&quot;https://eslint.org/docs/latest/rules/no-undef&quot;&gt;ESLint&apos;s &lt;code&gt;no-undef&lt;/code&gt; rule&lt;/a&gt; would&apos;ve saved us. We thought it was enabled, but unfortunately, it wasn&apos;t. This was because:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;... &lt;code&gt;plugin:@typescript-eslint/recommended&lt;/code&gt; turns off &lt;code&gt;no-undef&lt;/code&gt; because it&apos;s covered by TS, so double reporting is somewhat useless.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The reasoning makes sense, as TS should catch these errors (and the files in the offending PR were TS). But, it didn&apos;t. Why?&lt;/p&gt;
&lt;p&gt;TL;DR - we&apos;d disabled type checking because we were incrementally adopting TS across our codebase.&lt;/p&gt;
&lt;p&gt;You can bet that we&apos;ve enabled that rule now!&lt;/p&gt;
&lt;h2&gt;Long story short&lt;/h2&gt;
&lt;p&gt;You should lint your code if you work in a professional setting where many people contribute to a long-lived codebase (where you care about consistency and standards as time goes on).&lt;/p&gt;
&lt;p&gt;&amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Google Workspace Default Zoom is Everywhere!</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Watch out, it&apos;s taking over...</description><pubDate>Fri, 27 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Banner from &quot;../../../../components/Banner/index.astro&quot;;
import NewExtensionNameBanner from &quot;../../../../components/NewExtensionNameBanner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;NewExtensionNameBanner /&amp;gt;
&amp;lt;Banner type=&quot;info&quot;&amp;gt;
For more context, feel free to have a look at my &lt;a href=&quot;/blog/tags/google%20workspace&quot;&gt;previous
posts&lt;/a&gt; about building my extensions!
&amp;lt;/Banner&amp;gt;&lt;/p&gt;
&lt;h1&gt;Google Workspace Default Zoom is Everywhere!&lt;/h1&gt;
&lt;p&gt;When I started creating my browser extension, I focused on Google Chrome because we use Chrome as our default browser at work.
Work is the place I primarily use the Google Workspace suite. Makes sense, huh?!?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt;, obviously there are other browsers out there. I want to ensure my extension can be useful to as many
people as possible. So, I started trying to figure out how to port my extension to other browsers.&lt;/p&gt;
&lt;p&gt;I&apos;m glad to announce, I&apos;ve finally released the extension for both Firefox and Edge! 🎉&lt;/p&gt;
&lt;h2&gt;New Browsers!!&lt;/h2&gt;
&lt;h3&gt;Firefox&lt;/h3&gt;
&lt;p&gt;I am only able to offer the base extension for Firefox. This means Firefox users can only use predefined zoom values.
I talk more about why this is in the &lt;a href=&quot;#the-technical-deets-details&quot;&gt;technical details section&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you use Firefox, you can install the extension via the &lt;a href=&quot;https://addons.mozilla.org/firefox/addon/google-workspace-zoom-default/&quot;&gt;Firefox 🦊 Add-ons website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now those of you who don&apos;t want to ditch uBlock Origin can have this functionality! 😜&lt;/p&gt;
&lt;h3&gt;Edge&lt;/h3&gt;
&lt;p&gt;Edge uses Chromium under the hood, so all the features offered in &lt;strong&gt;both&lt;/strong&gt; extension versions worked out of the box!&lt;/p&gt;
&lt;p&gt;If you use Edge, you can install the extension from the Microsoft Edge Add-ons website:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/google-workspace-zoom-def/hnilnnalkgihkfnegpcbcgpgailgjbnn&quot;&gt;Google Workspace Default Zoom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/google-workspace-zoom-def/fhjmigdmbgbiodkejhmahfnaebdgnmjb&quot;&gt;Google Workspace Default Zoom - Extended&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;In exchange for allowing elevated permissions, this extension provides ability to set custom zoom levels (as opposed to only predefined values)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Technical Deets (Details)&lt;/h2&gt;
&lt;p&gt;I know a lot of you come here for the technical details of things, so I won&apos;t disappoint and will get started!&lt;/p&gt;
&lt;p&gt;As I&apos;ve pointed out before, I use &lt;a href=&quot;https://www.plasmo.com/&quot;&gt;Plasmo&lt;/a&gt; to build my extension. From their website:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Plasmo helps you build, test, and deploy powerful, cutting-edge products on top of the web browser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And, oh boy, it not disppoint! I was able to get the extension ported to Firefox and Edge &lt;em&gt;relatively&lt;/em&gt; quickly.&lt;/p&gt;
&lt;p&gt;I think the thing that took me the longest amount of time was figuring out how to become a Microsoft Edge Add-on developer. 😅 It
also took me quite a while to configure the Edge store listings, even though I&apos;d already documented them!&lt;/p&gt;
&lt;p&gt;And... Edge store reviews take sooooooooooooo long. Dang.&lt;/p&gt;
&lt;h3&gt;Firefox&lt;/h3&gt;
&lt;p&gt;Okay, so making the extension available in Firefox was interesting, and I almost gave up a few times. But, I figured it out! Let&apos;s discuss.&lt;/p&gt;
&lt;h4&gt;Problems with Local Development&lt;/h4&gt;
&lt;p&gt;Using Plasmo in dev mode was impossible.&lt;/p&gt;
&lt;p&gt;Firefox seemed to block the WebSocket connection (I think), which forced the
extension to reload itself continuously. This made debugging impossible as the extension was never able to be used (it
disappeared and reappeared too quickly). The only way to stop it was to disable the extension via the debugging page and/or
stop the Plasmo dev server.&lt;/p&gt;
&lt;p&gt;I ended up just running &lt;code&gt;plasmo build&lt;/code&gt; after every change and reloading the extension. It was a bit of a pain, but it
worked.&lt;/p&gt;
&lt;p&gt;First hurdle done!&lt;/p&gt;
&lt;h4&gt;No Extended Version of the Extension 😞&lt;/h4&gt;
&lt;p&gt;Since I could now finally &quot;develop&quot; the extension in Firefox, I tried to use custom zoom values. But, it didn&apos;t work.&lt;/p&gt;
&lt;p&gt;After some digging, I found an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions&quot;&gt;MDN page&lt;/a&gt;
confirming the &lt;code&gt;debugger&lt;/code&gt; permission is unavailable in Firefox.&lt;/p&gt;
&lt;p&gt;While it&apos;s available in Manifest V2 and V3, when
you actually go the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#browser_compatibility&quot;&gt;Browser Compatibility&lt;/a&gt;
section, you can see it&apos;s not supported in Firefox. 😭&lt;/p&gt;
&lt;p&gt;This means I unfortunately cannot ship the Extended version of the extension to Firefox. I&apos;m sorry Firefox users! 😞&lt;/p&gt;
&lt;p&gt;I&apos;ll keep an eye out for any changes in the future! Promise!&lt;/p&gt;
&lt;h4&gt;Manifest V2 vs V3&lt;/h4&gt;
&lt;p&gt;When looking through Manifest differences between Firefox and Chrome, I thought, &quot;Amazing, Firefox is transitioning to Manifest V3.
This is the same version used by Chrome. I won&apos;t have to do much to make this work!&quot; I was wrong...&lt;/p&gt;
&lt;p&gt;I used Manifest V3 initially. All the docs say to define your Add-on&apos;s ID in the manifest. So, I went
looking for my Add-on&apos;s ID. But, don&apos;t you have to publish it first before you get an ID? That&apos;s how the Chrome Webstore
works...&lt;/p&gt;
&lt;p&gt;After banging my head all over the place, it turns out you &lt;strong&gt;don&apos;t have to publish the Add-on to get an Add-on ID.&lt;/strong&gt; You
just create it yourself. Their docs don&apos;t explain this in a straightforward way.&lt;/p&gt;
&lt;p&gt;So, I generated a UUID and added it to my manifest in the right form (&lt;code&gt;{UUID}&lt;/code&gt;). Cool, Storage API is working. I test all the
functionality and it works (minus the extended version part, but whatever). Let&apos;s submit it!&lt;/p&gt;
&lt;p&gt;I package the extension locally and start going through the submission process. I get to the point of uploading the ZIP, but the
I get a message saying something along the lines of:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We suggest only using Manifest V3 for extensions that are distributed via the &quot;On your own&quot; option. To keep a wide range of support
we suggest using Manifest V2 if distributing using the &quot;On this site&quot; option.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cool, so I move back to Manifest V2 using the format &lt;code&gt;extension-id@example.com&lt;/code&gt;. Now, this can be anything as well, just make it up.
Their docs do stress it shouldn&apos;t be a real email address as you&apos;ll get spammed, but it can be anything unique.&lt;/p&gt;
&lt;p&gt;And after packaging it again, the submission is successful (and later approved)!&lt;/p&gt;
&lt;p&gt;I&apos;ve submitted &lt;a href=&quot;https://github.com/PlasmoHQ/docs/pull/128&quot;&gt;a PR&lt;/a&gt; to update Plasmo&apos;s docs relative to Manifest V2/V3 requirements in Firefox.
Hopefully this makes it clearer to future users.&lt;/p&gt;
&lt;h3&gt;Publishing&lt;/h3&gt;
&lt;p&gt;I&apos;ve automated the publishing process using &lt;a href=&quot;https://github.com/PlasmoHQ/bpp&quot;&gt;&lt;code&gt;PlasmoHQ/bpp&lt;/code&gt;&lt;/a&gt;. It publishes both extensions across the stores
for Chrome, Edge, and Firefox.&lt;/p&gt;
&lt;p&gt;It was relatively easy to extend for Firefox and Edge, just requiring the additional builds and packaging of the extension for these new targets (and modification of the ENV variables).
You can checkout the workflows that handle this for both the &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/blob/main/.github/workflows/submit.yml&quot;&gt;default&lt;/a&gt;
and &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/blob/main/.github/workflows/submit-extended.yml&quot;&gt;extended&lt;/a&gt; versions
if you want to see more detail.&lt;/p&gt;
&lt;p&gt;This makes publishing a breeze. All I do is publish a new version using a Git tag and the GitHub Actions takes care of the rest.&lt;/p&gt;
&lt;h3&gt;Sentry&lt;/h3&gt;
&lt;p&gt;Now that the extension is working and published across Chrome, Firefox, and Edge, I needed to fix Sentry. Well, doesn&apos;t Sentry just work?? No, it doesn&apos;t just work.&lt;/p&gt;
&lt;p&gt;I originally set Sentry up to work with Chrome. Specifically, I only uploaded source maps for the Chrome version of the extension. But, since
I now build the extension for each store, this doesn&apos;t work.&lt;/p&gt;
&lt;p&gt;Let me break it down a bit more. I&apos;ll use the default version of the extension as an example.&lt;/p&gt;
&lt;p&gt;I build the extension three times. Each build targets a different store/browser. This results in three, completely different outputs
even though the functionality and source code is the same!&lt;/p&gt;
&lt;p&gt;So, since the errors can happen in any of the three outputs, I need to upload the source maps for all of them to the same release in Sentry.&lt;/p&gt;
&lt;p&gt;One of my concerns was that the release would be created by during the first upload, then
in subsequent uploads it would fail due to trying to create a release that already exists. I had to test it and cross my fingers that it would worked.&lt;/p&gt;
&lt;p&gt;Thankfully, it ended up working as &lt;a href=&quot;https://github.com/getsentry/action-release&quot;&gt;Sentry&apos;s GitHub action&lt;/a&gt; is smart enough! So now all the source maps for each build are associated to single Sentry release. &lt;a href=&quot;https://github.com/vernak2539/chrome-extension-google-doc-default-zoom/blob/main/.github/workflows/submit.yml#L40-L66&quot;&gt;My GitHub action&lt;/a&gt;
has more detail if you want it!&lt;/p&gt;
&lt;h2&gt;Fin&lt;/h2&gt;
&lt;p&gt;Well, that&apos;s it. I hope this provides some level of value to you (or you find it interesting)!&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Google Workspace Default Zoom has a new name</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Damn Edge Store...</description><pubDate>Fri, 08 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Due to the Microsoft Edge Add-ons &lt;a href=&quot;https://learn.microsoft.com/en-us/legal/microsoft-edge/extensions/developer-policies#113-functionality&quot;&gt;Policy 1.1.2&lt;/a&gt;,
I&apos;ve had to change the name of my extension. Honestly, they suck...&lt;/p&gt;
&lt;p&gt;The extensions&apos; new names are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Default zoom for Google Workspace&lt;/li&gt;
&lt;li&gt;Default zoom for Google Workspace (Extended)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;They are still available for Chrome, Edge, and Firefox! They should automatically update, so don&apos;t worry if you had them installed already (you don&apos;t have to do anything).&lt;/p&gt;
&lt;h2&gt;Where to find&lt;/h2&gt;
&lt;p&gt;&amp;lt;table&amp;gt;&amp;lt;thead &amp;gt;&amp;lt;tr &amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;em&amp;gt;Chrome Extension&amp;lt;/em&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;em&amp;gt;Microsoft Edge Add-ons&amp;lt;/em&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;em&amp;gt;Firefox Add-ons&amp;lt;/em&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Default zoom for Google Workspace&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;✅&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;✅&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;✅&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;Default zoom for Google Workspace (Extended)&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;✅&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;✅&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;❌&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/p&gt;
&lt;h3&gt;Find it on Chrome Web Store&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/google-docs-zoom-default/nflkcdlimipkgbacnfnhfecjgmojhklo&quot;&gt;Default zoom for Google Workspace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/google-workspace-zoom-def/mdgikencgfhineaememjagpkiclbdkka&quot;&gt;Default zoom for Google Workspace (Extended)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;In exchange for allowing elevated permissions, this extension provides ability to set custom zoom levels (as opposed to only predefined values)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Find it on Microsoft Edge Add-ons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/google-workspace-zoom-def/hnilnnalkgihkfnegpcbcgpgailgjbnn&quot;&gt;Default zoom for Google Workspace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/google-workspace-zoom-def/fhjmigdmbgbiodkejhmahfnaebdgnmjb&quot;&gt;Default zoom for Google Workspace (Extended)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;In exchange for allowing elevated permissions, this extension provides ability to set custom zoom levels (as opposed to only predefined values)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Find it on Firefox Add-ons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-GB/firefox/addon/google-workspace-zoom-default/&quot;&gt;Default zoom for Google Workspace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Gemini CLI, are you there?</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Claude Code say what?</description><pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &quot;astro:assets&quot;;
import imgTooLong from &quot;./img/gemini-cli-img-01-taking-too-long.png&quot;;&lt;/p&gt;
&lt;p&gt;import Banner from &quot;../../../../components/Banner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;You may be wondering why I am and will be writing about Gemini/Google AI products. Well, because I&apos;m cheap 💸.&lt;/p&gt;
&lt;p&gt;I recently bought a new Pixel and was given a year subscription to the Google One AI Pro plan. Since it gives access to all
the Google AI features for free, I figured I&apos;d give it a shot (Don&apos;t worry Claude, I still use you at work) and see what
is possible.&lt;/p&gt;
&lt;p&gt;Thus, everything I write about here will likely be related to Google products, with maybe some special exceptions. Who
knows 🤷.&lt;/p&gt;
&lt;p&gt;&amp;lt;Banner type=&quot;info&quot;&amp;gt;
As I wrote this post, I found more and more things I think could be
improved. Will that stop me from using it? No.
&amp;lt;/Banner&amp;gt;&lt;/p&gt;
&lt;p&gt;Gemini CLI has been my primary tool for usage in my side projects. I have access to Code Assist in my VS Code IDE, but
honestly, I don&apos;t use it that much. I&apos;m trying to explore this non-IDE world I see coming as much as possible.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, or maybe surprisingly, this tool has been all over the place. I do think the v3 models are massive
improvements, but it still has it&apos;s ups and downs.&lt;/p&gt;
&lt;h2&gt;The Goods&lt;/h2&gt;
&lt;h3&gt;Skills&lt;/h3&gt;
&lt;p&gt;Gemini CLI is surprisingly good at picking up skills when compared to other platforms I&apos;ve used. It&apos;s ability to understand
what I&apos;m trying to do and relate it to a skill makes it so I forget that I actually setup skills previously (or that
my custom skill is different from a built-in skill).&lt;/p&gt;
&lt;p&gt;This is a recent change, with the v3 models being better than the v2 models.&lt;/p&gt;
&lt;h3&gt;Plan Mode (finally)&lt;/h3&gt;
&lt;p&gt;FUCKING FINALLY they added plan mode to Gemini CLI. I can now change to it just like Claude Code and it will go through
and figure out what I&apos;m trying to do and generate a plan. Then I can implement this plan.&lt;/p&gt;
&lt;p&gt;It&apos;s such a step up from the &lt;em&gt;&quot;Hey, research this topic and create a plan. DO NOT MODIFY FILES&quot;&lt;/em&gt; prompt I had been using
prior. I just wish that there was a checklist it would use to convey progress.&lt;/p&gt;
&lt;h3&gt;Continuous Improvement&lt;/h3&gt;
&lt;p&gt;I can tell the team working on the CLI tool is working hard to improve things. There are a lot of updates to the tool,
but mostly it just gets better and better. I mean, it should be getting better and better because it was so far behind
the curve when compared to other coding CLI tools.&lt;/p&gt;
&lt;p&gt;I do really appreciate all new features and the &lt;a href=&quot;https://geminicli.com/docs/&quot;&gt;documentation&lt;/a&gt; is actually pretty good,
which is kind of surprising for Google (shots fired).&lt;/p&gt;
&lt;h2&gt;The Bads&lt;/h2&gt;
&lt;h3&gt;Off the Rails (sometimes)&lt;/h3&gt;
&lt;p&gt;Sometimes Gemini goes down rabbit holes, like bad, and no matter how much you try to get it back the only thing you can
do is stop and restart it again. Yes, we&apos;re talking about context management.&lt;/p&gt;
&lt;p&gt;Honestly, context management with this thing was/is difficult. IIRC the &lt;code&gt;/compress&lt;/code&gt; command didn&apos;t exist for a while or it
did but didn&apos;t work...&lt;/p&gt;
&lt;p&gt;This was further confused when looking online. Some said it has a rotating context window. Others said differently. And it goes
on and on.&lt;/p&gt;
&lt;p&gt;I still sometimes default to using &lt;code&gt;/clear&lt;/code&gt; or restarting over &lt;code&gt;/compress&lt;/code&gt;, even when i&apos;m working on the same thing between
sessions. If I&apos;m doing this, and I want to easily get context back in the next session, I&apos;ll have gemini manually write
out what we&apos;ve been doing. Honestly, I should create a command for this...&lt;/p&gt;
&lt;h3&gt;Access&lt;/h3&gt;
&lt;p&gt;Getting access to Gemini during some periods is really hard. As I type this, my plan for a small tweak I want to make
is on 10 minutes of thinking. This is also the second time I&apos;ve stopped it and tried to restart it.&lt;/p&gt;
&lt;p&gt;This is bad... As a paying customer I&apos;d expect access to the tool I pay for. Luckily, I did get it for free, but if
I wasn&apos;t, I&apos;d be flaming pissed and likely have cancelled already.&lt;/p&gt;
&lt;p&gt;When I tried this with GasTown, even doing small things ran into the limits and I&apos;d get 429s.&lt;/p&gt;
&lt;p&gt;Like c&apos;mon... get with it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Like WTF is this?!?! I accepted the plan and now we&apos;re here...&lt;/p&gt;
&lt;p&gt;&amp;lt;Image
src={imgTooLong}
alt=&quot;Gemini CLI taking about 15 minutes to even start taking action for a plan&quot;
width={610}
style=&quot;width:100%&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Coding Quality&lt;/h3&gt;
&lt;p&gt;When I first started, Gemini was pretty shit at coding. The v2 models paled in comparison to the other models available
like Opus or Sonnet.&lt;/p&gt;
&lt;p&gt;It&apos;s only gotten a bit better with the v3 models. When comparing it to things I do at work with Opus, it&apos;s still not up
to snuff.&lt;/p&gt;
&lt;p&gt;This could be due to the auto model mode (will talk about it more below), where it&apos;s not using v3.1 Pro all the time.&lt;/p&gt;
&lt;h3&gt;Day to Day changes&lt;/h3&gt;
&lt;p&gt;One day things feel good. The next not so much. It&apos;s frustrating as all hell. You get excited to do things and then it
just shits the bed. Not sure what&apos;s going on when this happens. I use the same workflow and when I change I am a bit
less critical.&lt;/p&gt;
&lt;h3&gt;Sub Agents&lt;/h3&gt;
&lt;p&gt;I don&apos;t think Gemini CLI has quite gotten the gist of using Sub Agents effectively. I&apos;ll concede that at this point it&apos;s
still listed as &lt;a href=&quot;https://geminicli.com/docs/core/subagents/&quot;&gt;experimental&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It will use the codebase analyser only sometimes and rarely uses generalist agents. I think once this gets better,
then context management will be easier.&lt;/p&gt;
&lt;p&gt;I have yet to try being explicit about usage of Sub Agents, but it&apos;s on my list of things to try!&lt;/p&gt;
&lt;h3&gt;Model Choice... (Auto Mode)&lt;/h3&gt;
&lt;p&gt;I&apos;m not sure I really like how Gemini CLI chooses what model to use and when (or when Gemini does it). I&apos;ll end my session
and see that it used the flash model for almost all of my session, only ever choosing to use the Pro models for like 1
or two requests.&lt;/p&gt;
&lt;p&gt;When you look at the Gemini App, you see that Pro is good at math and code. So why don&apos;t you use it?!?!?!?!?!?!?!&lt;/p&gt;
&lt;h2&gt;The End&lt;/h2&gt;
&lt;p&gt;Will I continue to use Gemini CLI? Yes. Do I hope it gets better? God yes. Is it the worst thing I used? No, we&apos;ll talk
about &lt;a href=&quot;https://jules.google.com&quot;&gt;Jules&lt;/a&gt; in a future post.&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Oh, Hello AI</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>The thing that came out of nowhere /s...</description><pubDate>Wed, 11 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Banner from &quot;../../../../components/Banner/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;It&apos;s been quite a long-ass time since I posted here. Can&apos;t believe my last post was back in 2024... A lot has happened
since then. I&apos;ve moved across an ocean, lead a team to deliver a KMP, Server Driven UI mobile application, and
my company was acquired. Maybe I&apos;ll post about those in the future, but for now, let&apos;s get address the elephant in the
room... AI.&lt;/p&gt;
&lt;p&gt;&amp;lt;Banner type=&quot;info&quot;&amp;gt;
All my writing will be sans-AI. You&apos;ll get 💯 pure Alex. So, if I don&apos;t use
perfect grammer, then deal!
&amp;lt;/Banner&amp;gt;&lt;/p&gt;
&lt;p&gt;I remember a couple years ago when my company held a GenAI hackathon. It was an interesting one, but the tooling and
models were not what they are today, which led to, IMO, some cool projects, but not actually focused on the application
of GenAI, rather using them inside ML areas or just customising a chatbot.&lt;/p&gt;
&lt;p&gt;But, a lot has changed since then. AI Agents are a thing. Claude has gotten pretty good at generating code.
Companies are fully bought it, whether it&apos;s making them faster or not, or causing problems they can&apos;t get in front of.&lt;/p&gt;
&lt;p&gt;This really got me thinking in general. At first, I experienced &lt;a href=&quot;https://simonwillison.net/2026/Feb/15/deep-blue/&quot;&gt;Deep Blue&lt;/a&gt;
and thought about the future, which looked bleak. I did wonder what I was going to do, as this AI stuff will replace me.
I thought about how I don&apos;t have many skills outside of my profession. So, how would I provide for my family? Tbh,
this got me really down, and it was all I could think about for a while.&lt;/p&gt;
&lt;p&gt;I needed to get out of this funk. So, I started to research things, helping me find a path forward.&lt;/p&gt;
&lt;p&gt;First, I realised that I&apos;m not the only one feeling this way. I&apos;m not alone. I don&apos;t like to relish in the fact that
others feel pretty shit about things, but, in this case, it was nice to know I&apos;m not alone.&lt;/p&gt;
&lt;p&gt;Second, I watched some videos of experts in the field who had different takes from all doom and gloom. The one from
Grady Booch is below.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;youtubeWrapper&quot;&amp;gt;
&amp;lt;iframe
src=&quot;https://www.youtube.com/embed/OfMAtaocvJw&quot;
title=&quot;The third golden age of software engineering – thanks to AI, with Grady Booch&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
allowfullscreen
&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;He talks about how AI will enable the third golden age of softare engineering. How engineers will move up a
level of abstraction, just like when higher order programming languages replaced writing Assembly by hand. It&apos;s just
another tool to solve problems. A tool we need to use and get used to being around. Fundamentals (system design) are
still very important and what is needed in order to guide this next age of engineering.&lt;/p&gt;
&lt;p&gt;He also makes the point that there will be so much software and that itself means there will be a need for
engineers. I guess we can always clean up AI slop ha, until AI is cleaning up all its own slop.&lt;/p&gt;
&lt;p&gt;Lastly, I realised there&apos;s so many cool new things out there to try. There&apos;s a lot of projects aiming to utilise AI
to help people build stuff.&lt;/p&gt;
&lt;p&gt;So, I got off my ass and started trying things... Turns out they&apos;re useful, some more than others. I&apos;m going to deliver
a React Native application (link/post when I do) soon that used a lot of these tools. Things like GasTown, Ralph, Gemini
CLI, Jules, etc... So we&apos;ll explore those together.&lt;/p&gt;
&lt;p&gt;As I continued to try them, I saw that there&apos;s extrodinary value in learning how to use these tools as enablers. While
they do have their shortcomings (many of them), they are worth it and can help!&lt;/p&gt;
&lt;p&gt;I also have lots of other ideas where this will help me. Time is the limiting factor in my life, so maybe I&apos;ll be able
to get to those ideas I&apos;ve put on the back burner.&lt;/p&gt;
&lt;p&gt;While I&apos;m a bit more excited for the future, we&apos;ll see where it goes. At least I&apos;m not dreading it. But, I&apos;m a bit sad,
as I&apos;ll be missing the old way. The coding hands on will become a past time, which is what I liked and got good at. But,
we&apos;re ever evolving, so it&apos;s time (to use these tools in a sane manner with guardrails).&lt;/p&gt;
&lt;p&gt;Something stuck with me, which I&apos;ll leave here for the end of this post.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Don&apos;t outsource the thinking!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The human is still needed for thinking critically. Once you oursource that, we&apos;re lost (think &lt;a href=&quot;https://www.imdb.com/title/tt0387808/&quot;&gt;Idiocracy&lt;/a&gt;)&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Damn it Jules...</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>This thing is crazy... bad</description><pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &quot;astro:assets&quot;;
import imgTooMany from &quot;./img/jules_001-too-many.png&quot;;
import imgSamePrs from &quot;./img/jules_002-same-prs.png&quot;;
import imgMergeConflict from &quot;./img/jules_003-merge-conflicts.png&quot;;&lt;/p&gt;
&lt;p&gt;As I said in my &lt;a href=&quot;../03/gemini-cli.mdx&quot;&gt;last post&lt;/a&gt;, I don&apos;t like &lt;a href=&quot;https://jules.google.com&quot;&gt;Jules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Honestly, it just feels like a half-baked product. I think there&apos;s a lot of potential, but I&apos;m not into it at the moment.&lt;/p&gt;
&lt;p&gt;Why?? We&apos;ll let&apos;s get into it.&lt;/p&gt;
&lt;h2&gt;Bolt&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;You are &quot;Bolt&quot; ⚡ - a performance-obsessed agent who makes the codebase faster...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bolt is essentially an scheduled job that looks to improve performance in your codebase. You can set it up to run on your
preferred schedule, and when it does it will look for performance improvements.&lt;/p&gt;
&lt;p&gt;Sounds cool, right? When you get into the weeds and make a lot of changes, you can miss small or large performance
issues. So, this sounds like a really cool thing to use to cover yourself.&lt;/p&gt;
&lt;p&gt;But, I&apos;ve actually found it quite difficult to manage. This is because the schedule, which is definitely in control
of what I can change. But, life gets in the way and you forget. Then you come back to this...&lt;/p&gt;
&lt;p&gt;&amp;lt;Image
src={imgTooMany}
alt=&quot;Jules with too many open PRs&quot;
width={610}
style=&quot;width:100%&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;This is just the dashboard of Jules, but it shows just how much it&apos;s working. But, when you then look at the PRs, they&apos;re
basically fixing all the same thing over and over again.&lt;/p&gt;
&lt;p&gt;&amp;lt;Image
src={imgSamePrs}
alt=&quot;Jules with too many open PRs&quot;
width={610}
style=&quot;width:100%&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;It would be really cool if it kept track of the PRs it made and then wouldn&apos;t make them again. I say again, it would be
an &lt;strong&gt;amazing feature&lt;/strong&gt; if it took into account past PRs and only raised new ones if the issue wasn&apos;t already tackled.&lt;/p&gt;
&lt;h2&gt;Merge Conflicts&lt;/h2&gt;
&lt;p&gt;When you get in a groove, there&apos;s a lot of things going on in your repo. You&apos;re merging things left and right and then
you have this agent that is running in the background making changes. It&apos;s inevitable, you&apos;ll get merge conflicts.&lt;/p&gt;
&lt;p&gt;I thought it would be pretty easy to fix by directing Jules to do it, but I wasn&apos;t so lucky.&lt;/p&gt;
&lt;p&gt;&amp;lt;Image
src={imgMergeConflict}
alt=&quot;Jules merge conflict&quot;
width={610}
style=&quot;width:100%&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;The screenshot above shows me trying to do this from the PR itself. I&apos;ve also tried from the Jules console, but no luck.
It says something about not having access, but it does have access to my repo. It&apos;s kind of weird really.&lt;/p&gt;
&lt;p&gt;This is table stakes Jules. Come on.&lt;/p&gt;
&lt;h2&gt;General Usage&lt;/h2&gt;
&lt;p&gt;I found that when I wanted to move fast, it didn&apos;t. I would get it to spin up a plan for some functionality, but I would
constantly be waiting for either the plan to complete, things to finish working, or something else. Most of the time
it was just a small change I wanted to make or to nudge it in the right direction.&lt;/p&gt;
&lt;h2&gt;The End&lt;/h2&gt;
&lt;p&gt;I don&apos;t think I&apos;ll be using Jules much more, and will likely be disabling the scheduling of Bolt. I think it does have
promise, as I&apos;d really like to run a bunch of agents on a codebase autonomously, but I don&apos;t think it&apos;s there yet&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>A New Blog? Why?</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Hmmm, why a new blog? I wonder...</description><pubDate>Tue, 31 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Redirect from &quot;../../components/ClientRedirect/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Redirect pubDate={frontmatter.pubDate} collection=&quot;blog&quot; /&amp;gt;&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>Adding Comments to this Thing</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Let&apos;s use utterances to add some comments to this shite... I mean site.</description><pubDate>Sun, 19 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Redirect from &quot;../../components/ClientRedirect/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Redirect pubDate={frontmatter.pubDate} collection=&quot;blog&quot; /&amp;gt;&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>How do I search this thing?</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Where is everything?!?</description><pubDate>Thu, 23 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Redirect from &quot;../../components/ClientRedirect/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Redirect pubDate={frontmatter.pubDate} collection=&quot;blog&quot; /&amp;gt;&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item><item><title>My First Chrome Extension</title><link>https://words.byvernacchia.com/blog/undefined/</link><guid isPermaLink="true">https://words.byvernacchia.com/blog/undefined/</guid><description>Let&apos;s talk about creating &quot;Google Docs Default Zoom&quot;...</description><pubDate>Fri, 03 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Redirect from &quot;../../components/ClientRedirect/index.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Redirect pubDate={frontmatter.pubDate} collection=&quot;blog&quot; /&amp;gt;&lt;/p&gt;
</content:encoded><author>alvernacchia@gmail.com (Alex Vernacchia)</author></item></channel></rss>