Posts on Josh Can HelpNotes on technology, programming, parenting, and self improvement.https://www.joshcanhelp.com2024-02-09T00:00:00ZJosh Cunninghamjosh@joshcanhelp.comGoodbye Auth02024-02-09T00:00:00Zhttps://www.joshcanhelp.com/goodbye-auth0/My 6 years at Auth0 ... how it all started, what Auth0 meant to me, and why I will proudly wear that shield for as long as the swag holds up. <p>Last Thursday, I woke up to a pile of emails that I was, in truth, half expecting. They were all worded a bit differently and came from different senders but the message was the same:</p>
<blockquote>
<p>Today, Okta made the decision to eliminate a number of positions across multiple organizations. <strong>Unfortunately, your position has been eliminated as part of this reduction.</strong></p>
</blockquote>
<p>Things had been a little strange at work since the end of last year: unclear priorities for our group, an awkward reorganization, rumblings that <em>something</em> was going on. All of that contributed to a surge of professional anxiety and a sudden urge to make sure I had all my digital affairs in order.</p>
<p>And then it happened, I got the email. Slack didn't work. My laptop restarted and came back with accounts missing. It really, actually happened. And even though the writing seemed to be on the wall, I was still caught a bit off guard. What about that high-priority project I was helping to lead? What about the training I was scheduled to deliver? What about the offsite next month?</p>
<p>I wasn't totally sure what to do with myself so I tried to follow the instructions that were sent to me to log into the minimal number of apps I still had access to for the rest of the day. I found a few groups of former (or were they still current?) teammates collecting on Google Chat, Slack groups, and LinkedIn, everyone exchanging personal contact information and messages of support. I walked the dog and said "I can't believe it actually happened" to my wife several times.</p>
<p>By the end of the day, my phone was at record levels of screen time logged and I was exhausted from doing what probably looked like next to nothing but I felt ... OK. I had spent all day reading and responding to deluge of <a href="https://www.linkedin.com/posts/joshcanhelp_i-joined-the-layoff-club-this-morning-after-activity-7158890419303444480-ueBL">support, kind words, and offers to help on LinkedIn</a> and was told that the severance offered would give me at least a few months free from acute financial stress. I already made plans to see some former teammates, friends, and family in the next week. Things were going to be just fine.</p>
<p>I don't think there's any reason for me to dwell on the how and why these layoffs happened. There were reasons, some I was told and some I can guess. What I want to remember as I move on from the last 6 years was how it all started, what Auth0 meant to me, and why I will proudly wear that shield for as long as the hats, shirts, and socks hold up.</p>
<img src="https://www.joshcanhelp.com/_images/2024/auth0_swag.jpg" class="aligncenter" alt="Auth0 swag" />
<figcaption><em><p>I'm sure I'm missing something ...</p>
</em></figcaption>
<p>My journey with Auth0 started in January of 2018 working on SDKs, open source libraries that customers use to make integrating their applications with Auth0, hopefully, a bit easier. I was sent the job listing by a fellow preschool dad who thought I would be a good fit. The company culture sounded good (but who can trust that stuff), working in open source sounded fun (but maybe it's just a slog of support requests), and the security/authentication space felt like the challenge I was looking for (but could I hack it). I applied, made it through the interview and take-home project, and got an offer. I accepted and started my first W2 job in 4 years.</p>
<p>The SDKs team was scrappy, totally overloaded, and somewhat of an island in Auth0 engineering. We were mostly left to our own devices to manage somewhere around <a href="https://auth0.com/docs/libraries">60 open source libraries</a> with only 4 engineers. I scrambled to learn about OpenID Connect and OAuth 2 while figuring out what, exactly, the WordPress setup wizard was doing. I learned Laravel for the 3rd time, tried to dig back up what I remembered about Drupal, and squinted my eyes at Symphony code over and over. I dove into Ruby because I had gone through a tutorial a few years ago and remembered liking it. All the while I was being exposed to engineering concepts that just weren't a thing in agency work: <a href="https://www.joshcanhelp.com/wordpress-unit-testing-techniques/">unit testing</a>, CI/CD, git hygiene, release management.</p>
<p><a href="https://www.joshcanhelp.com/pitfalls-of-being-a-wordpress-developer/">I wrote a post that year</a> that would have been confusing to me the year before. The amount of things I learned during my time there is hard to wrap my head around, even having been through it myself. I was surrounded by smart, talented people in a safe, growth-minded workplace. There was chaos and uncertainty but everywhere you looked there were people doing the best work of their career trying to get this cool thing off the ground.</p>
<p>That "Auth0 feeling" was made tangible on the first offsite I went to in Panama City. Everyone around me told me that it would be amazing and I would have a great time. I was, to be honest, a bit skeptical. I'm not typically a company kool-aid drinker and I was expecting a lot of eye roll moments. I was, thankfully, completely surprised.</p>
<img src="https://www.joshcanhelp.com/_images/2024/auth0_panama_2018.jpg" class="aligncenter" alt="Auth0 Panama offsite 2018" />
<p>There was something truly special about what happened during that offsite. You felt like a part of something, like you were contributing to something singular and unique. There was talk of growth and ARR and everything else but the passion that everyone had to do this to the best of their abilities was clear in almost every conversation you had. And it wasn't obsession or fanaticism, it was confidence and excitement combined with camaraderie and just genuine good vibes. You were accepted here, regardless of where you came from in your career and in the world. Just give us your best and we'll use it.</p>
<p>I was caught up in the feeling but my own internal stuff was looming large. I wrote this journal entry while I was there:</p>
<blockquote>
<p>I'm in Panama, building a raspberry pi to pull data from secure internal sources and display to everyone in the company. Imposer syndrome in full effect at the moment ... right up until I stop thinking about it and just collaborate with someone. If I lose myself in the work at hand, that's where I become valuable, focused, and productive.</p>
</blockquote>
<p>I can remember this precise feeling, trying to write Node.js code in vim in a terminal connected to this misbehaving little gadget. I was quaking inside, feeling like someone would call me out. But that never happened and I doubled down on learning and growing from that point on. The excitement I felt leaving Panama was like nothing I had ever felt in my professional life.</p>
<p>After a year (that felt like 3), I was approached to be a part of the technical onboarding program that happened monthly in Bellevue. <a href="https://www.joshcanhelp.com/vittorio/">I wrote about that experience recently</a> and, when I look back on that time, I remember being so humbled and proud of where I worked as I met all the incredible people that came through from all over the world. Once I got my footing as an instructor, I felt like a critical part of creating the culture at Auth0. I got to meet about half of all the new hires for a year or so and was a part of them learning the product and digital identity. Everyone would work through 4 technical labs, each one hour long, and it was so rewarding to see folks try and fail and try something else and ask a neighbor and ask me and then get it working. We gave them a safe space to try something totally new right away and just about everyone walked away with something positive. Just writing these words gives me warm, fuzzy feelings ...</p>
<img src="https://www.joshcanhelp.com/_images/2024/auth0_bellevue_shark.jpg" class="aligncenter" alt="Auth0 shark" />
<figcaption><em><p>My son called the Bellevue office "Dad's Bellevue" and both kids loved the snacks that came back with me.</p>
</em></figcaption>
<p>The offsite that year in Los Cabos was just like the one the year before but even better. I knew many more people because of the onboarding and we were larger and more established. We also heard the news that Auth0 had completed a funding round and was now, based on factors I only partially understood, worth over a billion dollars. And this was all well and good but it meant so much more to everyone who had been a part of it. We had put in our best work, side-by-side with wonderful people from around the world, led by two kind and brilliant Argentinians, <strong>and it was working</strong>! There was no push to work long hours (even though some of us did because we felt like it) and there was no manufactured pressure around launch dates. We figured out what worked for us, it was paying off, and it felt like we were just getting started.</p>
<p>In the beginning of 2020, <a href="https://www.joshcanhelp.com/moment-in-time-during-pandemic/">something huge happened in the world</a> but, being a "modern" adult with kids, I worked right on through it. I helped convert the onboarding to virtual instead of in-person, transitioned out of the SDKs team, and onboarded as the second person on the <a href="https://marketplace.auth0.com/">Marketplace</a> team. As a part of transition, I worked on a <a href="https://github.com/auth0/wordpress/security/advisories/GHSA-59vf-cgfw-6h6v">high-severity CVE</a> that resulted in a <a href="https://github.com/auth0/wordpress/releases/tag/4.0.0">huge major release for a popular SDK</a> that I maintained. If this happened anywhere else, I might have been too embarrassed to link to that here and, at the time, I was mortified to be a part of such a critical issue caused by something as simple as a misconfigured code analyzer. At every point during the process, though, the security team was so kind and so gracious and everyone made it clear that issues like these were the result of process failures, not individuals. Just when I felt like I should have been hauled into involuntary security training for a month, I was surrounded by supportive folks who helped get the fixes out as soon as possible and <em>even praised me for my work</em> during that release. I learned something about a blame-free culture that I will carry with me to the end of my professional life.</p>
<p>That Auth0 spirit, the feeling that you were a piece of something special, was on full display throughout the pandemic. Our family had some <a href="https://www.joshcanhelp.com/how-we-are-teaching-right-now/">major challenges</a> with two young kids at home and two working parents. My wife and I would switch off on shifts: one person worked from 6AM to noon and the other noon to 6PM, with the other handling the house and the kids. Both of us, exhausted, would convene on the couch at night with our laptops, trying to eke out another hour of productivity, breath held that it would all be over soon.</p>
<img src="https://www.joshcanhelp.com/_images/2024/pandemic_yolo.jpg" class="aligncenter" alt="Pandemic YOLO" />
<figcaption><em><p>Not actually, no.</p>
</em></figcaption>
<p>But, once it was clear that we were in it for the long haul, the message at Auth0 was, sincerely, don't make life harder by trying to do more than you can do. The message was clear from the CEO down: take care of your people, do what you can do, don't burn yourself out. We adjusted due dates, spent time on meetings checking in with each other, and just made it through. I've said to many people since then: I would have been voluntarily unemployed very quickly without that support.</p>
<p>We were <s>12 years</s> 18 months into the pandemic and my tenure on the Marketplace team when I got the shocking news about <a href="https://www.okta.com/press-room/press-releases/okta-signs-agreement-to-acquire-auth0/">The Acquisition</a>. One of our main competitors, Okta, had acquired us for $6.5B dollars and we would trade in our fiery Auth0 orange for the more staid Okta blue. I wasn't sure exactly what to think. I was happy that my stock options would be worth something but that meant less than the thought of the Auth0 spirit fading away. We've all heard the stories about the small and spunky startup being acquired and slowly being dissolved into the larger entity.</p>
<p>But, the fact is, I didn't want to leave right away and I had faith that Auth0 would still live on, in some form, even under the umbrella of larger corporate entity. I had faith in the people that started the company and the people that were a part of building it to what it had become.</p>
<p>I spent another 18 months on Marketplace working with my teammates and partners to design and build integrations into our platform. We were flying by the seat of our pants a lot of the time but, as always, the people around me and the work we were doing kept me motivated. In the summer of 2022, however, <a href="https://www.joshcanhelp.com/stroke/">another catastrophe struck my family</a> and I was, yet again, unable to do much beyond handle the situation in front of me. I was, once again, surrounded by new teammates, a new manager, and a new director, all of whom told me in no uncertain terms: take care of your people and we'll handle it at work. This was over a year after The Acquisition and that support was the same as it had always been. I returned after several months of family leave and jumped right back in, truly excited to have a distraction in the form of writing code.</p>
<p>At the beginning of last year, I made what would be my last team change at the company. I was working with some true veterans with as many or more years in the company than I had. I loved the team and liked the work but something was missing and it didn’t really occur to me until I wrote the bulk of this post what that was: the Auth0 story had been told, the ending was revealed, the future was written. The question of whether we would IPO or stay private forever or get acquired and by what company was no longer a question. We were “Auth0 by Okta,” soon to be known only as “Okta CIC.” There was work to be done and still big hills to climb but the original book is closed and on the shelf. In a world full of companies, we were one of the few that made it and it happened because of the heart and soul and blood and sweat we put into it.</p>
<p>I will forever be grateful to the two incredible individuals who started Auth0, Eugenio Pace and Matias Woloski, along with all the impossibly talented humans I worked with and learned from over the last 6 years. I feel like I accomplished a decade of professional growth during that time and learned an enormous amount about what it means to build and run a people-first company.</p>
<p><img src="https://www.joshcanhelp.com/_images/2024/auth0_values.png" alt="" /></p>
<p>So, what’s next for me? I have a few frogs to swallow first (taxes, 2024 budget, resume and LinkedIn updates) the I plan to spend the next 2-3 months writing all the posts I’ve been meaning to write for the last year (<a href="https://github.com/joshcanhelp/josh-to-11/commits/master/">commit log</a>) and working on my two main open source projects, <a href="https://github.com/joshcanhelp/budget-cli">budget-cli</a> and <a href="https://github.com/joshcanhelp/api-getter">api-getter</a>. I also have a bike race coming up that I need to train for and two kids that might let me hang out with them more! After that, who knows? <a href="https://www.joshcanhelp.com/about/">Contact me if I can be useful to you</a>!</p>
Goodbye, Vittorio Bertocci2024-01-21T00:00:00Zhttps://www.joshcanhelp.com/vittorio/Vittorio Bertocci passed on October 7th, 2023. He had a major impact on me and I wanted to write a few words in his honor.<p>I wasn't sure if I wanted to write about this publicly, or at all. When you write a blog, there's a part of you that knows each post is performative and makes you self-conscious about certain topics. You have to ask yourself, is it important to say this out loud to the world or should you process this on your own? In this case, I would like as many people as possible to know what kind of person this world is now missing.</p>
<p>I don't need or want to spell out the details of Vittorio's life here. If you aren't familiar with him already, you can find some of his professional accomplishments on the <a href="https://auth0.com/blog/in-celebration-of-vittorio-bertocci/">Auth0 post about his passing</a>. I want to just say a few words out loud about what Vittorio meant to me and what kind of impact he had on my life.</p>
<p><img src="https://www.joshcanhelp.com/_images/2024/vittorio_bertocci_celebration_of_life.jpg" alt="Vittorio Bertocci" /></p>
<p>In 2019, after just over a year at Auth0, I was asked if I wanted to come to the office every month and assist with the engineering onboarding class. I was hesitant to accept as the commute was quite long and I wasn't sure exactly what my role would be. After some consideration, I said yes and started down a career path of which I would become quite proud.</p>
<p>The class was lead by Vittorio, a rock star in the identity industry. I was not familiar with his name at the time but his presence was enough to tell me that he was someone special (well, that and students asking him to autograph his book). As soon as he started teaching, though, I realized that he had more than just presence, he truly had a gift. I watched him give the same presentations several times in a row and never lost interest. He had a singular way with words and metaphors that kept you totally plugged into what he has saying.</p>
<p>After attending several sessions as a teacher's assistant, I was asked if I would take over teaching the class. It felt like an incredible opportunity but these were enormous shoes to fill. How would I ever be able to get in front of a class full of engineers, architects, and other technical folks and do what Vittorio does? With a dry mouth and a flaring inferiority complex, I said "yes" and began studying in earnest. I watched his <a href="https://auth0.com/docs/videos/learn-identity-series">Learn Identity series of videos</a> over and over, his accent and figures of speech lodging themselves deep in my brain. I read about token exchange and DPOP and mTLS, concepts he talked about for less than a minute in class, wanting to be prepared for when questions inevitably came up.</p>
<p><img src="https://www.joshcanhelp.com/_images/2024/vittori_learn_identity.png" alt="Vittorio Bertocci" /></p>
<p>During this time, Vittorio would meet with me, answer questions, and give feedback on the class. He was an incredibly busy guy, flying all over the world to give talks and perpetually involved with internal consultations, complex customer solutions, and any number of triple-digit message Slack threads. But when you had his attention, you had it. I wouldn't call Vittorio a perfectionist, someone as productive as him couldn't afford to be. But if he was involved in it then it needed to be done correctly. From the Learn Identity videos to the onboarding classes to SDK design, Vittorio had a strong opinion and he would take the time to get you to where his head was at.</p>
<p>Vittorio was very direct with his feedback, sometimes to the point where you hoped that the next comment would come with some sugar-coating. If you said something incorrectly or he disagreed with your point, he was going to make it clear. I never felt like he crossed a line with me but I did leave a few conversations considering whether I should continue taking over the class or not.</p>
<p>His directness, however, pushed me harder to understand the material and eventually run the entire class for close to a year (up until <a href="https://www.joshcanhelp.com/moment-in-time-during-pandemic/">COVID had its way with us</a>). His input on software design and user experience and security and business impact influenced so many things that I was involved in. If Vittorio was part of something, he made it his mission to make it better. He gave me his time, attention, and guidance and left me with a deep understanding of digital identity that has helped me make connections and contributions that I would not have been able to otherwise.</p>
<p>Something Vittorio and I shared was a love of understanding broad problem spaces and sharing that understanding with other people in creative ways. I was always impressed by his ability to break down complicated concepts and explain things in a way that was clear and engaging. He showed me that digital identity as a concept, something that easily could be dry and opaque to most people, can be fun and interesting. I was able to see that in the students I taught during engineering onboarding and it was such a great feeling. One engineer looked up at me after parsing through her OAuth-like Gmail login request, big smile on her face, and said, "wow, this stuff is really interesting!" These are my people.</p>
<p>I was given a unique and valuable gift being mentored by Vittorio, in both obvious and not obvious ways. I hope to pass at least a bit of that along to others in the remainder of my career.</p>
Conducting a Great Technical Interview2023-07-06T00:00:00Zhttps://www.joshcanhelp.com/technical-interview/Technical interviews are a vital component of culture building and setting your team up for success. Here is how I make sure they go well and have a great time doing it.<p>If you have ever been through the "getting a technical job" gauntlet then you have, more likely that not, gone through The Technical Interview. This interview usually comes after one or more Not Technical Interviews and serves to determine if you are allowed to wear the badge of Technical Enough.</p>
<p>I've had the pleasure to conduct technical interviews for many candidates ranging from engineers to managers to curriculum developers. I say "pleasure" because we conduct our technical interviews as conversations, not exams, and it makes for a much more pleasant experience for all parties involved. I work with some truly world-class people here so we must be doing something right.</p>
<p>While the technical interview is an important filter, it's harder than it might sound to get it right. This isn't the same conversation you have with the person next to you at a tech conference, trading war stories and poking around for something you have in common. This is a vital component of culture building and setting your team up for success and you often have less than an hour to do it.</p>
<p>There are other things going on here that makes this task difficult, besides the baseline pressure to get it right. Being an interviewer is, more often than not, a temporary second job that appears out of the blue. Just like all interruptive work, the work involved can put project timelines at risk (oh no, not the due date) and create more opportunities for context switching (the one true enemy of productivity). Depending on the importance this task is given, explicitly or not, it can become just one more thing in the way of "real work."</p>
<p>There is also this uniquely awful social component to interviewing where the candidate feels on trial, not sure if they should answer the question being asked or if there is a meta-question hiding in there somewhere. On the other side of the Zoom, it's often hard to let go of the need to look or sound smart and project the right authority since, hey, you already have the job they want. Awkward moments abound as everyone navigates all this in real time.</p>
<p>We have the makings here for a truly unpleasant situation so how have I learned to enjoy giving technical interviews? Like so many things, it comes down to identifying the job(s) to be done (JTBD).</p>
<p class="bigtext">
Effective technical interviews come down to two important things that you, the interviewer, most own: <strong>having a great technical conversation</strong> and <strong>finding out what you need to know</strong>.
</p>
<p>The former is not guaranteed and can be harder to get right if you're naturally introverted. The latter is straight-forward but easily overlooked if you're not deliberate about it. Both, I promise, become much less vague and anxiety-causing with intentional, directed preparation.</p>
<h2>As a team, prepare before all interviews</h2>
<p>This preparation starts with the team that the candidate is applying to join. Using the public job description as the foundation, <strong>write a list of the technical-specific qualities, experience, and knowledge that this person should have to be successful</strong>, if hired. These should be forward-looking, job-specific, and honest. Think about the things they should be able to do walking in the door as well as the things that they need to learn along the way.</p>
<p>As an example, let's say you're hiring for a mostly front-end programming position at a senior level. The job description might say "4 years programming experience with React, Vue, or similar." You should expect that the folks applying will have this experience on their resume in one form or another. But what else?</p>
<ul>
<li>You might be looking for leadership qualities since you expect grow the team with a more junior hire next</li>
<li>You might be looking for experience with legacy front-end code to help modernize a particular application</li>
<li>Your team might be taking on a backend service in the near future so some full-stack knowledge would help</li>
<li>Design resources might be slim so you're looking for someone with an eye for UX</li>
</ul>
<p>You should be able to boil this all down to a succinct (3 seems like a decent minimum, 8 seems like a lot) list of qualities, called a rubric, that will allow the team to compare candidates in a consistent and meaningful way. Post-interview, the team should score the candidate on these qualities individually then, after submitting their feedback, can more easily decide whether to move forward with someone.</p>
<p>Coming up with a coherent evaluation is now just a matter of generating the right questions. Either individually or with the team, <strong>write out a list of position-specific technical questions to ask during the interview</strong>. These will be the bank of questions you'll pick from for all candidates so write as many as you can but base them on the rubric.</p>
<p>For example, if leadership is one of the qualities you're looking for, a question could be:</p>
<blockquote>
<p>Tell us about a project that you lead, whether it finished successful or not. What did you learn? What would you have done differently?</p>
</blockquote>
<p>If you're looking for UX experience, a question could be:</p>
<blockquote>
<p>Tell us about a project where you were a part of deciding the end-to-end user experience. What was your role in that project? What did you learn?</p>
</blockquote>
<p>Write as many down as you can and then refine them. Think about the kinds of answers you might get. If the person you're speaking with doesn't give you a lot to go on, what could you ask to get them to expand? If they didn't have that specific experience, what else could you ask about in that vein? Expect to re-write each question a few times and try asking them to your teammates to see how they land.</p>
<p>You should end up with enough questions for all interviewers to choose from. If you did this individually then you'll want to combine them together and remove or combine related or duplicated ones. As a general rule of thumb, you should end up with <strong>no less than 12 questions total</strong>, distributed between the interviewers. Make sure they are ranked in some way from most important to ask to least so the least important ones are your backups if the interview is moving quickly.</p>
<p>Besides the main JTBD of <strong>finding out what you need to know</strong>, this bank of questions will make sure your team:</p>
<ul>
<li>... is consistent with your questions across candidates.</li>
<li>... avoids awkward silences where you're not sure what to ask.</li>
<li>... has potential questions for future open positions.</li>
</ul>
<p>As a final preparation step for all interviews, <strong>decide on who will lead the interviews or whether it will rotate</strong> and <strong>write down the opening to use for all interviews.</strong> Both of these tasks are easy to do and will greatly reduce awkwardness and increase consistency.</p>
<p>Having a designated leader for the interview keeps it on track and on-time. Writing down your opener makes sure you set the right context and reduces anxiety for the candidate. If they know what to expect, they can relax a bit.</p>
<p>As an example, here's the opener I've used on the last several interviews I lead:</p>
<blockquote>
<p><em>Optional</em> "Can you tell me how to say your name correctly?"</p>
<p><em>Introduce yourself, title, history, then pass the mic to another interviewer.</em></p>
<p>"This is a conversation, not a test. We’re looking to hear about how you approach technical problems and come up with solutions. We are looking for details: specific approaches you would take or have taken before."</p>
<p>"We’re going to be taking notes during the interview. Don’t worry, we’re paying attention!"</p>
<p>"This is, of course, a two-way interview. We’ll leave 10-15 minutes at the end for questions from you but if have more questions about the company, the team, or the position, please send questions via email and we’d be happy to answer them."</p>
<p>"Do you have any questions or specific considerations before we get started?"</p>
</blockquote>
<p>The leader should typically be someone who has attended at least a couple of these interviews before but it certainly doesn't have to be the most senior person on the team or the person who has done the most interviews. Consider rotating between team members so everyone gets experience.</p>
<h2>As an interviewer, prepare for specific interviews</h2>
<p>At this point, you should have a very clear idea of what you're looking for and a discrete list of questions that will get you there. Now it's time to really get to know the person that you'll be speaking with.</p>
<p>I usually do this the day of the interview, often blocking out an hour before the interview so the information is fresh in my mind. I do some combination of the following, depending on the candidate:</p>
<ul>
<li>I read their resume top to bottom at least once, usually twice</li>
<li>I get familiar with the companies they worked for and what they do</li>
<li>I review their GitHub contributions, if they have any</li>
<li>I read a few posts from their blog, if they have one</li>
<li>I'll look through their LinkedIn, if they have it</li>
</ul>
<p>What I'm looking for here are ways to personalize the questions that I have and hooks to keep them talking if I'm not getting a lot out of them. If you want, but don't need, experience with, say, data pipelines, then look for experience in their resume. If there is not a hint of data experience, and that's not a show-stopper, then don't ask about it. If they built Kafka topics and Airflow jobs for a year, then don't ask if they have data experience or not, ask them about "position ABC at company XYZ" and use the words they used. You'll show them that you cared enough to do work ahead of time and avoid wasting valuable time.</p>
<p>Be careful here, though, because the more personal you get, the less consistent you will be across all the interviews. Use the information you glean to pick your questions and tweak them, not to ask easy questions you know they have an answer for (or to ask questions you know they won't have a clue about). Use what you learn to preface questions, focus them, or eliminate them altogether.</p>
<p>One more thing I do at this stage is to write down an extra question or two just to make sure I have enough to work with. I find that that I'm the most nervous when I don't have enough questions to ask. 9 times out of 10 the interview is at risk of going long but every once in a while you get someone with terse, efficient answers and you're looking at the clock with 20 minutes left and nothing left to ask. Add a few questions at the end of your list so you end up disappointed you didn't have more time rather than stressed out that time is crawling.</p>
<h2>During the interview</h2>
<p>Hopefully your anxiety about potentially altering the course of a person's life is reduced a bit but, like all things that happen during the messiness of being a human, you are not guaranteed a fun time just because you followed my advice.</p>
<p>I find the hardest part about the interview itself is navigating the awkwardness I've talked about throughout this post. There are so many dynamics at play that it can be hard for all parties to stay focused on the task at hand. This often culminates in a situation that makes the candidate ramble, meander away from the question being asked, clam up, or misspeak.</p>
<p class="bigtext">
The most important thing to keep in mind is that <strong>signs of anxiety are not indications that the person is a bad fit for the job</strong>.
</p>
<p>In fact, being nervous could be a sign that they are a perfect fit because they want it so badly. I can think of a myriad reasons why someone have a tough time being evaluated:</p>
<ul>
<li>Maybe this is a really important company or position to them</li>
<li>Maybe they're getting a vibe from you that it's not going well</li>
<li>Maybe there is something totally unrelated going on in their life</li>
<li>Maybe they're worried about the next question</li>
<li>Maybe they have an inferiority complex</li>
<li>Maybe they're introverted and these complex social situations are difficult to navigate</li>
<li>Maybe you're nervous and it's making them nervous</li>
</ul>
<p>Whatever the reason is, it's critical to understand that <strong>it's up to you and your team to help them get through it</strong>, it's your burden to bear. You have the advantage of preparation, more authority to interrupt to get things back on track, and less at stake. Internalizing this will make it easier to do everything you can to keep things comfortable.</p>
<p>One thing I like to do, usually as the interview leader, is to come up with a personalized (not personal) ice-breaking question that gives me some information but should be easy for them to answer. If they're a regular open source contributor, I'll ask them a question about a specific library they're active in. If I see something interesting (and job relevant) on their resume, I'll ask them to talk about it. What you're looking for is a question that makes it easy for them to start talking and an early win that boosts their confidence. I can usually find a segue from that to a more pointed question, which has proven to be a great way to ramp up.</p>
<p>After that, the main thing I focus on doing is <strong>listening, smiling, and nodding</strong>. I want them to see me paying attention and plugged in. This is a challenge if you're a note-taker, like I am, but I can usually type a few key words out quickly and plug right back in. All of these things are signals to the candidate that you are interested in what they have to say and you care about what they are telling you. This boosts their confidence and allows them to do the best that they can.</p>
<p>And, really, at the end of the day, it all comes down to listening. We've all had the experience of spacing out for a few minutes and losing the thread on a meeting. When it's a weekly team sync or a topic that might not be directly relevant for your day-to-day work, that's forgivable and not too difficult to recover from. But remember: <strong>fighting awkwardness is your responsibility</strong>. If you're not listening, you're going to stumble over the next question, appear disjointed, or leave a big block of radio silence that your teammates might struggle to fill. If you pay close attention, on the other hand, it should be easy to find threads to pull on.</p>
<h2>After the interview</h2>
<p>At this point, I've certainly gone on long enough about this but I wanted to give you one more tip to make interviews more valuable and less painful. As soon as the meeting closes, go back to your notes (or start them) and fill out everything you remember about their answers, your impressions, and their performance. You're probably going to have a "thank god that's over" feeling as soon as you're off but every hour you wait to write down your impressions makes them less accurate and more vague. Get it over with and then you can move on to that Slack message.</p>
<hr />
<p>I hope this was helpful for anyone privileged enough to be in the position to make these kinds of decisions for your company and team. I've grown to enjoy interviews over the year and it all comes down to practice and preparation.</p>
My Journey, So Far, with Life Logging2023-05-05T00:00:00Zhttps://www.joshcanhelp.com/lifelogging/I realized recently that I have become somewhat obsessed with the idea of logging and archiving all the little aspects of my life in one place.<p>I realized recently that I have become somewhat obsessed with the idea of logging and archiving the many little aspects of my life in one place. I'm already (somewhat cautiously) collecting a fair amount of data about myself:</p>
<ul>
<li>Oura ring (sleep and activity, mainly)</li>
<li>Strava (longer workouts)</li>
<li>Day One (semi-regular journaling and family timeline moments)</li>
<li>Daily notes in Obsidian</li>
<li>... so much more, for better or worse</li>
</ul>
<p>I don't want to just turn on the data firehose in all the apps and track everything but I am enamored with the idea of looking back through my life and seeing as complete of a picture as I can. There seem to be a number of terms in this arena like life logging, digital archiving (maybe different but related), quantified self (maybe more directional), etc.</p>
<img src="https://www.joshcanhelp.com/_images/2023/book-store.jpg" class="aligncenter" alt="" />
<p>This started a while back, when I started thinking about collecting the timeline of my life. I didn't (and still don't) have a great grasp of time in the first 2 decades of my life. I would have a memory and my wife would ask "how old were you" and I would reply "like 10? maybe 12, no ... 14?" I had a tough time piecing things together.</p>
<p>I have always been a bit of a hoarder so I started to comb though old emails in various formats, documents my mom saved, and old letters trying to put dates on certain memories. The more I figured out, the more I wanted to know about surrounding events and what other people in my life were doing at that time. I would ask my mom and dad about certain events and dates with varying success. The remaining gaps were frustrating.</p>
<p>When our first kid was born almost a decade ago, it was clear in my mind that I wanted to put together a very clear picture of her childhood. Mobile phones are, of course, the absolute best way to do that and the countless date and location tagged photos will do a huge amount of the legwork for us. But there's a more complete picture to be had. What was it like for her parents to be parents? Who were they at that time? How accurate will her memories be?</p>
<p>One of the best sources for this kind of thing, for a lot of people, is social media. I exported and deleted all my social media long ago so any updates I write are in a journal or in a daily note. I started to import a photo or two from events in the past into Day One, adding a bit of color around how the day went and what we did. It's quite hard to keep up with that but I try to do that now and then. I also write my kids long letters each year during the holiday break with my favorite photos of them, who they were, what they liked, and all about the ups and downs.</p>
<p>A good portion of this is about my kids but there is something pulling me towards collecting more and more data (while being wary of what's stored in the cloud). I've started downloading my Google Calendar on a regular basis, bought software to export all my text messages, and have my eye on email now, my oldest and best record of communication (why did I delete my oldest account without exporting?!?). I have the export files from Facebook, Instagram, and Twitter before I nuked all the content there. I have Netflix history, contact list backups, iCloud data, old notes apps I used to use, Spotify plays, Amazon purchases. Just this all out makes me <em>vibrate</em> with nerdy excitement!</p>
<p>I also have a treasure trove of old photos, old documents, and genealogy work from my mom laying around that I haven't looked through. Transferring analog documents to digital ones is a whole other layer of excitement. One day I spent a whole day downloading PDF copies of the instruction manuals I had and recycling the paper version. I still get a smile on my face when I think about it and having it all at hand in Dropbox is quite useful.</p>
<p>Before you say anything ... I am perfectly aware that all of this life logging and digitizing and organization amounts to an actual value approaching nothing. If my daughter could know what movie we watched the day she was born (Pulp Fiction, seriously?) at some point in the future, that might be interesting for a second but then, who cares? This feels like the most navel-gazing use of time one could imagine.</p>
<p>But there is just something so appealing in it for me. Collecting the data that was collected on me and stitching it together to be able to paint a picture of specific moments in time. My life has been mostly mundane, especially for the last 10-15 years, but there is a story there and sometimes it's just nice to go back and relive it in whatever form you have it in.</p>
<img src="https://www.joshcanhelp.com/_images/2023/book-store-2.jpg" class="aligncenter" alt="" />
<p>The amount of data exhaust that we're all generating is pretty staggering. I'm fairly cautious about what I sign up for but I'm still finding more and more sources of data I can export and use. It's a bit of a bind, to be honest; I love having this data available to me but I don't love allowing companies to collect it. For example, I use Amazon as little as possible for buying things (buy local) but being able to see random purchases from many years ago is fascinating. I also turn off all the Google tracking I possibly can but seeing everywhere I've been over a period or time is so interesting to me. There's a catch 22 around every corner, it seems.</p>
<p>What I am truly cautious about, though, is how much time I spend recording and collecting and transforming. When I take photos, I try to take a few good ones rather than capture every moment. I'm cognizant of the "observer effect" where you take yourself out of a great moment just to capture it. I have felt this sense of anxiety/FOMO when I realize that I <em>could have</em> collected something but didn't or forgot to. Collecting for the sake of collecting is fine as long as it doesn't color my life in a negative way. There is a whole bunch of toil baked into this hobby and I'm wary of creating an endless source of digital chores for myself.</p>
<p>To that end, what I'm "working on" (in that, I'm writing and thinking about it on a somewhat regular basis) now is how to collect all this stuff in one place, keep it backed up, and access it without doing a bunch of manual work. I want something that just reads an Instagram export combined with a Netflix export combined with an email archive and just show me a little picture of the day/week/month in the past. I don't want metrics or a daily score or anything qualitative, I just want to see what I can see in whatever interesting way I choose.</p>
<p>I also want to make it as easy as possible to pull that data down and keep it safe (read: local or encrypted cloud backup), whether that's via API or scheduled backup downloads or just instructions on how to do it along with a date record of when it was last completed. I've been working on more projects involving data collection and transformation and storage in my day job and seeing how that relates to personal data. I want to make it easy to collect as much of my own raw data as possible, even if I don't know exactly what I want to do with it right now.</p>
<p>I'm realizing this is part of a bigger picture in my life. I recently wrote my own <a href="https://www.joshcanhelp.com/budget-cli/">command line software for budgeting</a> and one of the main joys there was that the software was my own and it did exactly what I wanted. That kind of software is, IMHO, the most fun kind software to write and I'm always hugely excited when I'm able to document and put it out there in the wild. And then, when I want something a little different, I go have fun changing it! I get what I want and I have fun in the process, what could be better?!</p>
<p>This feels like a somewhat similar thing to me. I have am idea of what I want and some of the skills to make it happen. It's not for other folks but maybe other folks will benefit. It's not a business idea but maybe it becomes one. Like the budgeting software, it combines two interests I have - digital archiving and software - into one fun project.</p>
<p>If you're doing something similar on your own or in public or know of any similar projects out there that I can learn from, I'd love to hear from you. I'll be working on this in the open, in both words and code, and it would be great to talk to and hear from folks thinking or working in the same vein.</p>
<h2 class="hr" id="references">
<span class="pink"><</span>
References
<span class="pink">></span>
</h2>
<ul>
<li><a href="https://vimeo.com/61947763">Beautiful documentary on Life Logging from 2013</a></li>
<li><a href="https://writings.stephenwolfram.com/2012/03/the-personal-analytics-of-my-life/">Stephen Wolfram on taking things way further than I want to</a></li>
</ul>
An Open Letter to My Mental Boardroom2023-03-26T00:00:00Zhttps://www.joshcanhelp.com/mental-boardroom/A note to all the people that help make me better everyday.<p>Dear Distinguished Board Member,</p>
<p>I'm am pleased to inform you that you've been accepted as a member of my mental boardroom. Congratulations! This position comes with no extra compensation but it also comes with no strings, expectations, or responsibilities attached.</p>
<p>You have been selected not because of your tenure, perfection, or what you have to offer but because of your composition of character, generosity of spirit, and approach to life. You have been an important voice inside my head and I'm a better person because of it.</p>
<p>Each board member, who shall, for the most part, remain anonymous, has helped me to create a life that I love and that I'm proud of and pushed me to be a better person in this world.</p>
<p>In no particular order, you, collectively, have:</p>
<ul>
<li><strong>Helped me put things in perspective.</strong></li>
<li><strong>Helped me have more fun.</strong></li>
<li><strong>Helped me stay active.</strong></li>
<li><strong>Helped me find the humor.</strong></li>
<li><strong>Helped me be patient.</strong></li>
<li><strong>Helped me show kindness.</strong></li>
<li><strong>Helped me find the right words.</strong></li>
<li><strong>Helped me realize that it's going to be OK. Really.</strong></li>
<li><strong>Helped me make an important decision.</strong></li>
<li><strong>Helped me listen.</strong></li>
<li><strong>Helped me grow as a human and a husband and a dad.</strong></li>
<li><strong>Helped me feel just a little better.</strong></li>
</ul>
<p>I think <a href="https://genius.com/600763">Slimkid3 from Pharcyde</a> put it best:</p>
<blockquote>
<p>There comes a time in every man's life<br />
When he's gotta handle shit up on his own<br />
Can't depend on friends to help you in a squeeze<br />
Please, they got problems of their own</p>
</blockquote>
<p>Maybe you've watched my kids in a pinch or helped me move one (or many) times or let me borrow something I needed, and for that, thank you once again. But your position as Board Member means that you have helped me through hard times by merely existing. Maybe I remember a conversation we had at just the right time to give me perspective or maybe your voice came into my head when I was deliberating a hard choice or maybe I just felt like a better person because you choose to be in my life. You are there with me through the hardest of hard times and I know, if I need to, I could call or text but, if I don't, you still still have the power to help. Selflessly, graciously, and unknowingly as a model and example for the best person I can be in the moment.</p>
<p><strong>You help me handle shit on my own.</strong></p>
<p>Thank you again for what you've shown and taught me over the course of however long that I've known you. I sincerely hope that I have made a positive mark on your life as well, whatever the size.</p>
<p>Specifically to my wife, Anna ... your position as a member of this Board is not incidental to our marriage. You have been a source of growth, inspiration, and motivation for the almost 2 decades that I've known you. You have had a hand in all of the things on that list above and, intensionally and otherwise, have helped me push far past who I ever thought I could be. <strong>I am honored to know you as I do.</strong></p>
<p>Specifically to my kids ... you have played an essential part in all this by being never-failing diving rods pointing to my deficiencies, anxieties, shortcomings, and mistakes. You are the truest truth that I know of and the source of immense, and often painful, growth. When I can handle all of my stuff combined with all of your stuff and still laugh at the end of the day, I feel like like a god. <strong>Thank you for that challenge.</strong></p>
If Your Loved One Has a Stroke2022-11-21T00:00:00Zhttps://www.joshcanhelp.com/stroke/This year, I became my mom's co-caregiver, medical advocate, and financial administrator. I have learned an enormous amount about myself, Medicare, hospitals, assisted living facilities, the power of attorney process, medical advocacy, and mental health that might be helpful to others.<p>On June 7th of this year, my mom was taken to the hospital after she was found on the floor of her apartment by family. She suffered a major stoke at some point in the previous 24 to 48 hours and was mostly unresponsive when the medics arrived. At the hospital, she had an emergency craniectomy to reduce swelling on her brain and, when I saw her that evening, she was still mostly unresponsive but now had a massive, stapled incision that traced down the middle of her head and back around under her ear. I went home that night shaking, crying, and feeling like the earth had disappeared right out from under me.</p>
<p><img src="https://www.joshcanhelp.com/_images/2022/harborview-peace.jpg" alt="" /></p>
<p>Since that day, I've become my mom's co-caregiver, medical advocate, and financial administrator. I have learned an enormous amount about myself, Medicare, hospitals, assisted living facilities, the power of attorney process, medical advocacy, mental health, and so much more. While a lot of what I went through was unique, I wanted to share both my experience and what I learned so others who find themselves in the same position might feel a bit more prepared.</p>
<p><strong>Note:</strong> I am not a doctor or a lawyer, just a geek who went through a thing. Nothing on this page constitutes legal or medical advice, just things I've learned and experienced.</p>
<h2>What do to before this happens</h2>
<p>This is the section for anyone who is not currently going through this but are in a position where a parent or spouse or other adult will need to rely on you if something like this does happen to them. This is also the section for those of you who might feel like they are decades away from something like this happening. You just never knew; be prepared.</p>
<p>First and foremost, get the <strong>power of attorney</strong> (POA) in order. For adults that you will be responsible for, make sure they have one that indicates you (and/or other family members) and make sure you have a copy. Scan it immediately and save it in an online file manager like Dropbox, Google Drive, or similar. There are different types of POA and the document itself will spell out what person has what power. You will need the ability to make medical and financial decisions on their behalf as this will be key to moving things forward.</p>
<p>There are many types of POA documents and I am not in a position to give legal advice but a single, durable POA that names you as the agent probably covers what you need. Based on my own experience and some off-the-record advice from a helpful employee at the bank, a co-POA situation where two people are named equally (instead of primary and secondary) can make things much more complicated, especially if one of the people is not physically nearby or easy to contact.</p>
<p>Having a POA document is a very important first step but it's not everything you need to start making decisions. That POA document needs to be officially on record at each of the different organizations that you will be working with. The process for getting that record could take anywhere from no time - just show the document - to weeks. Banks and other financial institutions take this stuff seriously, and for good reason. Start this process right away for:</p>
<ul>
<li>The hospital they are at</li>
<li>Financial institutions (banks, brokers, etc.)</li>
<li>Health insurance</li>
</ul>
<p>Next, make sure you talk with them about what they want, both in terms of medical care and procedures, quality of life, and long term care. This might come in the form of a <strong>advance health directive</strong> document for acute care (things like the surgery my mom needed or life support) while in the hospital. But during this whole situation, you'll find that a document like that can only cover so much. You might have to deal with:</p>
<ul>
<li>Assisted living facilities</li>
<li>Mental impairment</li>
<li>Pain management</li>
</ul>
<p>The NIH has <a href="https://www.nia.nih.gov/health/advance-care-planning-health-care-directives">a great resource for advance care planning</a> that talks about the kinds of questions you should have answers for. You should talk this through with anyone you might have to make these decisions for.</p>
<h2>Mindset</h2>
<p>You will probably learn all of this on your own throughout the ordeal. I found a lot of peace once I internalized these lessons, peace that probably can't come from just reading a list. Still, maybe these will help:</p>
<ul>
<li>Your life has changed forever, one way or another. You can accept this or fight it but the truth will not change.</li>
<li>There will be big, bright, shiny silver linings to this experience if you look for them. You'll feel guilty for finding positivity in all of this pain and trauma; get past this guilt as soon as possible.</li>
<li>You will make mistakes, drop balls, and forget things; you are under a lot of pressure right now during a traumatic experience without enough information. Forgive yourself quickly.</li>
<li>If you have a family of your own, there will be heart-wrenching conflicts between your job as a parent/spouse and your job as a caregiver. You will need to find a way to balance these as best as you're able. Communication is key.</li>
<li>You will need help from people. If you're already not good at asking for help, this will all become harder.</li>
<li>You will find yourself wishing this would have never happened. This might lead you to some dark thoughts about what could have made that happen. Forgive yourself ahead of time for these thoughts.</li>
</ul>
<h2>Keep notes + records</h2>
<p>I had the benefit of already being a <a href="https://www.joshcanhelp.com/notes/">prolific note-taker</a> so my natural instinct was to start writing as soon as possible. I journaled, took notes during medical conversations, recorded events, and maintained a mom-specific to-do list. My neurotic and compulsive note-taking has both pros and cons, though, so how I did it might not be a great fit for you.</p>
<p>At the very least, record ...</p>
<ul>
<li>Expenses incurred (parking, transportation, professional services)</li>
<li>Notes about current condition, future procedures, etc along with the provider's name and the date</li>
<li>What needs to happen immediately, soon, and at some point</li>
<li>Questions for medical staff that come to you suddenly or from other family members, friends, etc; record the answers as well</li>
</ul>
<p>You might find that your brain's memory-making process is operating differently during this time and you'll be glad that you focused on writing things down rather than trying to jam them in your already-full and traumatized brain. You might also find that you will need to connect the dots between different medical institutions, financial institutions, and other people. Having names, numbers, questions, and answers written down as completely as possible can save some of your sanity down the road and make it easier to use your brain for things other than retaining facts.</p>
<p>Also, posterity, if you're into that sort of thing.</p>
<h2>Working with hospitals, care facilities, and other providers</h2>
<p>If you have the same experience as I did, you will find that the <em>vast majority</em> of individuals you work with in these contexts are talented and caring and want the same positive outcomes that you do. You will also find, however, that the system or company that they all operate in is a confusing, heartless, discombobulated, near-sentient monster made out of forms, red tape, automated voice menu systems, and notifications. This dichotomy is felt everywhere and the staff in these facilities are well aware of it with very little power to change it.</p>
<p>So, your best way to deal with this depends on whether you're dealing with a person or a system. When you're talking one to one with a doctor, LNP, nurse, NA, or other direct provider, keep this mantra running through your head:</p>
<ul>
<li>This person has a very hard job in a very weird industry</li>
<li>This person regularly gets treated like shit by other caregivers like myself</li>
<li>This person wants my loved one to get better just like I do</li>
<li>This person can have a bad day just like I can</li>
<li>This person will be more inclined to help me and my loved one if I'm kind and patient with them</li>
<li>This person is likely frustrated with the same parts of the system that I am</li>
<li>This person has been specifically trained to give me clear and concise medical information without emotions</li>
</ul>
<p>Armed with this, you can come into conversations in your anxious and emotional state and still have a good outcome. You will feel good about yourself for doing the right thing and getting what you needed and the provider will be thankful that you're not another asshole and be more inclined to work with you more closely.</p>
<p><img src="https://www.joshcanhelp.com/_images/2022/hospital-hall.jpg" alt="" /></p>
<p>Here is part of what I wrote to the hospital once my mom was released to give you a bit of an idea of the rollercoaster we were on during our visit there.</p>
<blockquote>
<p>For people who are not familiar with that world, the hospital is a cold, loud, terrifying place, and doubly so if you're the one receiving treatment. Your body is a machine and it's under the control of a big, scary system that's primarily there to treat a machine. Alarms, PA announcements, procedures ... it all combines to give you a sense of dread for what's coming next. After weeks of being in this environment, it starts to weigh very heavily on your psyche.</p>
<p>If it weren't for people like you and the vast majority of the staff that I interacted with during her stay, this would have been more than I or my mom could handle.</p>
<p>Thanks to the care and honesty and communication I experienced, I always felt like my mom was in very good hands, even when she looked like she would have rather died than go through this ordeal. At every turn, I was asked if I was comfortable, if I had any questions, if I needed anything. I saw people treating my mom with care, talking to her like a person despite her aphasia, and giving her choices wherever possible.</p>
<p>With an injury like this, you have to find positivity and hope wherever you can. I never felt like anyone tried to make me feel better just for the sake of it. I was given vague but kind and clear answers when the prognosis was unclear. And I was told when I was doing a good job as a caregiver, something that from the first minute of this experience was the most important job in my world. Your unit in particular gave me hope that, with hard work and the right therapy, she could actually end up OK.</p>
<p>You all work with people during their most vulnerable and frightened time, angry at circumstances they couldn't control, facing the indefinite loss of functions of their body that they took for granted for their whole life. The heart and patience you have to have to do that job, all while helping caregivers navigate this horrifying new world, is impossible to quantify. Seeing my mom go through what she has so far has been the most difficult thing I've ever lived through and without the support and smiles and kind words I received, I would have been in a much, much worse condition than I am in now.</p>
</blockquote>
<p>I hope that if you are going through something like this now that you are able to see your care team and the surrounding staff as teammates through this ordeal, rather than a force to reckon with. I think some (most?) of that comes down to your approach.</p>
<h2>Learn to be an advocate</h2>
<p>Sometimes you need to be persistent and repetitive to get what you need. For all of its talented people, policies and procedures, and technology, acute medical care in a hospital is still an absolute circus focused on treating the human body as a machine. If your loved one is non-communicative or not responsive, as my mom was for most of her visit, your job is <em>incredibly</em> hard and <em>incredibly</em> important.</p>
<p>Your job as an advocate is to speak up on behalf of the patient. In the case of my mom, she was not able to speak for most of her hospital stay so she couldn't tell them that she has claustrophobia and being strapped to a bed is like torture for her. She couldn't tell them that the constant alarms are making it hard to sleep or that she wants the door open so she can see what's going on in the hall, or that she likes iced tea, not apple juice. Each time I visited I would fix up her room, fill out meal preferences, and change the lights or shades.</p>
<p>But that's the easy part of the job, expressing preferences. The hard part comes when you get the funny feeling that things around you aren't quite as well orchestrated as you would expect. You might need to tell a doctor what you heard from an LNP just the day before and hold your tongue when they look surprised. You might need to explain (kindly) to multiple nurses that your mom is, in fact, able to get herself to the bathroom just fine if she is unstrapped. You might need to stand up to specially-trained staff and question the approach that lead to your loved one ending up in tears.</p>
<p>There is no instruction guide to doing all of this correctly and there is no guarantee that you will cover all the bases and connect all the dots. Just the fact that you need to do so much unpaid and unscheduled work might shake any confidence you had left in health care. You might ask yourself, what happens to the people that don't have someone like me around?</p>
<p>One technique that helped me was asking questions, all of the questions. I found that phrasing my comments as a question made it easier to call attention to things without resorting to accusations. In some cases, my assumptions were wrong so the question let me save face and get more information. In others, it let the person I was talking to know that I was having a problem and let them come to me with an explanation or solution. If you're curious, confused, or suspicious, just ask the question.</p>
<h2>Parting words: the same but different</h2>
<p>When I started collecting notes and thoughts for this post, I expected pages and pages and pages of experiences and emotions and helpful words to pour out of me. This journey has changes me in so many ways and I feel like I have a lot of support within me to offer someone going through something similar.</p>
<p>But when it came time to put this all down into a helpful guide on a technology-focused blog, I was left feeling a bit flat. How could you possibly boil down some of the hardest months of my life into something that would help someone just starting down that road? And how much of this advice is applicable versus just life lessons I was due to learn?</p>
<p>So, in the end, I didn't worry too much about writing the perfect companion piece to the unique suffering that is helping a loved one through a traumatic event like this. The fact is, I reached out desperately to so many people around me and was met with a tidal wave of support, advice, love, and information that made this whole thing go about as good as one could expect. The advice above is tactically helpful across a range of situations, I believe, and might cover some ground that you have not considered before. In that way, I hope some of my words will come to you at the right time and help you in some small way.</p>
<p>If you are going through this now or are worried that you might in the near future, I don't think there is really anything I could tell you that will truly prepare you for what's to come. You will feel strong, very strong, weak, and very weak. You will be surprised by what you're capable of one minute and then disappointed by yourself the next. You will kick yourself over and over for not spending more time with this person, not asking those questions you've been meaning to ask, not learning about their history and yours. You will do all of this or none or it, or part of it and something else.</p>
<p>But you will go through it, kicking and screaming or standing tall or both. And you will change, for better or worse or both. You will never quite be the same after this and there's nothing you can do about that but accept it.</p>
<p>I wish you the best.</p>
<p><img src="https://www.joshcanhelp.com/_images/2022/mom-beach.jpg" alt="" /></p>
<h2 class="hr" id="references">
<span class="pink"><</span>
References
<span class="pink">></span>
</h2>
<ul>
<li><a href="https://trevorklee.substack.com/p/things-ive-noticed-while-visiting">Things I've noticed while visiting the ICU</a> and the <a href="https://news.ycombinator.com/item?id=33661482">HN discussion thread</a></li>
<li><a href="http://www.buzzfeed.com/xtinehlee/i-had-a-stroke-at-33">I Had a Stroke at 33</a></li>
</ul>
Engineering Velocity from the Bottom Up2022-03-07T00:00:00Zhttps://www.joshcanhelp.com/engineering-velocity/When I look to increase my velocity, I'm trying to deliver more work I'm proud of without burning out. I want to get the most out of my time and maximize the parts of the job that I enjoy. Here's how.<p>What comes to mind when you hear the term "engineering velocity?"</p>
<img src="https://www.joshcanhelp.com/_images/2022/gt40-engine.jpg" class="aligncenter" alt="" />
<p>If you take <a href="https://en.wikipedia.org/wiki/Velocity_(software_development)">Wikipedia's definition</a>:</p>
<blockquote>
<p>The main idea behind velocity is to help teams estimate how much work they can complete in a given time period based on how quickly similar work was previously completed.</p>
</blockquote>
<p>... then you might have an urge to run away screaming. Work in this industry long enough and you'll be exposed to many misguided attempts to do more with less.</p>
<p>I want to take a different tack, though, and take a look at it from the bottom up. If you enjoy the technical designing, code writing, and problem solving components of software engineering then <strong>you probably care just as much about your velocity as any product manager does</strong>. This kind of velocity is not about how fast you can get tickets across a board; you can increase that metric by shipping garbage, working overtime, and banking technical debt.</p>
<p>When I look to increase my velocity, I'm trying to <strong>deliver more work I'm proud of without burning out</strong>. I want to get the most out of my time and maximize the parts of the job that I enjoy.</p>
<blockquote>
<p>"Working hard is not valuable." <em>~ Senior Engineer at Google</em></p>
</blockquote>
<p>Spending hours debugging issues, re-running flaky integration tests, fretting over whether or not a PR is ready to merge ... all of these things are low value tasks that slow us down and make us spend cycles on work that, frankly, we're just not that into. Clicking, typing, and talking faster might get your PR shipped but maybe it’s time to slow down and look closely at the system you are working in.</p>
<blockquote>
<p>"... velocity improves when you focus on the details. It's usually an aggregation of interruptions and churn that are the biggest productivity threats to a team." ~ Engineering Director at Okta</p>
</blockquote>
<p>I talked with a number of folks at different levels here at Auth0 and elsewhere to see where they see velocity sinks. As I compiled what I got out of those conversations, I saw a pattern of work "pooling" in different places, usually at a transition point. Depending on how your team is structured, these might be transition points between individuals or even teams, compounding the problem.</p>
<p>This all adds up to a simple but powerful idea: <strong>your own day-to-day productivity is a result of a system.</strong></p>
<p>From Cal Newport's piece on the <a href="https://www.newyorker.com/culture/office-space/the-frustration-with-productivity-culture">Frustration with Productivity Culture</a>:</p>
<blockquote>
<p>Instead of demanding that employees individually produce more, we should instead seek systems that produce more given the same number of employees. This shift might seem subtle but its impact can be enormous, as it frees individuals from the complexity of optimizing output all on their own, and defuses the psychological torment of pitting the personal versus the professional.</p>
</blockquote>
<p>Your own velocity on a team has far more contributors than just your speed in writing code and tests. Use your <a href="https://www.joshcanhelp.com/we-need-your-beginners-mind/">beginner's mind</a> and start asking questions about all the levels below.</p>
<p>Your primary job as an individual in this system to be mindful of time sinks in the pipeline described below and take the time to fix or flag them. Shift away from "<em>we're behind; I need to work harder/faster/more</em>" and towards "<em>it seems like we've slowed down; let's figure out what part of the system could work better.</em>"</p>
<h2>Sequencing</h2>
<p>As I was putting this together, I kept thinking about a sequence diagram. Successful, deployed changes to an application flow from the initial state to the end state. Thinking about it end-to-end acted as a good reminder that my velocity does not start or end in my editor.</p>
<p><a href="https://swimlanes.io/u/KjpfVHTFT" target="_blank_"><img src="https://www.joshcanhelp.com/_images/2022/engineering-velocity.png" class="aligncenter" alt="Engineering velocity sequence" /></a></p>
<p>The stages in the diagram above are explored below. Think of this less as a "how to do this exactly right" and more of a reminder to look for time sinks at each stage and gauge them relative to the others.</p>
<h3>Vision</h3>
<p>This is where your impact as an engineer comes into focus but your influence at this stage does not have a huge effect on your velocity so I'm not going to spend much time on it here. Suffice to say, an unclear or unknown vision will cause a lot of indecision and confusion at all levels of an organization.</p>
<p>You might hear the word "alignment" in planning meetings and your eyes might glaze over but it turns out, in the strange game of telephone that is software development, aligning what you create with the overarching visions of your team, product, and company is one way to avoid do-overs. Double if you call out misalignment before work starts.</p>
<p>If you're not sure how the specific thing you're building connects to the company strategy at large, <strong>ask the question!</strong> Seeing the big picture of the impact your change contributed to helps you to make decisions, both large and small, more easily and with more confidence.</p>
<h4>Things that can help at this stage:</h4>
<ul>
<li>Links to company, domain, team, and product-specific vision documents from engineering artifacts</li>
<li>Engineering participation in product meetings and documents</li>
<li>Always asking “what is the job to be done” at all stages, large and small</li>
<li>More great tips in <a href="https://newsletter.pragmaticengineer.com/p/working-with-product-managers-advice-from-pms">Working with Product Managers: Advice from PMs</a></li>
</ul>
<h3>Planning</h3>
<p>This is where your impact is getting stronger but we're still just fading in. The planning phase is typically where engineers start taking over or are, at least, heavily involved. At this point, the picture of what we are building should be much more clear. Visual designs are at or near their highest fidelity as we move from "what" to "how."</p>
<p>Before starting the research for this post, I was already convinced that planning was at least as important as writing code. Once I consolidated all the feedback I got, it was clear that this step might outweigh the rest in terms of proactively creating the right environment to move fast both as a team and as individuals.</p>
<p>In terms of velocity, planning helps us do 3 critical things:</p>
<ul>
<li><strong>Make sure we're shipping incremental value.</strong> This switches our thinking from "faster" to "earlier." What's the smallest piece that we can get into use?</li>
<li><strong>Avoid re-work and churn.</strong> Building the wrong thing and finding bugs in production are huge time sinks and morale killers.</li>
<li><strong>Help reviewers to gate on the right things.</strong> Technical aspects of a PR are important but so is shipping something correct and complete.</li>
</ul>
<p>Easy, right?</p>
<h4>Things that can help at this stage:</h4>
<ul>
<li>Early user research</li>
<li>Designs that cover the targeted use cases</li>
<li>Acceptance criteria for the milestone that defines done</li>
<li>Acceptance criteria for individual tasks that add up to a completed milestone</li>
<li>All-team effort on breaking down work and writing requirements</li>
</ul>
<h3>Building Context</h3>
<p>This is part of the this pipeline that often gets left out of the conversation. At some point, all of this vision-ing and planning and pontificating turns into functional software. There is this magical moment between planning and code at which a Jira story blossoms into a beautiful technical path forward. <strong>That's this stage.</strong></p>
<p>Arguing about design in pull requests is expensive and inefficient. Proposing a design ahead of time for your team and/or architects to review saves time down the road and avoids the "sunk cost" pressure you might feel when someone is suggesting a rewrite in a pull request.</p>
<p>But building context is not just about explaining the perfect thing in your head so other people get it. Writing about and diagramming and sequencing a large and/or complex part of your system helps you develop a more complete picture yourself, including dependencies, potential security flaws, performance issues, gaps in design, etc.</p>
<p>From <a href="https://www.thezbook.com/the-biggest-mistake-i-see-engineers-make/">The Biggest Mistake I See Engineers Make</a>:</p>
<blockquote>
<p>When you work on a team, you shouldn’t be in competition ... you are working cooperatively to ship the best possible product, as quickly as possible. And there’s a huge advantage in leveraging the team’s collective wisdom to build better and faster.</p>
</blockquote>
<p>Finally, all this context-building at this stage is helpful down the road when you’re asking yourself “how does this work? why did we build it this way?”</p>
<h4>Things that can help at this stage:</h4>
<ul>
<li>Technical design documents or diagrams and a system to organize them</li>
<li>Spikes to gauge feasibility and complexity</li>
<li>Decision-making frameworks like DACI to help weigh options</li>
<li>Up-to-date architecture documentation for the service we're creating or using</li>
<li>Clearly documented data model(s)</li>
<li>Spending time onboarding new team members</li>
<li>Protecting time needed to write the docs (internal and external)</li>
</ul>
<h3>Writing Code</h3>
<p>Ah, the magic land of headphones, keyboards, and caffeine ... or whatever combination gets your fingers moving.</p>
<p>In this section, you will not find great tips to write code faster. We're going to just assume that you write code plenty fast enough. I'm also not going to wade into any controversial waters here (TDD, OO vs functional, code comments) because that goes against the overall point I'm trying to make here.</p>
<p>What I suggest concentrating on are all the things that slow you down when you're <em>not</em> writing code. Things like …</p>
<p>… does your test suite take so long you can take a shower while waiting?<br />
… how often are you fighting with your local environment?<br />
… how much focus time can you (or do you) carve out for yourself?<br />
… how much time do you take searching through folder trees for a file?<br />
… how clear are the error messages when something goes wrong?</p>
<p>We all know that feeling that comes from working on something small or in the beginning. The tests run in seconds, the architecture is dead simple, and you can move at a super-human pace. Then you switch over to the app your team has been maintaining for 5 years and it's all swear words and hair pulling (ask me about the time I left vulgarities in demo content). The latter will never be the former but it's easy to find yourself regularly sinking the same 15 or 20 minutes into a task or problem in the name of "shipping."</p>
<h4>Things that can help at this stage:</h4>
<ul>
<li>Stable unit and integration tests that can be run per file or group</li>
<li>Initial setup and onboarding documentation</li>
<li>Scout's mindset on local environment startup, performance, and issues</li>
<li>Debugging profiles, guides, pairs, and FAQs</li>
<li>Regular, protected blocks of focus time > 1 hour</li>
</ul>
<h3>Reviewing</h3>
<p>Love it or hate it, code reviews are a critical part of the job. You will, of course, be working in a culture as a part of a team within an organization that probably has a way of doing things, for better or worse. As such, there is likely a limit to the changes you can make to the process. Like the rest of this post, though, limitations should not stop you from proposing and talking about ways to make this stage faster and less painful.</p>
<p>I'm personally fascinated by this stage of the process because it's such a <a href="https://en.wikipedia.org/wiki/Sociotechnical_system">sociotechnical</a> minefield! I'm submitting my hard work for review by someone I probably don't know all <em>that</em> well with part of me hoping I don't get called out for something stupid and part of me relieved to not be held completely accountable for a potential flaw that causes a data leak that appears on the news. Even if I adore the people I work with, <strong>I'm still gutted when they find that inevitable stupid mistake I made.</strong></p>
<p>Depending on the flexibility your team has, start with an honest, open conversation about what makes this stage difficult. Everyone should know what constitutes a complete review so reviewers aren't scared to approve (or reject) a PR and authors aren't motivated to spend an inordinate amount of time gold-plating (aka making it perfect) trying to avoid embarrassment.</p>
<h4>Things that can help at this stage:</h4>
<ul>
<li>Automated linting, checks, and tests</li>
<li>Reducing PR sizes to encourage faster, more effective reviews</li>
<li>Consistent review criteria across team members</li>
<li>Spending more time pairing and reviewing with new team members</li>
<li>Clear success criteria for the task being worked on</li>
<li><a href="https://www.joshcanhelp.com/rock-your-code-reviews-webinar/">More tips on my notes from from "Rock Your Code Reviews"</a>.</li>
</ul>
<h3>Deployment</h3>
<p>Entire books have been written on deployment methods and strategies. I'm far from an expert in this domain but I can tell you a story about how I have come to understand the importance of a healthy deployment pipeline.</p>
<p>I started my development career as a freelancer. When you're building sites and applications yourself, there is not a lot of time in the budget, paid or otherwise, to worry too much about what happens after you "merge" (or, in this case, "copy to the production server via FTP") your changes. In the most critical cases, I would have a very long manual script I would maintain to test out all the various use and edge cases once that code was in production. Discover a new problem in production? Add a new block of bullet points.</p>
<p>Once I started working at Auth0 and seeing how it's done when the stakes are much higher and when you have teams of people worrying about these kinds of things, <strong>my mind was blown</strong>. I thought back to countless hours I spent debugging a critical issue in production using only haphazard logging and sparse email descriptions of the error messages. At the time, I thought "there must be a better way" but a lack of knowledge and development budget kept the system functioning at the same, sub-optimal level.</p>
<h4>Things that can help at this stage:</h4>
<ul>
<li>Functional or E2E tests that check your changes against the entire system</li>
<li>Documentation for common pitfalls, errors, and flakiness</li>
<li>Continuous improvement of observability systems</li>
<li>Clear general and change-specific rollback plans</li>
<li>Protecting time post-merge to shepherd the change through</li>
</ul>
<h3>Staging</h3>
<p>Catching bugs in production is expensive and rebuilding incorrectly-implemented functionality is even worse. Your goal at this stage is general acceptance of the current trajectory of what you're building. Think of it like a vector: does the magnitude and direction look correct at this point?</p>
<p>What you're doing at this stage is creating a way to review that's a level higher than individual changesets of code. This higher level creates a stronger connection between the changes you're making and the vision. You can think about this kind of hands-on testing as closing the loop on what you just made.</p>
<h4>Things that can help at this stage:</h4>
<ul>
<li>Regular demos to reviewers, the team, and stakeholders</li>
<li>Early and thorough testing by designers and stakeholders</li>
<li>Early access customer testing</li>
<li>Easy and simple issue reporting</li>
<li>Clear documentation on how to pause the promotion to production</li>
</ul>
<h2>“Umm, this is a lot Josh”</h2>
<p>Yeah, I feel you, this <em>is</em> a lot.</p>
<p>This post was mainly an exercise to wrap my head around all of the different things I've heard, read, and implemented to help myself and my team ship well. There are a lot of things to consider and we're all going to have to be OK with this simply never being a destination we reach.</p>
<p>But perfection here is not the goal. Technology moves fast, we're all human, and, at some point, there is an inflection point of return on investment of time. I hope that this can be a helpful rubric for folks to use when they start getting that sinking feeling that something is wrong.</p>
<p>In the end, I think it's critically important for us as individuals to move beyond a mindset that puts the responsibility for our productivity solely in our hands. Pushing yourself to write faster or work longer or check more boxes in a day <em>might</em> work in the short term but you risk burnout, dissatisfaction, and frustration. Instead, <strong>look at the systems that you're a part of and accept that your editor window is just a small piece of the puzzle.</strong></p>
<h2 class="hr" id="read-more">
<span class="pink"><</span>
Read More
<span class="pink">></span>
</h2>
<ul>
<li><a href="https://leaddev.com/productivity-eng-velocity/debugging-engineering-velocity-and-leading-high-performing-teams">Debugging engineering velocity and leading high-performing teams</a></li>
<li><a href="https://www.newyorker.com/culture/office-space/the-frustration-with-productivity-culture">The Frustration with Productivity Culture</a></li>
<li><a href="https://www.thezbook.com/the-biggest-mistake-i-see-engineers-make/">The Biggest Mistake I See Engineers Make</a></li>
<li><a href="https://newsletter.pragmaticengineer.com/p/working-with-product-managers-advice-from-pms">Working with Product Managers: Advice from PMs</a></li>
<li>A colleague recommends <a href="https://www.amazon.com/Philosophy-Software-Design-2nd/dp/173210221X">A Philosophy of Software Design</a> if you are, in fact, interested in improving how you write code.</li>
</ul>
Generate new Eleventy post drafts with Hygen2022-01-11T00:00:00Zhttps://www.joshcanhelp.com/generate-eleventy-posts-with-hygen/Making new boilerplate files is one of many tiny professional pet peeves that makes me ask "what would a real engineer do?" Answer: automate it!<p>What do you do when you need to make a new file in a project?</p>
<p>If you're like me, you often duplicate an existing file that's similar and make changes from there. If you're also like me, this leads to frequent copy/pasta errors to the point where you wonder whether it would just be easier to type this stuff out each time. The term I picked up while writing this post is "boilerplate fatigue" and it's perfect.</p>
<p>This is one of many tiny professional pet peeves that makes me ask "what would a <em>real engineer</em> do?" The answer to that is usually the one that means the least amount of work and the most amount of consistency: "Automate it," says the voice of a real engineer in my head. 🤖</p>
<img src="https://www.joshcanhelp.com/_images/2022/xerox-sigma-9.jpg" class="aligncenter" alt="Xerox Sigma 9" />
<figcaption><em><p>The Xerox Sigma 9 introduced in 1971</p>
</em></figcaption>
<p>Enter <a href="https://www.hygen.io/">Hygen</a>. If you've never used a code generator before, the idea is to make creating new project files easier by providing a template that's hydrated with values from a prompt or command line flags. Instead of copying an existing file, changing what's there, remembering anything that was missing, and getting that sinking "doing it wrong" feeling, you run a command, answer a few questions, and you have a fresh, new file.</p>
<p>This appeals to me in my Eleventy site here because I have a number of custom fields in the front-matter that I often forget about or need to look up in layout files. These fields are a bit different for each content type and I often forget which ones can be used in a post and which in a page. I also have a number of shortcodes and patterns I use and I want a way to remind myself of what those are.</p>
<p>Let's see how this all comes together ...</p>
<hr />
<p>Follow <a href="https://github.com/jondot/hygen#quick-start">the quick start</a> to get Hygen installed on the command line. The point where this tutorial starts is where you can get the following response on the command line:</p>
<pre class="language-bash"><code class="language-bash">❯ hygen<br /><br />Error: please specify a generator.<br />Hygen v6.1.0</code></pre>
<p>Get yourself to the root of your Eleventy project and run the following:</p>
<pre class="language-bash"><code class="language-bash">❯ hygen init self<br /><span class="token comment"># ... adds generator templates</span><br /><br />❯ hygen generator with-prompt post<br /><span class="token comment"># ... adds post template</span></code></pre>
<p>⚠️ If you get a warning like "SyntaxError: Invalid left-hand side expression in prefix operation", the <a href="https://github.com/jondot/hygen/issues/321#issuecomment-938449496">file renaming solution here</a> fixed the issue for me.</p>
<p>What we did here was create the templates that create templates for what we want to create (so meta). Then we created the actual templates we'll use to generate new posts. You should now be able to run the generator and get a new file:</p>
<pre class="language-bash"><code class="language-bash">❯ hygen post with-prompt<br />✔ What's your message? · Begin again<br /><br />Loaded templates: _templates<br /> added: app/hello.js</code></pre>
<p>At this point, I've got 7 new files and a remote idea of what is going on here. I'm going to be making one of these for each of my <a href="https://www.joshcanhelp.com/eleventy-custom-content-type-collections/">content types</a> and the custom fields within.</p>
<p>There really is a bit of meta going on here so I'm going to start with the simplest task: adjusting our post generator to create posts in the right format. In general, my posts:</p>
<ul>
<li>are stored in a year sub-directory in a main posts directory</li>
<li>get their date and permalink from the file name</li>
<li>have a number of custom fields</li>
</ul>
<p>Let's adjust where the file name and location using <a href="http://www.hygen.io/docs/templates#conditional-rendering">conditional rendering</a> and a <a href="http://www.hygen.io/docs/templates#change-case-helpers">change case helper</a>:</p>
<pre class="language-text"><code class="language-text">// _templates/post/with-prompt/hello.ejs.t<br /><br />---<br /><br />to: input/posts/<%= (new Date()).getFullYear() %>/<%= (new Date()).getFullYear() %>-<%= (new Date()).getMonth() + 1 %>-<%= (new Date()).getDate() %>-<%= h.changeCase.param(message) %>.md<br /><br />---</code></pre>
<p>This takes the current year, along with the command line prompt response, and sticks a Markdown file in just the right place:</p>
<pre class="language-text"><code class="language-text">input/<br />├─ posts/<br />│ ├─ 2022/<br />│ │ ├─ 2022-1-11-post-draft.md</code></pre>
<p>That is a bit verbose, though, so I added a <code>.hygen.js</code> file to <a href="http://www.hygen.io/docs/extensibility">extend the helper object</a> with a few handy functions:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// .hygen.js</span><br /><br /><span class="token keyword">const</span> getYear <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getFullYear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> getMonth <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getMonth</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> getDay <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getDate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">helpers</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">getDate</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>getYear<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>getMonth<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>getDay<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /> getYear<br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>... and updated the post template:</p>
<pre class="language-text"><code class="language-text"><!-- _templates/post/with-prompt/hello.ejs.t --><br /><br />---<br />to: input/posts/<%= h.getYear %>/<%= h.getDate() %>-<%= h.changeCase.param(message) %>.md<br />---</code></pre>
<p><em>Much better!</em></p>
<p>We've got a file going to the right place, next we're going to construct the front matter that goes with each post. In order to find all these different fields, I combined:</p>
<ul>
<li>The <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/_includes/partials/head.njk">include file that creates the head output</a></li>
<li>The <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/_includes/layouts/post.njk">post layout template</a></li>
<li>The <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/_includes/partials/content-footer.njk">include file that outputs the content footer</a></li>
</ul>
<p>Figuring all this out for this post made me happy that I'm collecting it all together in one place. I can add all these as prompts in the creation process and not have to go hunting down what fields are possible when I create new posts.</p>
<p>The list of fields I use as of this writing are:</p>
<ul>
<li><code>title</code> for the main, visible title and incoming link text</li>
<li><code>meta_title</code> for the title tag (defaults to <code>title</code> in the template)</li>
<li><code>excerpt</code> for the short text on post listings</li>
<li><code>meta_description</code> (defaults to <code>excerpt</code> in the template)</li>
<li><code>featured_img</code> for the small thumbnail image on certain listing pages</li>
<li><code>tags</code></li>
<li><code>canonical_link</code></li>
<li><code>link_to</code> for any post that refers or replies to another URL</li>
<li><code>hn_link</code> for <a href="https://news.ycombinator.com/submitted?id=joshcanhelp">Hacker News submissions</a></li>
</ul>
<p>Each field needs a prompt in the <code>prompt.js</code> file for the generator. Most of these are just basic inputs but I took a look at the library Hygen uses for the prompts and ... <a href="https://github.com/enquirer/enquirer">wow</a>! The number of field types is <em>mind-blowing</em>!</p>
<p>One great feature of this library is the ability to write custom validation code. I use this for checking to make sure I'm not skipping required fields, like the title. The logic is simple here: return <code>true</code> if it passes validation or a string error message if not. See the next code block for how I'm using it for titles.</p>
<p>I'll be honest, I'd like to spend a day thinking of things to add to posts just so I can use more of these prompts but that's another day. For now, everything is a basic input besides tags. Here's the start of the prompt file:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// _templates/post/with-prompt/prompt.js</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span><br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'input'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'title'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">"Post title"</span><span class="token punctuation">,</span><br /> <span class="token function-variable function">validate</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">!</span>value <span class="token operator">?</span> <span class="token string">"Title cannot be empty"</span> <span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span><br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'list'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'tags'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">"Enter tags, separated by commas"</span><br /> <span class="token comment">// TODO: Validate against a list of existing tags</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /><span class="token punctuation">]</span></code></pre>
<p>... which prompts:</p>
<pre class="language-bash"><code class="language-bash">❯ hygen post with-prompt<br />✔ Post title · Generate posts <span class="token keyword">in</span> Eleventy<br />✔ Enter tags, separated by commas · Eleventy, JavaScript<br /><br />Loaded templates: _templates<br /> added: input/posts/2022/2022-1-10-generate-posts-in-eleventy.md</code></pre>
<p>... and, when combined with this template:</p>
<pre class="language-text"><code class="language-text"><!-- ... front matter ... --><br /><br />---<br />title: <%= title %><br />tags: ["<%- tags.join('\", \"') %>"]<br />---<br /><br />## Some markdown!</code></pre>
<p>... outputs:</p>
<pre class="language-text"><code class="language-text">---<br />title: Generate new posts in Eleventy with Hygen<br />tags: ["Eleventy", "JavaScript + TypeScript"]<br />---<br /><br />## Some markdown!</code></pre>
<p><strong>So awesome!</strong></p>
<p>For things like the <code>meta_title</code> that are optional, Hygen provides a way to check whether that value was entered and then output content if it is by using <a href="http://www.hygen.io/docs/templates#local-variables">local variables</a>. For the <code>list</code> prompt type, like <code>tags</code>, you need to check <code>locals.tags.length</code> because an empty array is truth-y in JavaScript.</p>
<p>Once I had all of the post front matter outputting, I added all of the <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/eleventy/shortcodes.js">shortcodes</a> and other formatting options I'm able to use in the body of the template. Then I copied all of this over to a <code>page</code> generator and removed the fields that are not used in that layout. I used the EJS <code>include</code> function and a <a href="https://www.hygen.io/docs/templates#predefined-variables">predefined variable</a> to import shared output to both templates.</p>
<p>By the end of this, I was fully in "human with a hammer" mode and thinking of all the places I could use Hygen. For now, though, just this will save time, mistakes, and annoyance!</p>
<h2 class="hr" id="references">
<span class="pink"><</span>
References
<span class="pink">></span>
</h2>
<ul>
<li><a href="https://github.com/joshcanhelp/josh-to-11/commit/b9d1507e9e791ae8a76c6b96dbd665bf6a049cb2">Full changeset to add Hygen to my site</a> and <a href="https://github.com/joshcanhelp/josh-to-11/commit/4fd3ab4e1fbcc89535e0eec43356b0034e2b2cf3">a few additional features</a></li>
<li><a href="http://www.hygen.io/">Hygen homepage and documentation</a> and <a href="https://github.com/jondot/hygen/">GitHub repo</a></li>
</ul>
Eleventy Custom Content Type Collections and Layouts2022-01-04T00:00:00Zhttps://www.joshcanhelp.com/eleventy-custom-content-type-collections/How-to on creating separate collections and layouts for a custom content type. In this case: cocktails!<p>I love a great cocktail. There's something magical about taking a poison that can power a car and make it taste like heaven.</p>
<p><img src="https://www.joshcanhelp.com/_images/2022/bartender.jpg" alt="" /></p>
<p>I've been keeping <a href="https://www.joshcanhelp.com/cocktails/">a list of cocktails</a> that I've made and a list of cocktails that I would like to make once I have the ingredients on hand. I kept them as little clusters of bullet points that I published in an app called Workflowy and just sent the link to anyone I happened to talk about it with. When I <a href="https://www.joshcanhelp.com/notes/">moved to Obsidian</a>, that online list went away but I still maintained the collection of recipes.</p>
<p>During some time away from work recently, it occurred to me that I could put off <em>countless</em> more important things if I just sat down and got this collection of recipes back online for the ~5 people that have the previous link. So I sat down and did just that.</p>
<p>I have not gone very deep with Eleventy since I <a href="https://www.joshcanhelp.com/taking-wordpress-to-eleventy/">converted this whole site earlier this year</a> and I was looking forward to getting to know the data cascade a little better. Most of what I've done on this blog was using <a href="https://www.11ty.dev/docs/data-frontmatter/">template front matter</a>, the blocks of data at the top of the <a href="https://raw.githubusercontent.com/joshcanhelp/josh-to-11/master/_content/post/2021/2021-11-01-taking-notes.md">Markdown files that get turned into posts</a>. Everything was defined in the content and there wasn't any reason to generate data of any kind.</p>
<p>But the content that made up these recipes was a bit different:</p>
<ul>
<li>The file names were the titles</li>
<li>Chronological order was not important</li>
<li>There were specific ingredients that I wanted to call out</li>
</ul>
<p>This, as well as the URL, all needed to be built programmatically so I wouldn't have to maintain any of those data blocks. I was also hoping I'd find a way to manage my post dates and URLs using their file name instead of explicitly in the front matter.</p>
<p>If you've worked in <a href="https://www.joshcanhelp.com/tag/wordpress/">WordPress</a> before then you're probably familiar with the concept of <a href="https://wordpress.org/support/article/post-types/#custom-post-types">custom post types</a>. These are developer-defined content types that can be edited and themed differently than the built-in posts and pages. They're great for unique content types like the ones I'm working with here and created the model that I needed in my head:</p>
<ul>
<li>I needed a single template file for individual content pieces of this type</li>
<li>I needed a page where they aggregate</li>
<li>I needed to parse the content for the ingredients that I wanted to display separately</li>
</ul>
<p>I started with a layout alias that pointed to an empty template file.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// .eleventy.js</span><br /><br />eleventyConfig<span class="token punctuation">.</span><span class="token function">addLayoutAlias</span><span class="token punctuation">(</span><span class="token string">"cocktail"</span><span class="token punctuation">,</span> <span class="token string">"layouts/cocktail.njk"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This would be the field I would look for when filtering the content into collections. This is not technically necessary but it gives us an easy way to answer the question "how should this content be handled" throughout our code.</p>
<p>Next, I added all the cocktails as Markdown files <a href="https://github.com/joshcanhelp/josh-to-11/tree/master/input/cocktails">in their own directory</a> in the main <code>input</code> directory where the rest of my content lives. A few things to call out here:</p>
<ul>
<li>The file names are Sentence Capitalized and function as the title for the page</li>
<li>Individual cocktails recipes are in a <code>made</code> or <code>next</code> directory, indicating whether I've made them or not</li>
<li>The <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/cocktails/made/Old%20Fashioned.md">individual recipes</a> have no front matter at all, everything is generated</li>
</ul>
<p>The magic here all happens in a <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/cocktails/cocktails.11tydata.js">template data file</a>. The docs are a little thin on this but there are two "levels" of data here:</p>
<ul>
<li>The top-level properties, like <code>layout</code> in the linked file above, are pulled as-is and used as defaults.</li>
<li>The properties under <code>eleventyComputed</code> are generated using template-specific data and will override all previous data.</li>
</ul>
<p>I set the <code>layout</code> to <code>"cocktail"</code> for everything in that directory and added <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/_includes/layouts/cocktail.njk">the layout file</a> to use. This layout looks pretty similar to <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/_includes/layouts/post.njk">the post layout</a> with one main difference: content is being passed through a <code>stripSquareBrackets</code> filter that <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/eleventy/filters.js#L20">strips out the brackets</a> my note-taking app, Obsidian, uses to link between local files. More on how these are used below.</p>
<p>So, we have a valid layout alias, content to work with, a layout to display that content, and a data file to tie it all together. Now it's time for the <code>eleventyComputed</code> magic!</p>
<p>The easiest was the <code>title</code> and <code>meta_title</code>. Those both come mostly as-is from the file name:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// input/cocktails/cocktails.11tydata.js</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token comment">// ... </span><br /> <span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">title</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>fileSlug<span class="token punctuation">,</span><br /> <span class="token function-variable function">meta_title</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>fileSlug <span class="token operator">+</span> <span class="token string">" Cocktail Recipe"</span><span class="token punctuation">,</span><br /> <span class="token comment">// ... </span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>That <code>data.page</code> object has some handy data from the file system as well as a few converted properties:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token comment">// From the file system:</span><br /> date<span class="token operator">:</span> <span class="token number">2021</span><span class="token number">-12</span>-12T01<span class="token operator">:</span><span class="token number">34</span><span class="token operator">:</span><span class="token number">32</span>.174Z<span class="token punctuation">,</span><br /> inputPath<span class="token operator">:</span> './input/cocktails/next/Yuletide Wave Punch.md'<span class="token punctuation">,</span><br /><br /> <span class="token comment">// Eleventy-generated</span><br /> fileSlug<span class="token operator">:</span> 'Yuletide Wave Punch'<span class="token punctuation">,</span><br /> filePathStem<span class="token operator">:</span> '/cocktails/next/Yuletide Wave Punch'<span class="token punctuation">,</span><br /> url<span class="token operator">:</span> '/cocktails/yuletide-wave-punch/'<span class="token punctuation">,</span><br /> outputPath<span class="token operator">:</span> '_dist/cocktails/yuletide-wave-punch/index.html'<br /><span class="token punctuation">}</span></code></pre>
<p>I used the <code>filePathStem</code> to figure out if the recipe was in the "made" group or not and added some content at the top of the recipe indicating it's status.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> madeIt <span class="token operator">=</span> data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>filePathStem<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">"/made/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The permalink for the cocktail is built from the file name by replacing non-letter characters with dashes and lower-casing the whole string. You can also use the <code>data.page.url</code> value, appending <code>/index.html</code> at the end. I had a few additional characters to pull out so I went with my own regex.</p>
<p>Finally, I wanted something akin to tags here based on specific ingredients in the recipe. I <a href="https://www.joshcanhelp.com/notes/">use Obsidian</a> to manage almost everything I write, including these recipes. It lets you use double square brackets <code>[[Like This]]</code> to link to other, related files. In these recipes, I use them to tie cocktails together by ingredients and keep notes on the specific ingredients themselves.</p>
<p><img src="https://www.joshcanhelp.com/_images/2022/obsidian-linked-mentions.png" alt="" /></p>
<p>I'm not able to replicate that inter-linking from Obsidian on my blog (yet) but I <em>can</em> use that information to tie cocktail recipes together. The content of the recipe itself is not available in these data files but it's easy to read the contents of the file being processed using the built-in Node file system module <code>fs</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// input/cocktails/cocktails.11tydata.js</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token comment">// ... </span><br /> <span class="token function-variable function">ingredients</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// Get the absolute path to the file</span><br /> <span class="token keyword">const</span> filePath <span class="token operator">=</span> data<span class="token punctuation">.</span>page<span class="token punctuation">.</span>inputPath<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"relative path"</span><span class="token punctuation">,</span> __dirname<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Read the file contents</span><br /> <span class="token keyword">const</span> fileContent <span class="token operator">=</span> <span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span>filePath<span class="token punctuation">,</span> <span class="token string">"utf8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Find all the bracketed content</span><br /> <span class="token keyword">const</span> ingredients <span class="token operator">=</span> fileContent<span class="token punctuation">.</span><span class="token function">matchAll</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\[\[[\w\d\s]*\]\]</span><span class="token regex-delimiter">/</span><span class="token regex-flags">gm</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Flatten the regex array and get rid of any duplicates</span><br /> <span class="token keyword">const</span> ingredientsFlat <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token operator">...</span>ingredients<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">flat</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token comment">// Ditch the brackets</span><br /> <span class="token keyword">return</span> ingredientsFlat<br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">ingredient</span><span class="token punctuation">)</span> <span class="token operator">=></span> ingredient<br /> <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"[["</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"]]"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This saves all the ingredients in brackets to an array that I can use to output on the page or find other cocktails that match.</p>
<blockquote class="warning-block"><p>The processing time for ~100 files was not visibly affected by reading the file content at least one extra time per file. If you start to get into the thousands of files, though, this might not be a viable solution. I read through several posts and issues and it was clear that this data had not been loaded by the time these data files are processed.</p>
</blockquote>
<p>At this point, we've got the individual cocktail recipes building their own pages but we need a list of cocktails separated by whether I've made them or now. For that, I used 2 <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/eleventy/collections.js#L54">custom collections</a> , <code>cocktailsMadeCollection</code> and <code>cocktailsNextCollection</code>, that use the <code>layout</code> property and the presence of the <code>made</code> folder to pull out the custom content types and sort into one or the other. The logic looks like this:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// .eleventy.js</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> config<span class="token punctuation">.</span><span class="token function">addCollection</span><span class="token punctuation">(</span><span class="token string">"cocktailsMadeCollection"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">collection</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> collection<span class="token punctuation">.</span><span class="token function">getAllSorted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">tpl</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> hasMadePath <span class="token operator">=</span> tpl<span class="token punctuation">.</span>filePathStem<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">"/made/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> hasMadePath <span class="token operator">&&</span> <span class="token string">"cocktail"</span> <span class="token operator">===</span> tpl<span class="token punctuation">.</span>data<span class="token punctuation">.</span>layout<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>alphaSortTitle<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The "I haven't made this" collection is the same except for the path being checked.</p>
<p>Last but not least, we need a layout to display these collections. With single pages, I always start with <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/pages/cocktails.md">a Markdown template</a> to make it easier to add any contextual content. I've had layout files with one-off content in HTML and it just ... <em>felt dirty</em>.</p>
<p>That template points to <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/_includes/layouts/cocktails.njk">this layout</a> which counts up and displays the two collections of recipes. The recipes are displayed with their ingredients compiled from the computed data.</p>
<p><strong>And that's that!</strong></p>
<p>I used what I learned to <a href="https://github.com/joshcanhelp/josh-to-11/blob/master/input/posts/posts.11tydata.js">dynamically handle some of the post data</a> as well. Managing posts is a bit easier and less to think about when I create new ones. This all came together quite easily, which is not surprising as that's been the case for the 2 years I've been using Eleventy. If you're looking to start writing on a static site or <a href="https://www.joshcanhelp.com/taking-wordpress-to-eleventy/">move over from WordPress</a>, I highly recommend it!</p>
<h2 class="hr" id="references">
<span class="pink"><</span>
References
<span class="pink">></span>
</h2>
<ul>
<li><a href="https://www.joshcanhelp.com/cocktails/">The full list of cocktails</a> and <a href="http://localhost:8080/cocktails/vaquero/">my favorite</a></li>
<li><a href="https://github.com/joshcanhelp/josh-to-11/pull/27/files">PR to add this whole system</a> and the <a href="https://github.com/joshcanhelp/josh-to-11/pull/28/files">PR to add all the recipes</a></li>
<li><a href="https://www.11ty.dev/docs/data-template-dir/">Eleventy docs on template data files</a></li>
<li><a href="https://www.11ty.dev/docs/collections/">Eleventy docs on collections</a></li>
<li><a href="https://benmyers.dev/blog/eleventy-data-cascade/">Run-down of the data cascade in Eleventy</a></li>
</ul>