<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Luis Martinez - Web Developer</title>
    <subtitle>Articles on web development, TypeScript, accessibility and building software with AI agents, by Luis Martinez.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://www.webdevluis.com/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://www.webdevluis.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-08-23T00:00:00+00:00</updated>
    <id>https://www.webdevluis.com/atom.xml</id>
    <entry xml:lang="en">
        <title>How to Use Two GitHub Accounts on One Machine Without Switching Anything</title>
        <published>2026-08-23T00:00:00+00:00</published>
        <updated>2026-08-23T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/two-github-accounts-one-machine/"/>
        <id>https://www.webdevluis.com/blog/two-github-accounts-one-machine/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/two-github-accounts-one-machine/">&lt;p&gt;Tl,dr:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One default GitHub account. A second account that takes over for every repo under one folder.&lt;/li&gt;
&lt;li&gt;No switching. No &lt;code&gt;git config user.email&lt;/code&gt; per clone. No &quot;oops, pushed to work as my personal self&quot;.&lt;/li&gt;
&lt;li&gt;Three separate things have to agree: your git identity, your SSH key, and the &lt;code&gt;gh&lt;/code&gt; CLI. They live in three different places and none of them know about each other.&lt;/li&gt;
&lt;li&gt;Two traps will silently break this and give you no error message. Both are at the bottom, with proof.&lt;/li&gt;
&lt;li&gt;Two more things that break it later: logging in a new account hijacks your global default, and moving a scoped folder reverts that tree to your default identity without complaining.&lt;/li&gt;
&lt;li&gt;There is a copy-paste prompt at the end of each part, if you want an agent to just do it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-is-this-so-annoying&quot;&gt;Why is this so annoying?&lt;/h2&gt;
&lt;p&gt;You have a personal GitHub account. Then you get a client, or a job, and now you have a work GitHub account too.&lt;/p&gt;
&lt;p&gt;So you clone the work repo and push.&lt;/p&gt;
&lt;p&gt;And the commit shows up under your personal name and email.&lt;/p&gt;
&lt;p&gt;Or worse, it doesn&#39;t show up at all, because your personal SSH key has no access to the work org, and git yells at you about a repository that &quot;does not exist&quot;.&lt;/p&gt;
&lt;p&gt;The usual fix you land on is &lt;code&gt;git config user.email work@example.com&lt;/code&gt; in every work clone.&lt;/p&gt;
&lt;p&gt;That works until you forget once.&lt;/p&gt;
&lt;p&gt;I wanted a rule instead:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Everything under &lt;code&gt;~/dev/work&lt;/code&gt; is the work account. Everything else is me.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No thinking. No switching. Just a folder.&lt;/p&gt;
&lt;p&gt;Here&#39;s how to build that from nothing.&lt;/p&gt;
&lt;h2 id=&quot;why-is-this-three-problems-and-not-one&quot;&gt;Why is this three problems and not one?&lt;/h2&gt;
&lt;p&gt;This is the part that trips people up, so it&#39;s worth 30 seconds of your time.&lt;/p&gt;
&lt;p&gt;When you push to GitHub, three completely independent systems get involved.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identity.&lt;/strong&gt; The name and email baked into the commit object. This is just text. Git does not verify it. Lives in &lt;code&gt;~/.gitconfig&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transport.&lt;/strong&gt; The SSH key that proves to GitHub who you are. This is what actually decides whether your push is allowed. Lives in &lt;code&gt;~/.ssh/config&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The API.&lt;/strong&gt; The &lt;code&gt;gh&lt;/code&gt; CLI, for &lt;code&gt;gh pr create&lt;/code&gt;, &lt;code&gt;gh repo view&lt;/code&gt; and friends. Its own token, its own keyring. Knows nothing about your git config or your SSH setup.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can get all three wrong independently.&lt;/p&gt;
&lt;p&gt;Think about how bad that is. Your commit can be &lt;em&gt;authored&lt;/em&gt; as your work self, while being &lt;em&gt;pushed&lt;/em&gt; with your personal key, while &lt;code&gt;gh pr create&lt;/code&gt; fails because it&#39;s talking to GitHub as a third identity.&lt;/p&gt;
&lt;p&gt;So we configure all three. And we make all three key off the same folder.&lt;/p&gt;
&lt;p&gt;There&#39;s a fourth thing, commit signing, which is really a sub-problem of identity. I&#39;ll cover it. Get it wrong and GitHub stamps &quot;Unverified&quot; on every commit you make, and you will not know why.&lt;/p&gt;
&lt;h2 id=&quot;part-1-how-do-you-set-it-up-from-scratch&quot;&gt;Part 1: How do you set it up from scratch?&lt;/h2&gt;
&lt;p&gt;Assume your machine is completely fresh. Nothing configured.&lt;/p&gt;
&lt;p&gt;I&#39;ll use these placeholders. Swap in your own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;personal-user&lt;/code&gt; and &lt;code&gt;work-user&lt;/code&gt;: the two GitHub usernames&lt;/li&gt;
&lt;li&gt;&lt;code&gt;personal@example.com&lt;/code&gt; and &lt;code&gt;work@example.com&lt;/code&gt;: the two emails&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/dev/work&lt;/code&gt;: the folder that belongs to the work account&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-make-two-ssh-keys&quot;&gt;1. Make two SSH keys&lt;/h3&gt;
&lt;p&gt;One key per account. GitHub will NOT let two accounts share a key.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ssh-keygen&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ed25519&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;personal@example.com&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/id_ed25519&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ssh-keygen&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ed25519&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;work@example.com&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;     -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/id_ed25519_work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add each of your public keys to its own GitHub account, under &lt;strong&gt;Settings → SSH and GPG keys → New SSH key&lt;/strong&gt;, key type &lt;strong&gt;Authentication&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/id_ed25519.pub&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; paste into personal-user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/id_ed25519_work.pub&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; paste into work-user&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;2-write-ssh-config&quot;&gt;2. Write &lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is your default. It pins your personal key for github.com.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Host github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  HostName github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  User git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentityFile ~/.ssh/id_ed25519&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentitiesOnly yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That last line is not optional, and it&#39;s the one you&#39;ll leave out.&lt;/p&gt;
&lt;p&gt;Why does it matter? Without it, SSH offers every key in your agent, in agent order, and GitHub accepts the first valid one. Both of your keys are valid.&lt;/p&gt;
&lt;p&gt;So the account you land on depends on which key you happened to load first.&lt;/p&gt;
&lt;p&gt;It will work for weeks and then break on a Tuesday.&lt;/p&gt;
&lt;h3 id=&quot;3-write-a-second-ssh-config-for-work-only&quot;&gt;3. Write a second SSH config, for work only&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;~/.ssh/config-work&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Host github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  HostName github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  User git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentityFile ~/.ssh/id_ed25519_work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentitiesOnly yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, it declares &lt;code&gt;Host github.com&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;That&#39;s the whole point. This file is going to &lt;em&gt;replace&lt;/em&gt; &lt;code&gt;~/.ssh/config&lt;/code&gt; for work repos, not get merged into it.&lt;/p&gt;
&lt;p&gt;Lock both down:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 600&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/config&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/config-work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;4-write-gitconfig&quot;&gt;4. Write &lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Your default identity, plus one conditional include at the bottom.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Your Name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	email&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; personal@example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	defaultBranch&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Always reach GitHub over SSH, never HTTPS, so the KEY decides the account&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; instead of a cached token.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;url &amp;quot;git@github.com:&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	insteadOf&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; https://github.com/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Work account override. This is LAST on purpose: git applies config in file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; order, so whatever is in here wins inside that folder.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;includeIf &amp;quot;gitdir:/home/you/dev/work/&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	path&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; /home/you/dev/work/.gitconfig-work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three things you should know about that &lt;code&gt;includeIf&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use an absolute path.&lt;/strong&gt; &lt;code&gt;~&lt;/code&gt; works in newer git versions. Absolute paths always work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the trailing slash.&lt;/strong&gt; &lt;code&gt;gitdir:/home/you/dev/work/&lt;/code&gt; matches that folder and everything below it. Drop the slash and you&#39;re matching a filename pattern instead, and it gets weird.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It matches where the REPO is, not where you are.&lt;/strong&gt; So &lt;code&gt;git -C ~/dev/work/thing status&lt;/code&gt; from anywhere still gets you the work identity. That&#39;s good, and it&#39;s why the folder rule is reliable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also doesn&#39;t leak into look-alike folders. &lt;code&gt;~/dev/work-notes&lt;/code&gt; and &lt;code&gt;~/dev/workX&lt;/code&gt; stay personal.&lt;/p&gt;
&lt;p&gt;I tested that one. It&#39;s exact.&lt;/p&gt;
&lt;h3 id=&quot;5-write-dev-work-gitconfig-work&quot;&gt;5. Write &lt;code&gt;~/dev/work/.gitconfig-work&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The override. Identity, SSH key, and signing.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Your Name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	email&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; work@example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Pin the work SSH key for every git network operation in this tree.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -F REPLACES ~/.ssh/config, so the personal key is not even a candidate.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	sshCommand&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ssh -F /home/you/.ssh/config-work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Sign with the work SSH key, not your personal GPG key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gpg&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	format&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ssh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	signingkey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; /home/you/.ssh/id_ed25519_work.pub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	gpgsign&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	gpgsign&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;core.sshCommand&lt;/code&gt; line is doing the heavy lifting here.&lt;/p&gt;
&lt;p&gt;It&#39;s the only mechanism I found that is genuinely airtight. The obvious alternatives are not, and I put both of them in the traps section at the bottom, with the output that proves it.&lt;/p&gt;
&lt;h3 id=&quot;6-make-commit-signing-actually-verify&quot;&gt;6. Make commit signing actually verify&lt;/h3&gt;
&lt;p&gt;If you turn on signing, do it properly or turn it off.&lt;/p&gt;
&lt;p&gt;Half-configured signing is worse than none. GitHub renders &quot;Unverified&quot; on every commit, and most people never work out why.&lt;/p&gt;
&lt;p&gt;Here&#39;s the rule GitHub uses: the signing key must be registered on the account, AND it must be associated with the email in the commit.&lt;/p&gt;
&lt;p&gt;So go to the &lt;strong&gt;work&lt;/strong&gt; account&#39;s &lt;strong&gt;Settings → SSH and GPG keys → New SSH key&lt;/strong&gt; and add the work public key a SECOND time, this time with key type &lt;strong&gt;Signing Key&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/id_ed25519_work.pub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same key, added twice. Once as Authentication, once as Signing. GitHub treats those as two different things.&lt;/p&gt;
&lt;p&gt;This is the step you are most likely to miss in the whole setup.&lt;/p&gt;
&lt;p&gt;You can check what&#39;s registered without even logging in, because it&#39;s public:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://api.github.com/users/work-user/ssh_signing_keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Empty array? Then GitHub will mark your commits Unverified, no matter how correct your local config is.&lt;/p&gt;
&lt;p&gt;While you&#39;re here, teach git to verify SSH signatures locally too. Otherwise &lt;code&gt;git log --show-signature&lt;/code&gt; just shrugs at you. Create &lt;code&gt;~/.config/git/allowed_signers&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;work@example.com ssh-ed25519 AAAAC3Nz...paste the key part here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And point git at it in &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gpg &amp;quot;ssh&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	allowedSignersFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; /home/you/.config/git/allowed_signers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;7-make-the-gh-cli-follow-the-folder-too&quot;&gt;7. Make the gh CLI follow the folder too&lt;/h3&gt;
&lt;p&gt;Log into both of your accounts. &lt;code&gt;gh&lt;/code&gt; supports this natively and keeps both tokens around:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; login&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;   #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; do this twice, once per account&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; should list both&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing that will catch you on that second login: &lt;code&gt;gh auth login&lt;/code&gt; has &lt;strong&gt;no &lt;code&gt;--user&lt;/code&gt; flag&lt;/strong&gt;. You cannot tell it which account to log in as.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ gh auth login -u work-user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unknown shorthand flag: &amp;#39;u&amp;#39; in -u&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The account is decided by whoever you happen to be signed in as &lt;em&gt;in the browser&lt;/em&gt; when you complete the flow. So before the second login, sign out of GitHub in your browser or run the flow in a private window. Otherwise you will authenticate as the same account twice and wonder why &lt;code&gt;gh auth status&lt;/code&gt; only ever lists one.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-u&lt;/code&gt; does exist on &lt;code&gt;gh auth token&lt;/code&gt; and &lt;code&gt;gh auth switch&lt;/code&gt;. Just not on &lt;code&gt;login&lt;/code&gt;. That inconsistency is the whole trap.&lt;/p&gt;
&lt;p&gt;One more thing, and it bites harder than it looks: &lt;strong&gt;&lt;code&gt;gh auth login&lt;/code&gt; makes whichever account you just logged in as the globally &lt;em&gt;active&lt;/em&gt; one.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The wrapper below injects a token per invocation for your &lt;em&gt;scoped&lt;/em&gt; folders, so those stay correct. But everywhere else it deliberately does nothing and lets &lt;code&gt;gh&lt;/code&gt; use the active account. Which you just changed.&lt;/p&gt;
&lt;p&gt;So the moment that second login finishes, every personal repo on the machine is quietly talking to GitHub as your work self:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cd ~/code/personal-thing &amp;amp;&amp;amp; gh api user --jq .login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;work-user          # wrong, and nothing told you&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Switch back once, right after logging in:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; switch&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; personal-user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;              #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; confirm: personal is Active, the rest are not&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the same failure mode as everything else in this post. Nothing errors. The wrong identity just becomes the default and waits.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;gh&lt;/code&gt; has no idea what folder you&#39;re in. It uses whichever account is globally &quot;active&quot;.&lt;/p&gt;
&lt;p&gt;So in a work repo you get this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ gh repo view&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GraphQL: Could not resolve to a Repository with the name &amp;#39;work-user/thing&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s asking as your personal self, which cannot see the repo.&lt;/p&gt;
&lt;p&gt;Now you will say: just use &lt;code&gt;gh auth switch&lt;/code&gt;, Luis!&lt;/p&gt;
&lt;p&gt;Sure. But that&#39;s global mutable state you have to remember to flip back, which is exactly the thing we&#39;re trying to get rid of.&lt;/p&gt;
&lt;p&gt;Better: a tiny wrapper on your &lt;code&gt;PATH&lt;/code&gt; that picks the account per invocation. Put this at &lt;code&gt;~/.local/bin/gh&lt;/code&gt;, and make sure &lt;code&gt;~/.local/bin&lt;/code&gt; comes before &lt;code&gt;/usr/bin&lt;/code&gt; in your &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#!&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Pick the GitHub account per invocation, mirroring the git folder rule.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WORK_TREE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;HOME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/work&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WORK_ACCOUNT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Owners belonging to the work account, for when a repo is named explicitly.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WORK_OWNERS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;work-user|SomeWorkOrg&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;use_work&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;WORK_TREE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; use_work&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; $&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =~&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span&gt;space&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;WORK_OWNERS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/ &lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; use_work&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;GH_REPO&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =~&lt;/span&gt;&lt;span&gt; ^&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;WORK_OWNERS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/ &lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; use_work&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Exclude `gh auth ...` so login/logout/status keep working on the real&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; account list instead of on an injected token.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;use_work&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -eq&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    token&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; token&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;WORK_ACCOUNT&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        GH_TOKEN&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; exec&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;gh: no stored token for &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;WORK_ACCOUNT&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&amp;amp;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; +x&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.local/bin/gh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GH_TOKEN&lt;/code&gt; is scoped to that one process. Your globally active account is never touched, so there&#39;s no state to forget to reset.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;WORK_OWNERS&lt;/code&gt; check is there for a case the folder rule genuinely can&#39;t cover: &lt;code&gt;gh pr list -R SomeWorkOrg/thing&lt;/code&gt;, run from your home directory. There&#39;s no repo folder to look at, so we match on the owner name instead.&lt;/p&gt;
&lt;p&gt;And notice it&#39;s a shell script on &lt;code&gt;PATH&lt;/code&gt;, not an alias or a shell function. That matters more than it looks. It keeps working inside scripts, inside Makefiles, in non-interactive shells, and when your editor or your AI agent shells out to &lt;code&gt;gh&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;8-verify-it-properly&quot;&gt;8. Verify it, properly&lt;/h3&gt;
&lt;p&gt;Do not trust &quot;it didn&#39;t error&quot;.&lt;/p&gt;
&lt;p&gt;Check what actually goes over the wire.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Which account does each tree authenticate as?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ssh&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; git@github.com&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                          #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; Hi personal-user!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ssh&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/config-work&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; git@github.com&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; Hi work-user!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Which identity does each repo resolve to?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/dev/work/some-repo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; user.email&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; work@example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/code/personal-thing&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; user.email&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; personal@example.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run that &lt;strong&gt;inside an actual repo&lt;/strong&gt;, and nowhere else. This is the one verification step that lies to you.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gitdir:&lt;/code&gt; matches the location of a &lt;code&gt;.git&lt;/code&gt; directory. If there isn&#39;t one, there is nothing to match, and git quietly falls back to your global identity:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cd ~/dev/work &amp;amp;&amp;amp; git config user.email      # the work folder itself&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;personal@example.com                          # looks broken, is fine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cd ~/dev/work/some-repo &amp;amp;&amp;amp; git config user.email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;work@example.com                              # the real answer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The top-level folder of your work tree is usually just a container, not a repo. Check it there and you will conclude the whole setup failed. &lt;code&gt;git init&lt;/code&gt; a throwaway repo inside instead.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Which key is actually OFFERED? This is the real test.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ssh&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/config-work&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; git@github.com&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&amp;amp;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; grep&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Offering public key&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You want exactly ONE line there, naming the work key. If you see two, &lt;code&gt;IdentitiesOnly&lt;/code&gt; is missing somewhere.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Real write access, without changing anything on the server.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/dev/work/some-repo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; push&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-dry-run&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; origin&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; HEAD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; gh follows the folder?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/dev/work&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; api&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; user&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-jq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .login&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;   #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; work-user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; api&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; user&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-jq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .login&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; personal-user&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a signing check, in a throwaway repo inside the work tree:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;mkdir&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/dev/work/tmp-check&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/dev/work/tmp-check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; init&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;q&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; commit&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-allow-empty&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; log&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-format=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;%an &amp;lt;%ae&amp;gt; sig:%G?&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; Your Name &amp;lt;work@example.com&amp;gt; sig:G&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ..&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; rm&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;rf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tmp-check&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sig:G&lt;/code&gt; means good signature. &lt;code&gt;sig:N&lt;/code&gt; means not signed. Anything else, something&#39;s off.&lt;/p&gt;
&lt;h3 id=&quot;can-an-agent-just-do-part-1-for-me&quot;&gt;Can an agent just do part 1 for me?&lt;/h3&gt;
&lt;p&gt;It can. Paste this into Claude Code, Codex, or whatever you use. It&#39;s written so the agent verifies things instead of assuming them.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Set up two GitHub accounts on this machine, scoped by folder. I want a default&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;account, plus a second account that automatically takes over for every repo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;under one specific folder. No manual switching, ever.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Ask me for these first, then do the work:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - default GitHub username + email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - second GitHub username + email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - the absolute path of the folder that belongs to the second account&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Configure all three layers so they agree:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1. IDENTITY. In ~/.gitconfig set the default user.name/user.email. At the very&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   BOTTOM of the file add an includeIf with a &amp;quot;gitdir:&amp;quot; condition pointing at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   the folder (absolute path, trailing slash) and loading a .gitconfig-work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   file inside it, which overrides user.email.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2. TRANSPORT. Generate a separate ed25519 key per account if they do not exist.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   In ~/.ssh/config pin the default key to Host github.com and include&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   &amp;quot;IdentitiesOnly yes&amp;quot;. Then create a SEPARATE file ~/.ssh/config-work that&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   also declares Host github.com but points at the second key, again with&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   IdentitiesOnly yes. In .gitconfig-work set:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       core.sshCommand = ssh -F /absolute/path/to/.ssh/config-work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   Do NOT try to solve this with url.insteadOf rewriting and do NOT try to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   solve it with &amp;quot;ssh -i&amp;quot;. Both silently fail. Verify why before trusting it:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     - insteadOf rules do not chain, and when two rules match the same prefix&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       length the global one wins, so an https:// remote inside the work tree&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       still resolves to the default key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     - &amp;quot;ssh -i key&amp;quot; does NOT override IdentityFile from ssh_config, they are&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       additive and the config entry gets offered first.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   Prove both of these on this machine and show me the output.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;3. SIGNING. In .gitconfig-work use SSH signing (gpg.format = ssh,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   user.signingkey = the work .pub, commit.gpgsign = true) so the signing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   identity matches the commit identity. Set gpg.ssh.allowedSignersFile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   globally and write the allowed_signers entry. Then TELL me to add that&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   public key to the second GitHub account a second time with key type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   &amp;quot;Signing Key&amp;quot; (not just Authentication), and check via&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   https://api.github.com/users/USERNAME/ssh_signing_keys whether it landed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;4. gh CLI. Log in to both accounts. Note that `gh auth login` has NO --user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   flag, so the account is chosen by whoever is signed in in the browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   during the flow. Tell me to use a private window for the second one. Then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   install a wrapper script on PATH (before&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   the real gh) that picks the account per invocation: if the working directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   is inside the work folder, or an explicitly named repo owner belongs to the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   work account, run the real gh with GH_TOKEN set from&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   `gh auth token -u WORK_USER`. Exclude `gh auth ...` from the wrapper. Never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   call `gh auth switch`, the globally active account must stay untouched.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Also check ~/.git-credentials. If a bare `[credential] helper = store` exists,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;it will silently copy tokens out of the keyring into a plaintext file. Scope it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;away from github.com and tell me if a stale token is sitting there.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Back up every file you touch before editing, and tell me where the backups are.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Then VERIFY and show me the output of each, do not just tell me it worked:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - ssh -T git@github.com, and with -F the work config, showing two different&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;Hi &amp;lt;user&amp;gt;!&amp;quot; responses&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - `ssh -v ... | grep &amp;#39;Offering public key&amp;#39;` for both, proving exactly ONE key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    is offered in each case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - git config user.email resolved inside and outside the folder, checked&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    INSIDE A REAL REPO in each case (includeIf &amp;quot;gitdir:&amp;quot; matches the location&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    of a .git directory, so in a bare folder it silently reports the global&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    identity and looks like a failure)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - git push --dry-run against a real repo in each tree&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - gh api user --jq .login from inside and outside the folder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - a signed test commit in a throwaway repo inside the work tree, showing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sig:G and the correct author email, then delete it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - that look-alike sibling folders (e.g. the folder name plus a suffix) do NOT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    inherit the work identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Report anything that does not pass instead of glossing over it.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;how-do-you-add-a-third-account&quot;&gt;How do you add a third account?&lt;/h2&gt;
&lt;p&gt;Once you have the pattern, another account is mechanical. Say a second client, &lt;code&gt;client-user&lt;/code&gt;, living in &lt;code&gt;~/dev/client&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Four steps. All of them copies of what you already have.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. A key.&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ssh-keygen&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ed25519&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;client@example.com&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/id_ed25519_client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add your public key to that GitHub account twice: once as &lt;strong&gt;Authentication&lt;/strong&gt;, once as &lt;strong&gt;Signing Key&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. An SSH config for it.&lt;/strong&gt; &lt;code&gt;~/.ssh/config-client&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Host github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  HostName github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  User git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentityFile ~/.ssh/id_ed25519_client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentitiesOnly yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. A git config for it.&lt;/strong&gt; &lt;code&gt;~/dev/client/.gitconfig-client&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Your Name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	email&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; client@example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	sshCommand&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ssh -F /home/you/.ssh/config-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gpg&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	format&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ssh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	signingkey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; /home/you/.ssh/id_ed25519_client.pub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	gpgsign&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	gpgsign&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. One more &lt;code&gt;includeIf&lt;/code&gt;&lt;/strong&gt;, appended to &lt;code&gt;~/.gitconfig&lt;/code&gt; after the existing one:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;includeIf &amp;quot;gitdir:/home/you/dev/client/&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	path&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; /home/you/dev/client/.gitconfig-client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Does the order matter? Only if the folders are nested. Last match wins. If they&#39;re siblings, order is irrelevant.&lt;/p&gt;
&lt;p&gt;Then add the account to the &lt;code&gt;gh&lt;/code&gt; wrapper. With three accounts a lookup table reads better than a chain of ifs:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#!&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; One row per NON-default account:  &amp;lt;gh-account&amp;gt;:&amp;lt;tree&amp;gt;:&amp;lt;owners-regex&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; The default account is deliberately absent. It is whatever `gh` has active&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; when no row matches.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ACCOUNTS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;work-user:&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;HOME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/work:work-user|SomeWorkOrg&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;client-user:&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;HOME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/client:client-user|ClientOrg&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;best&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cwd&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;ACCOUNTS&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    acct&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;%%&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; rest&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    tree&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;rest&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;%%&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; owners&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;rest&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; LONGEST matching tree wins, so a nested folder beats the one it sits in.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;cwd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;tree&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span&gt;tree&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -gt&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;best&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        account&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;acct&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; best&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span&gt;tree&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; An explicitly named owner outranks the folder rule.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; $&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =~&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span&gt;[[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span&gt;space&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;owners&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/ &lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;GH_REPO&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =~&lt;/span&gt;&lt;span&gt; ^&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;owners&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/ &lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        account&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;acct&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; best&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    token&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; token&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        GH_TOKEN&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; exec&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;gh: no stored token for &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&amp;amp;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;gh: run &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;gh auth login&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; and authenticate as &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&amp;amp;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;GH_BIN&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why a loop and not a &lt;code&gt;case&lt;/code&gt; statement? Because &lt;code&gt;case&lt;/code&gt; is &lt;strong&gt;first&lt;/strong&gt;-match-wins, and the &lt;code&gt;includeIf&lt;/code&gt; rule one section above is &lt;strong&gt;last&lt;/strong&gt;-match-wins. List your accounts in the same order in both places and nested folders resolve to opposite accounts:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;case &amp;quot;$CWD&amp;quot; in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;quot;$HOME/dev/work/&amp;quot;*)        account=work-user ;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;quot;$HOME/dev/work/client/&amp;quot;*) account=client-user ;;   # never reached&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;esac&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cwd = ~/dev/work/client/repo  -&amp;gt;  work-user      (git says client-user)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ranking by the length of the matching tree makes the wrapper agree with git instead of quietly disagreeing with it.&lt;/p&gt;
&lt;p&gt;Don&#39;t forget &lt;code&gt;gh auth login&lt;/code&gt; for the new account. Otherwise the wrapper finds no token and quietly falls through to your default.&lt;/p&gt;
&lt;p&gt;That failure is silent. Which is exactly why the version back in part 1 prints a warning.&lt;/p&gt;
&lt;p&gt;And then immediately switch back:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; switch&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; personal-user&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding a third account makes this worse than it was with two, because now the login you just did leaves the &lt;em&gt;newest&lt;/em&gt; account as the global default for every unscoped folder you own. Check all of your trees, not just the new one:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;o&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  printf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;%-32s %s\n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; api&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; user&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-jq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .login&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three different answers, each matching the folder. If they are all the same, your wrapper is not being reached at all. Check that &lt;code&gt;~/.local/bin&lt;/code&gt; really comes before &lt;code&gt;/usr/bin&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PATH&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; tr&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; grep&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;^(.*/\.local/bin|/usr/bin)$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;can-an-agent-do-part-2-too&quot;&gt;Can an agent do part 2 too?&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;This machine already has multi-account GitHub set up: a default account, plus&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;per-folder overrides driven by &amp;quot;includeIf gitdir:&amp;quot; entries in ~/.gitconfig,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;each loading a .gitconfig-NAME that sets user.email and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;core.sshCommand = ssh -F ~/.ssh/config-NAME, plus a gh wrapper on PATH that&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;picks the account per invocation.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Read those existing files first and follow the pattern exactly. Do not&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;redesign it and do not touch the accounts that already work.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Add one more account. Ask me for:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - the new GitHub username + email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - the absolute path of the folder it should own&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - any GitHub org names belonging to it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Then:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  1. Generate ~/.ssh/id_ed25519_NEW if it does not exist, chmod 600.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  2. Create ~/.ssh/config-NEW declaring Host github.com with that key and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     IdentitiesOnly yes, chmod 600.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  3. Create the folder&amp;#39;s .gitconfig-NEW with user.name/user.email,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     core.sshCommand pointing at config-NEW, and SSH commit signing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     (gpg.format = ssh, user.signingkey = the .pub, commit.gpgsign = true).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     Add the key to ~/.config/git/allowed_signers.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  4. Append one more includeIf to ~/.gitconfig. If the new folder is nested&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     inside an existing scoped folder, place it AFTER that one, since last&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     match wins. Say which case applies.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  5. Extend the gh wrapper with the new folder and its owners. Refactor to a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     lookup table if it is getting long, and rank directory matches by longest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     matching path, NOT with a `case` statement, which is first-match-wins and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     would disagree with git on nested folders. Keep the `gh auth` exclusion&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     and keep the warning when no token is found.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Tell me to run `gh auth login` for the new account. There is no --user flag on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;`gh auth login`, so warn me that the account is picked by whoever is signed in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;in the browser, and that I should use a private window. Also tell me to add the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;public key to that GitHub account TWICE: once as an Authentication key, once as&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;a Signing Key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;After I have logged in, remind me to run `gh auth switch -u DEFAULT_USER`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;`gh auth login` leaves the account it just logged in as globally ACTIVE, and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;the wrapper falls through to the active account for every unscoped folder, so&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;until I switch back all of my default repos silently resolve to the new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;account. Verify this by running `gh api user --jq .login` from EVERY tree,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;including the ones you did not touch, not just the new one.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Back up what you edit. Then verify and show real output:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - ssh -F ~/.ssh/config-NEW -T git@github.com returns the new username&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - `ssh -v ... | grep &amp;#39;Offering public key&amp;#39;` shows exactly ONE key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - git config user.email in the new folder, checked inside a REAL REPO (in a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    bare folder includeIf does not fire and you will get the global identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    and a false negative), and confirm the PRE-EXISTING accounts still resolve&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    correctly (regression check, this is the point)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - gh api user --jq .login from the new folder and from the old ones&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  - a signed empty commit in a throwaway repo in the new folder shows sig:G&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    with the right email, then delete it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;If I tell you the folder for an EXISTING account has moved, treat that as a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;different job: the path is hardcoded in four places and a missing includeIf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;path is silently ignored rather than erroring, so the tree just reverts to the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;default identity. Update all four, then verify. The four are the includeIf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CONDITION and its path= line (both, in ~/.gitconfig), the .gitconfig-NAME file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;itself which must move with the folder, the tree in the gh wrapper table, and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;any comments naming the old path. Run this first and make sure it comes back&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;empty afterwards:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    grep -rn &amp;quot;OLD/PATH&amp;quot; ~/.gitconfig ~/.ssh/config-* ~/.local/bin/gh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;~/.ssh/config-NAME and the keys are keyed on host and account, not directory,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;so they do NOT need changing.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;what-if-you-move-a-scoped-folder-later&quot;&gt;What if you move a scoped folder later?&lt;/h2&gt;
&lt;p&gt;You will. The folder that seemed right on day one ends up somewhere else, and the whole mechanism is keyed on an absolute path.&lt;/p&gt;
&lt;p&gt;Nothing warns you. Git does not validate that an &lt;code&gt;includeIf&lt;/code&gt; path exists, and a missing include is not an error, it is just skipped. So the tree silently reverts to your default identity and keeps committing.&lt;/p&gt;
&lt;p&gt;There are &lt;strong&gt;four&lt;/strong&gt; places holding that path, and you have to change all of them:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;rn&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;dev/work&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.gitconfig&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.ssh/config-work&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.local/bin/gh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;~/.gitconfig&lt;/code&gt;, the &lt;code&gt;includeIf&lt;/code&gt; condition &lt;strong&gt;and&lt;/strong&gt; its &lt;code&gt;path =&lt;/code&gt; line. Two edits on one stanza, and it is easy to change the condition and forget the path.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;new-folder&amp;gt;/.gitconfig-work&lt;/code&gt;, which has to physically move with the folder.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.local/bin/gh&lt;/code&gt;, the tree in the account table. Miss this one and git is right while &lt;code&gt;gh&lt;/code&gt; is wrong, which is the most confusing possible state.&lt;/li&gt;
&lt;li&gt;Any comments naming the old path. Cosmetic, but this is the file you will read in a year when something breaks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note what is NOT on that list: &lt;code&gt;~/.ssh/config-work&lt;/code&gt; and the keys. Those are keyed on host and account, not on a directory, so they survive a move untouched.&lt;/p&gt;
&lt;p&gt;Then verify with a real repo in the new location, and confirm both layers agree:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;mkdir&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/dev/newplace/tmp-check&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/dev/newplace/tmp-check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; init&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;q&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; commit&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-allow-empty&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; log&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-format=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;%ae sig:%G?&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; work@example.com sig:G&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; api&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; user&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-jq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .login&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; -&amp;gt; work-user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ..&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; rm&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;rf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tmp-check&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both lines have to be right. Checking only the git one is how you end up with correctly-authored commits and a &lt;code&gt;gh pr create&lt;/code&gt; that opens PRs as the wrong person.&lt;/p&gt;
&lt;h2 id=&quot;what-are-the-two-traps&quot;&gt;What are the two traps?&lt;/h2&gt;
&lt;p&gt;Both of these look correct. Both are recommended all over the internet. Both silently do the wrong thing.&lt;/p&gt;
&lt;p&gt;I only found them because I tested the output instead of trusting the config.&lt;/p&gt;
&lt;h3 id=&quot;trap-1-why-don-t-insteadof-rewrites-chain&quot;&gt;Trap 1: why don&#39;t &lt;code&gt;insteadOf&lt;/code&gt; rewrites chain?&lt;/h3&gt;
&lt;p&gt;The classic advice for this problem is a fake SSH host alias plus URL rewriting:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Host github.com-work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  HostName github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentityFile ~/.ssh/id_ed25519_work&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;url &amp;quot;git@github.com-work:&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	insteadOf&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; git@github.com:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That works for &lt;code&gt;git@&lt;/code&gt; remotes. It does not work for &lt;code&gt;https://&lt;/code&gt; remotes.&lt;/p&gt;
&lt;p&gt;And here&#39;s the thing. It fails &lt;em&gt;quietly&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you also have the common global rewrite &lt;code&gt;https://github.com/&lt;/code&gt; to &lt;code&gt;git@github.com:&lt;/code&gt;, then inside your work folder an https remote gets rewritten once, to &lt;code&gt;git@github.com:&lt;/code&gt;, and stops. Git does not run the rewrite a second time to catch the work rule.&lt;/p&gt;
&lt;p&gt;So it goes out on your personal key.&lt;/p&gt;
&lt;p&gt;You&#39;d think adding an &lt;code&gt;https://&lt;/code&gt; rule to the work config would fix it. It doesn&#39;t. Both rules match the same prefix, and on a tie the global one wins:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;inside the work folder:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  https://github.com/Org/repo.git  -&amp;gt;  git@github.com:Org/repo.git   (personal key)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Piling more rules into the work file won&#39;t save you either, because you can&#39;t make your rule &lt;em&gt;more specific&lt;/em&gt;. The owner varies.&lt;/p&gt;
&lt;h3 id=&quot;trap-2-why-doesn-t-ssh-i-override-ssh-config&quot;&gt;Trap 2: why doesn&#39;t &lt;code&gt;ssh -i&lt;/code&gt; override &lt;code&gt;ssh_config&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;So your next idea is to skip URL rewriting entirely and just force the key:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	sshCommand&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ssh -i ~/.ssh/id_ed25519_work -o &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;IdentitiesOnly&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks bulletproof, right?&lt;/p&gt;
&lt;p&gt;It is not. &lt;code&gt;-i&lt;/code&gt; and &lt;code&gt;IdentityFile&lt;/code&gt; from &lt;code&gt;ssh_config&lt;/code&gt; are &lt;strong&gt;additive&lt;/strong&gt;, not overriding, and the config entry gets offered first:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ssh -i ~/.ssh/id_ed25519_work -o IdentitiesOnly=yes -T git@github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Hi personal-user!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read that again. You explicitly passed the work key, with &lt;code&gt;IdentitiesOnly&lt;/code&gt;, and you got the personal account.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-F&lt;/code&gt; is the ONLY flag that&#39;s actually authoritative, because it replaces the config file rather than adding to it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ssh -F ~/.ssh/config-work -T git@github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Hi work-user!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s why the setup above uses a whole second SSH config file instead of a one-line flag. It&#39;s slightly more machinery.&lt;/p&gt;
&lt;p&gt;It&#39;s also the difference between working and appearing to work.&lt;/p&gt;
&lt;h2 id=&quot;what-is-that-token-doing-in-git-credentials&quot;&gt;What is that token doing in &lt;code&gt;.git-credentials&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;Go look at this file:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.git-credentials&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you find a GitHub token sitting in there, it probably arrived by accident.&lt;/p&gt;
&lt;p&gt;Git&#39;s credential protocol runs every configured helper. When one of them answers successfully, git then calls &lt;code&gt;store&lt;/code&gt; on the rest of the chain. So if you have a bare &lt;code&gt;[credential] helper = store&lt;/code&gt; sitting alongside the &lt;code&gt;gh&lt;/code&gt; credential helper, &lt;code&gt;store&lt;/code&gt; will faithfully copy &lt;code&gt;gh&lt;/code&gt;&#39;s OAuth token out of your keyring and write it into a plaintext file in your home directory.&lt;/p&gt;
&lt;p&gt;Nobody chose that. It just happens.&lt;/p&gt;
&lt;p&gt;Why care, in a multi-account setup? Two reasons. It&#39;s a plaintext credential, often world readable. And it&#39;s ONE account&#39;s token being handed out for whatever host matches, which is precisely the cross-contamination you&#39;re trying to prevent.&lt;/p&gt;
&lt;p&gt;If you follow the setup above, all your GitHub traffic goes over SSH and that file is never consulted for github.com. Still worth cleaning up.&lt;/p&gt;
&lt;p&gt;Find out whether a bare &lt;code&gt;store&lt;/code&gt; is in the chain:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-global&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-get-regexp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;^credential&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A line reading &lt;code&gt;credential.helper store&lt;/code&gt;, with no host in the key, is the problem one. It applies to every host. Drop it, and re-add &lt;code&gt;store&lt;/code&gt; scoped to only the host that genuinely needs it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-global&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-unset-all&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; credential.helper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-global&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-add&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; credential.https://gitlab.com.helper&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; store&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then delete any stale github.com line out of the file itself, and lock it down:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sed&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/github\.com/d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.git-credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 600&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.git-credentials&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What you want to end up with is host-scoped helpers only:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;credential.https://github.com.helper  !/usr/bin/gh auth git-credential&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;credential.https://gitlab.com.helper  store&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;so-what-do-you-actually-get&quot;&gt;So what do you actually get?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone&lt;/code&gt; a work repo into the work folder, and it just works. Right identity, right key, verified signature.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gh pr create&lt;/code&gt; in a work repo opens the PR as your work self.&lt;/li&gt;
&lt;li&gt;Your personal repos are completely untouched by any of it.&lt;/li&gt;
&lt;li&gt;Nothing to switch, so nothing to forget to switch back.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The whole thing is five files and about sixty lines.&lt;/p&gt;
&lt;p&gt;In short: the config was the easy part. Most of the work was finding out that the two most-recommended approaches don&#39;t actually work.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://git-scm.com/docs/git-config#_conditional_includes - the &lt;code&gt;includeIf&lt;/code&gt; docs, including &lt;code&gt;gitdir&lt;/code&gt; matching rules&lt;/li&gt;
&lt;li&gt;https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf - &lt;code&gt;insteadOf&lt;/code&gt;, and the &quot;longest match wins&quot; rule that causes trap 1&lt;/li&gt;
&lt;li&gt;https://man.openbsd.org/ssh_config - &lt;code&gt;IdentityFile&lt;/code&gt; and &lt;code&gt;IdentitiesOnly&lt;/code&gt;, worth reading the exact wording on additive behaviour&lt;/li&gt;
&lt;li&gt;https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification - why Authentication and Signing keys are different things&lt;/li&gt;
&lt;li&gt;https://cli.github.com/manual/gh_auth_switch - the official multi-account story for &lt;code&gt;gh&lt;/code&gt;, if you&#39;d rather switch than wrap&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Set it up once and stop thinking about it. Ganbatte!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>I Tried to Make AI Output World-Class. Here&#39;s What Actually Happened.</title>
        <published>2026-04-09T00:00:00+00:00</published>
        <updated>2026-04-09T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/ai-output-world-class-experiment/"/>
        <id>https://www.webdevluis.com/blog/ai-output-world-class-experiment/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/ai-output-world-class-experiment/">&lt;p&gt;&lt;em&gt;A multi-day experiment in context engineering, multi-agent pipelines, and the gap between &quot;good enough&quot; and genuinely excellent AI output.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I spent ~$5 and 500+ LLM calls trying to systematically push AI output from good to world-class across three domains. The most humbling finding: my hand-built debugging knowledge base made the model &lt;em&gt;worse&lt;/em&gt; at debugging. It already knew more than I was teaching it. For landing page copy, no single prompt won everywhere. The trick was routing, picking the right strategy for each visitor type. For UI design, iterative revision improved individual elements but couldn&#39;t crack visual coherence without a design system defined upfront. The thread connecting all three: a weaker model with the right setup beat a stronger model with the wrong one.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;the-question&quot;&gt;The Question&lt;/h2&gt;
&lt;p&gt;It started with a landing page CTA.&lt;/p&gt;
&lt;p&gt;I was building &lt;a href=&quot;https://hellosafelyfed.appforgelabs.com/menu-decoder&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Menu Decoder&lt;span class=&quot;sr-only&quot;&gt; (link opens in a new window)&lt;/span&gt;&lt;/a&gt;, a travel app that helps people with dietary restrictions navigate foreign menus. I asked Claude to write the copy. It produced:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ready to get started?&lt;/strong&gt;
Try the app and see what it can do for you.
&lt;em&gt;Try Menu Decoder&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-generic-cta.webp&quot; alt=&quot;The generic CTA Claude generated. Could be for any product.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is technically fine. It is also completely useless. It could be the CTA for a weather app, a recipe tool, a budgeting spreadsheet. It has no relationship to the specific fear a person with a peanut allergy feels when staring at a Thai menu they can&#39;t read.&lt;/p&gt;
&lt;p&gt;I asked Claude to try again, this time focused on what the app actually solves. It produced:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Scan any menu. Know what&#39;s safe.&lt;/strong&gt;
Point your camera at a foreign menu and get instant allergen alerts in your language.
&lt;em&gt;Scan a Menu Now&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-specific-cta.webp&quot; alt=&quot;Same model, better direction. The copy now speaks to the actual fear.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Better. Obviously better. But I hadn&#39;t changed the model or the context. I had just given it better direction. Which raised the question that consumed the next several days:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is there a systematic process for making AI produce genuinely excellent output? Or does it always require a human expert to point it in the right direction?&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;the-hypothesis&quot;&gt;The Hypothesis&lt;/h2&gt;
&lt;p&gt;My initial assumption was straightforward: LLMs are generalists. They know a lot but not deeply. The path to world-class output should look something like how humans become world-class experts. Acquire deep domain knowledge, develop judgment, apply it to specific problems.&lt;/p&gt;
&lt;p&gt;So I set out to build what I called a &quot;latent space activator,&quot; a framework that would, through careful context engineering, transform a generalist LLM into a domain expert capable of producing top 1% output.&lt;/p&gt;
&lt;p&gt;I started with code debugging, then landing page copy, then UI design. The experiment ran for several days, 500+ LLM calls, and cost roughly $15 in API fees.&lt;/p&gt;
&lt;p&gt;The results were not what I expected.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;experiment-1-code-debugging&quot;&gt;Experiment 1: Code Debugging&lt;/h2&gt;
&lt;h3 id=&quot;why-this-domain&quot;&gt;Why This Domain&lt;/h3&gt;
&lt;p&gt;Code debugging has a property that makes it ideal for experimentation: ground truth exists and is unambiguous. Code either runs or it doesn&#39;t. A bug is either fixed or it isn&#39;t. You don&#39;t need human judges or conversion metrics. You need a terminal.&lt;/p&gt;
&lt;p&gt;I built a three-layer pipeline:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Knowledge Base:&lt;/strong&gt; four markdown files encoding debugging expertise from first principles. What a bug actually is (a divergence between mental model and system reality), eight root cause classes with correct and incorrect fixes, anti-patterns, decision trees.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Latent Space Activator:&lt;/strong&gt; a system prompt that encoded not just knowledge but reasoning process. Classify before diagnosing, form falsifiable hypotheses, reason from invariants not symptoms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multi-Agent Pipeline:&lt;/strong&gt; Generator produces a diagnosis, Critic adversarially challenges it, Arbitrator resolves the dispute. The theory was that adversarial review would improve output quality.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I tested this against eight code problems (quadratic complexity bugs, mutable default arguments, race conditions, closure capture issues) across four models and four configurations, 356 runs total.&lt;/p&gt;
&lt;h3 id=&quot;what-the-data-said&quot;&gt;What the Data Said&lt;/h3&gt;
&lt;p&gt;The first surprise: the knowledge base made things worse.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Config&lt;/th&gt;&lt;th&gt;Expert Rate&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Bare model (no KB, no agents)&lt;/td&gt;&lt;td&gt;92%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;KB only&lt;/td&gt;&lt;td&gt;85%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Full pipeline (KB + agents)&lt;/td&gt;&lt;td&gt;93%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The bare model outperformed the KB-only configuration. Adding carefully crafted expert knowledge about debugging made the model worse at debugging than it was without any help.&lt;/p&gt;
&lt;p&gt;The explanation, once visible, was obvious: the model already had this knowledge. It had been trained on every debugging tutorial, every Stack Overflow answer, every textbook on algorithms. The KB didn&#39;t add knowledge. It competed with knowledge the model already held, in a slightly different vocabulary, producing interference.&lt;/p&gt;
&lt;p&gt;The second surprise: all models, all configurations, all eight problems produced a 100% code execution pass rate. The problems were solved correctly every time regardless of architecture. The domain was simply within the native capability of current models.&lt;/p&gt;
&lt;h3 id=&quot;the-real-finding&quot;&gt;The Real Finding&lt;/h3&gt;
&lt;p&gt;The original hypothesis, that architecture could substitute for model quality, couldn&#39;t be tested here because there was no gap to close. Current frontier models solve production-grade debugging problems reliably without any help.&lt;/p&gt;
&lt;p&gt;The finding that mattered: &lt;strong&gt;KB scaffolding is net-negative when the model already has the expertise.&lt;/strong&gt; You can&#39;t improve on what&#39;s already there by telling it how to think. You can only interfere.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;experiment-2-landing-page-copy&quot;&gt;Experiment 2: Landing Page Copy&lt;/h2&gt;
&lt;h3 id=&quot;a-different-kind-of-problem&quot;&gt;A Different Kind of Problem&lt;/h3&gt;
&lt;p&gt;Landing page copy has a property that makes it structurally different from code: the ground truth is slow (conversion rates take weeks to measure), subjective (what&#39;s world-class for B2B SaaS isn&#39;t world-class for consumer fintech), and contested (experts disagree).&lt;/p&gt;
&lt;p&gt;Instead of A/B testing, I built a judge. An LLM scoring output on five dimensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Visitor emotional accuracy:&lt;/strong&gt; does the copy name the actual fear the visitor arrives with?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specificity:&lt;/strong&gt; is this copy specific to this product, or could it be any competitor&#39;s?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mechanism clarity:&lt;/strong&gt; does the visitor understand HOW the product solves the problem?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CTA-awareness match:&lt;/strong&gt; does the call to action ask for the right level of commitment?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interchangeability:&lt;/strong&gt; could this copy be used by a competitor unchanged?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Six products, four configurations, 30+ runs. Products included Moz, Crazy Egg, GoHenry, Smart Insights, Sunshine.co.uk, and Course Hero, all drawn from documented conversion case studies with real lift numbers, not taste-based selection.&lt;/p&gt;
&lt;h3 id=&quot;the-routing-discovery&quot;&gt;The Routing Discovery&lt;/h3&gt;
&lt;p&gt;No single configuration won across all products. But a clear pattern emerged when I sorted by visitor awareness level:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cold traffic&lt;/strong&gt; (visitor doesn&#39;t know the product exists): question_first won by a significant margin. GoHenry, a fintech product for children marketed to anxious parents via social media, scored 7.4 with question_first vs 6.2 bare. Forcing the model to articulate the visitor&#39;s emotional state before writing produced copy that named the specific tension parents feel: wanting their children to develop real financial skills while being terrified of real financial exposure.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&quot;Give Your Child Real-World Money Skills, Without Real-World Risks.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That headline emerged from question_first. Bare generated: &quot;Introducing GoHenry: The Smart Way to Teach Kids About Money.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hot traffic&lt;/strong&gt; (visitor ready to buy): bare won. Sunshine.co.uk, a budget travel site where the conversion barrier was skepticism about prices that seemed too good to be true, scored 7.4 bare and 6.0 with question_first. The visitor already knows what they want. Emotional probing adds noise. Specifics close.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Products with rational barriers&lt;/strong&gt; (value vs free alternatives): mechanism_first was the critical intervention. Smart Insights, a digital marketing education platform competing against free content, went from 5.4 GENERIC with question_first to 7.2 COMPETENT with mechanism_first. The key sentence that appeared: &lt;em&gt;&quot;Get the depth of a marketing consultant at a fraction of the cost.&quot;&lt;/em&gt; That&#39;s the mechanism. It reframes the product from &quot;another education platform&quot; to &quot;consultant alternative.&quot;&lt;/p&gt;
&lt;h3 id=&quot;the-ceiling&quot;&gt;The Ceiling&lt;/h3&gt;
&lt;p&gt;Nothing broke 8.0. The best generated copy scored 8.4, and that was on my actual product, Menu Decoder, using the routed pipeline with Sonnet + mechanism_first.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&quot;AI that reads what&#39;s not on the menu&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Snap a photo of any foreign-language menu and get instant safety alerts, even for hidden ingredients never listed on the page.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The judge gave this 9/10 for visitor emotional accuracy, 9/10 for specificity, 9/10 for interchangeability. The one weak score: 7/10 for CTA-awareness match, because the copy asks cold traffic to buy immediately without a lower-commitment first step.&lt;/p&gt;
&lt;p&gt;The judge was right. The copy earned the click. The product flow has to earn the conversion. Copy can&#39;t fix a paywall that asks too much.&lt;/p&gt;
&lt;h3 id=&quot;the-key-insight&quot;&gt;The Key Insight&lt;/h3&gt;
&lt;p&gt;The most important finding from the copy experiment wasn&#39;t about prompts. It was about the relationship between model quality and architecture value:&lt;/p&gt;
&lt;p&gt;Running the same pipeline on Sonnet vs Gemini Flash showed that &lt;strong&gt;the interaction between model and configuration matters more than either alone.&lt;/strong&gt; Sonnet with kb_scaffold produced WORLD_CLASS copy for Course Hero. Sonnet with bare produced worse copy than Gemini Flash with bare on Sunshine. A better model with the wrong configuration underperforms a weaker model with the right one.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;experiment-3-ui-design&quot;&gt;Experiment 3: UI Design&lt;/h2&gt;
&lt;h3 id=&quot;the-setup&quot;&gt;The Setup&lt;/h3&gt;
&lt;p&gt;Default Claude Code UI output is competent and consistent. It is also visually identical across every project. Tailwind cards with shadow-md, blue-500 accent color, rounded-xl on everything. You can tell it&#39;s AI-generated not because it&#39;s bad but because it has no identity.&lt;/p&gt;
&lt;p&gt;I wanted to know: can context engineering produce genuinely distinctive UI design? Or is the generic aesthetic a hard ceiling?&lt;/p&gt;
&lt;p&gt;I built a pipeline that rendered generated Astro components in a browser using a Vite dev server, took Playwright screenshots, and evaluated them with a multimodal judge receiving the actual rendered screenshot.&lt;/p&gt;
&lt;p&gt;The test case: Menu Decoder&#39;s main scan screen. Camera viewfinder, dish list with safety badges, FAB.&lt;/p&gt;
&lt;h3 id=&quot;test-1-what-activates-better-design&quot;&gt;Test 1: What Activates Better Design&lt;/h3&gt;
&lt;p&gt;Four conditions, same functional spec, one variable changed per condition:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bare:&lt;/strong&gt; White background, rounded cards, blue FAB, small pill badges. Classic default Tailwind. Shadow count: 2. Blue/indigo count: 2.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;product_context:&lt;/strong&gt; Describe the user&#39;s emotional state. Anxious traveler, dietary restriction, foreign restaurant. Visual output: identical to bare. Zero change. Describing the user does not activate visual capability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;reference_style:&lt;/strong&gt; Specify the aesthetic target. Dark background, no shadows, no blue, Linear/Vercel aesthetic. Shadow count: 0. Blue/indigo count: 0. Dark background, translucent badge fills, sharp corners. A completely different design language activated by three sentences of style direction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;full_brief:&lt;/strong&gt; Product context + style direction + explicit anti-patterns. The standout difference from reference_style: solid full-height color blocks for safety badges (bright green, amber, red) filling the right side of each dish row. The design comment at the top of the generated file read: &lt;em&gt;&quot;The safety badge must be the dominant visual element in each row.&quot;&lt;/em&gt; It was executed.&lt;/p&gt;
&lt;div class=&quot;gallery&quot;&gt;
  &lt;figure&gt;
    &lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-ui-bare-default.webp&quot; alt=&quot;bare: default Tailwind output&quot; loading=&quot;lazy&quot;&gt;
    &lt;figcaption&gt;bare&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-ui-reference-style.webp&quot; alt=&quot;reference_style: dark, flat, no shadows&quot; loading=&quot;lazy&quot;&gt;
    &lt;figcaption&gt;reference_style&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-ui-full-brief.webp&quot; alt=&quot;full_brief: bold safety color blocks&quot; loading=&quot;lazy&quot;&gt;
    &lt;figcaption&gt;full_brief&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The activating variable is visual style direction, not product understanding. Telling the model what the user feels does nothing. Telling the model what the screen should look like does everything.&lt;/p&gt;
&lt;h3 id=&quot;test-2-the-feedback-loop&quot;&gt;Test 2: The Feedback Loop&lt;/h3&gt;
&lt;p&gt;I built an iterative revision loop: generate, screenshot, judge critique, revise, repeat twice. The judge was given the rendered screenshot and asked to identify the most important thing to fix, with a specific implementation instruction.&lt;/p&gt;
&lt;p&gt;The revision instructions were excellent:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;Move the SCAN MENU button from its current centered bottom position to a fixed position at the bottom of the viewfinder area. The dish list must never be obscured by interactive elements.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;Replace small right-aligned pill badges with full-height color blocks that fill the entire right portion of each dish row. Apply the safety color to a div with &lt;code&gt;absolute right-0 top-0 h-full w-20&lt;/code&gt; containing centered white text.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The model executed both instructions. The layout improved measurably. And then something interesting happened: the visual coherence degraded. Locally correct decisions (FAB moved up, badges widened) produced a globally incoherent layout. A card container appeared around the dish list for no clear reason. The viewfinder got compressed awkwardly. The design started to feel assembled from competing decisions rather than designed from a unified logic.&lt;/p&gt;
&lt;div class=&quot;gallery&quot;&gt;
  &lt;figure&gt;
    &lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-ui-revision-pass0.webp&quot; alt=&quot;Pass 0: initial generation&quot; loading=&quot;lazy&quot;&gt;
    &lt;figcaption&gt;Pass 0: initial&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-ui-revision-pass1.webp&quot; alt=&quot;Pass 1: color blocks added, FAB still overlaps&quot; loading=&quot;lazy&quot;&gt;
    &lt;figcaption&gt;Pass 1: fixes applied&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img src=&quot;/blog/ai-output-world-class-experiment/ai-experiment-ui-revision-pass2.webp&quot; alt=&quot;Pass 2: viewfinder crushed, layout lost coherence&quot; loading=&quot;lazy&quot;&gt;
    &lt;figcaption&gt;Pass 2: coherence lost&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The score went from 5.2 to 6.4 and then stopped moving.&lt;/p&gt;
&lt;h3 id=&quot;the-structural-finding&quot;&gt;The Structural Finding&lt;/h3&gt;
&lt;p&gt;The revision loop worked at the element level and failed at the system level. Which revealed the real problem: &lt;strong&gt;there is no design system.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every world-class product UI is built on a design system. Color tokens named by function, a typographic scale, a spacing unit, a component language. A designer doesn&#39;t design a screen; they define a system and then apply it.&lt;/p&gt;
&lt;p&gt;The model is being asked to simultaneously define a design system and build a screen. Under that cognitive load, it defaults to its training distribution: Tailwind defaults. The revision loop makes individual decisions more correct without ever establishing the system that would make them coherent.&lt;/p&gt;
&lt;p&gt;The correct pipeline architecture, not yet built but clearly indicated by the evidence, is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1 (once per brand):&lt;/strong&gt; Given a product brief, generate a design system specification. Color tokens. Type scale. Spacing unit. Component language. One sentence describing the visual identity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2 (per screen):&lt;/strong&gt; Given the design system spec + functional spec, build the screen. Every decision references the spec.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3:&lt;/strong&gt; One targeted revision pass. Screenshot, specific instruction, revise once.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;what-three-experiments-actually-proved&quot;&gt;What Three Experiments Actually Proved&lt;/h2&gt;
&lt;h3 id=&quot;finding-1-the-assumption-was-wrong&quot;&gt;Finding 1: The assumption was wrong&lt;/h3&gt;
&lt;p&gt;The original hypothesis, that LLMs need to be taught domain expertise through context engineering, is wrong for well-covered domains. Current models already have world-class expertise in code debugging encoded in their weights. The architecture added nothing and sometimes subtracted.&lt;/p&gt;
&lt;p&gt;The corrected hypothesis: &lt;strong&gt;context engineering adds value where the model has genuine capability gaps, not where it has latent expertise being suppressed.&lt;/strong&gt; The KB helped weaker models on specific problem types. It hurt stronger models on problems they already understood.&lt;/p&gt;
&lt;h3 id=&quot;finding-2-the-expert-s-attempt-to-help-can-make-things-worse&quot;&gt;Finding 2: The expert&#39;s attempt to help can make things worse&lt;/h3&gt;
&lt;p&gt;The KB was built by someone reasoning carefully about debugging expertise. It made outputs worse. The model&#39;s native representation of the domain was better than the expert&#39;s explicit encoding of it.&lt;/p&gt;
&lt;p&gt;This has an uncomfortable implication: a domain expert trying to engineer the model&#39;s context might systematically underperform a non-expert who just asks the model directly. The expert&#39;s mental model competes with the model&#39;s better one.&lt;/p&gt;
&lt;h3 id=&quot;finding-3-routing-beats-universal-optimization&quot;&gt;Finding 3: Routing beats universal optimization&lt;/h3&gt;
&lt;p&gt;There is no single best configuration across domains. question_first wins for cold traffic. bare wins for hot traffic. mechanism_first wins for rational barriers. few_shot wins for warm traffic with pattern-matchable products.&lt;/p&gt;
&lt;p&gt;The pipeline that produces consistent excellence isn&#39;t a better universal prompt. It&#39;s a routing layer that selects the right intervention for the specific problem type. Classification before generation.&lt;/p&gt;
&lt;h3 id=&quot;finding-4-architecture-value-is-inversely-proportional-to-model-capability&quot;&gt;Finding 4: Architecture value is inversely proportional to model capability&lt;/h3&gt;
&lt;p&gt;This appeared in both the code and copy experiments. For the cheapest model, the architecture added 6+ percentage points of expert rate. For the frontier model, it added nothing or hurt.&lt;/p&gt;
&lt;p&gt;The window in which &quot;use a better prompt&quot; beats &quot;use a better model&quot; is shrinking as inference costs drop. This doesn&#39;t mean prompting is useless. It means the ROI calculation changes, and the engineering cost of maintaining a complex pipeline should be weighed against the cost of upgrading the model.&lt;/p&gt;
&lt;h3 id=&quot;finding-5-the-generation-revision-loop-is-necessary-but-not-sufficient-for-ui&quot;&gt;Finding 5: The generation/revision loop is necessary but not sufficient for UI&lt;/h3&gt;
&lt;p&gt;Copy can be world-class in a single generation pass with the right activation. UI design cannot, because world-class UI requires a coherent system applied consistently, and single-pass generation under prompt pressure defaults to assembling from framework defaults rather than designing from first principles.&lt;/p&gt;
&lt;p&gt;The iterative revision loop improves individual elements. It doesn&#39;t establish the system. A design system definition phase, before any screen is generated, is the missing step.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;what-i-m-building-next&quot;&gt;What I&#39;m Building Next&lt;/h2&gt;
&lt;p&gt;The routing pipeline for landing copy is in production. Menu Decoder&#39;s landing page uses the output. The headline, &lt;em&gt;&quot;AI that reads what&#39;s not on the menu,&quot;&lt;/em&gt; came from this experiment.&lt;/p&gt;
&lt;p&gt;The UI design pipeline needs the design system layer before it&#39;s useful. That&#39;s the next experiment, and it&#39;ll be driven by an actual deliverable: Menu Decoder needs a full UI, not just a test screen.&lt;/p&gt;
&lt;p&gt;The code debugging findings have a different application: they suggest that for domains where models already have strong priors, the most valuable architectural contribution isn&#39;t a KB or a multi-agent pipeline. It&#39;s a &lt;strong&gt;selective reliability mechanism&lt;/strong&gt;. A fast, cheap check that identifies the small percentage of generations that are genuinely uncertain, and applies review only there. Most generations don&#39;t need it. Some do. Knowing which is which is the real engineering challenge.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The total cost to run all experiments described in this post: $15 in API fees and 8 hours of compute time across several days of iteration.&lt;/p&gt;
&lt;p&gt;The most expensive insight: sometimes the best thing you can do is get out of the way.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Built with Claude Code, OpenRouter, Bun, and Astro.&lt;/em&gt;
&lt;em&gt;Experiments run on Gemini Flash 2.0, DeepSeek R1, Qwen3 Coder, and Claude Sonnet 4.5.&lt;/em&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Optimal Tech Stack, Architecture, and DevOps Setup for Building with Claude Code</title>
        <published>2026-04-01T00:00:00+00:00</published>
        <updated>2026-04-01T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/optimal-stack-for-claude-code/"/>
        <id>https://www.webdevluis.com/blog/optimal-stack-for-claude-code/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/optimal-stack-for-claude-code/">&lt;p&gt;&lt;em&gt;A first-principles analysis of how to structure your codebase, not for humans, but for an AI agent.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Most advice about AI coding tools tells you the same thing: use the most popular framework. More Stack Overflow answers, better AI output.&lt;/p&gt;
&lt;p&gt;That intuition is roughly right. It is also incomplete.&lt;/p&gt;
&lt;p&gt;When you are building with Claude Code specifically, the agent is not just generating code from memory. It is navigating your codebase, reading files, running tools, and working inside a fixed context window.&lt;/p&gt;
&lt;p&gt;Your codebase structure affects the quality of every edit it makes.&lt;/p&gt;
&lt;p&gt;So this post is not about best practices. It is about optimizing for how Claude Code actually works, from first principles and from what the leaked Claude Code source confirmed about its internal mechanics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tl,dr:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code navigates your codebase via grep and surgical string replacement. It does not read everything.&lt;/li&gt;
&lt;li&gt;Fewer files means less token overhead, which means more context left for actual logic.&lt;/li&gt;
&lt;li&gt;TypeScript strict mode. Astro + Vue + Tailwind on the frontend. Hono + Bun + Drizzle on the backend. One file per feature slice. systemd + Caddy on a plain VPS.&lt;/li&gt;
&lt;li&gt;It all falls out of two principles: full context in one load, and types as prediction constraints.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;how-does-claude-code-actually-work&quot;&gt;How does Claude Code actually work?&lt;/h2&gt;
&lt;p&gt;Before you decide anything about architecture, you need to know what Claude Code is doing under the hood.&lt;/p&gt;
&lt;p&gt;It is not a chatbot that reads your whole codebase.&lt;/p&gt;
&lt;p&gt;It is an agent with a set of tools: file read, bash, grep, string replacement, LSP. It uses them to navigate your project and make surgical edits. Every tool call costs tokens. Every file it reads takes up space in a context window.&lt;/p&gt;
&lt;p&gt;And the part that matters most: the quality of its output DEGRADES as your context window fills up.&lt;/p&gt;
&lt;p&gt;The leaked source confirmed a few specific mechanics. Each one should change how you structure your code.&lt;/p&gt;
&lt;p&gt;It uses str_replace, not full rewrites. The primary edit tool finds a unique string in your file and replaces it.&lt;/p&gt;
&lt;p&gt;Which means it fails on ambiguous patterns. If the same code block appears twice in a file, the edit becomes unpredictable. File size matters less than pattern uniqueness within a file.&lt;/p&gt;
&lt;p&gt;It greps, it does not read. Rather than loading every one of your files in full, Claude Code uses a dedicated Grep tool to locate the relevant code before reading it. A well-structured, clearly named codebase is easier to grep than a clever, abstract one.&lt;/p&gt;
&lt;p&gt;It has a three-layer memory system. A lightweight MEMORY.md index of pointers, on-demand topic files, and grepped identifiers from transcripts. Your raw files are never fully re-read into context, they get fetched on demand.&lt;/p&gt;
&lt;p&gt;So co-located context beats scattered context. Every time.&lt;/p&gt;
&lt;p&gt;The context window is around 180k usable tokens. The system prompt, tool definitions and memory files eat roughly 20k of that before you write a single line of code. At approximately 4 characters per token, your practical working budget for code is around 140,000 to 160,000 characters per session.&lt;/p&gt;
&lt;p&gt;That is the whole budget.&lt;/p&gt;
&lt;p&gt;Autocompaction triggers at a ~13k token buffer. When your context fills, Claude Code compresses conversation history and re-injects recently accessed files at 5,000 tokens each. Files it has not touched recently are dropped.&lt;/p&gt;
&lt;p&gt;So co-locating your files is a performance concern. Not an aesthetic one.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;first-principle-why-does-information-density-matter&quot;&gt;First principle: why does information density matter?&lt;/h2&gt;
&lt;p&gt;The single most important optimization you can make is maximizing the ratio of relevant logic to total tokens consumed.&lt;/p&gt;
&lt;p&gt;Every import statement, config file header, re-exported type and boilerplate comment is a token that contributes no logic. In a multi-file architecture you pay that cost again and again.&lt;/p&gt;
&lt;p&gt;In a single file you pay it once.&lt;/p&gt;
&lt;p&gt;Which is why the conventional &quot;one file per component&quot; rule hurts you here. That rule exists entirely for human navigation.&lt;/p&gt;
&lt;p&gt;Claude Code does not navigate a directory tree the way you do. It pays a tool call cost to open each file.&lt;/p&gt;
&lt;p&gt;Fewer files, fewer tool calls, more of your context window occupied by actual logic.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;so-what-is-the-stack&quot;&gt;So what is the stack?&lt;/h2&gt;
&lt;h3 id=&quot;typescript-why-strict-mode-no-exceptions&quot;&gt;TypeScript: why strict mode, no exceptions?&lt;/h3&gt;
&lt;p&gt;Strict mode is not just good practice here. It mechanically improves Claude&#39;s output quality.&lt;/p&gt;
&lt;p&gt;Why? Because when Claude generates the next token in a sequence, your types constrain the probability space of what it can generate. A typed interface makes hallucinating a wrong method name significantly less likely, because the correct token has higher probability given the surrounding context.&lt;/p&gt;
&lt;p&gt;More types, better output. Not because of convention. That is just how prediction works.&lt;/p&gt;
&lt;p&gt;Practical rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;strict: true&lt;/code&gt; in tsconfig&lt;/li&gt;
&lt;li&gt;Explicit return types on all functions&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;any&lt;/code&gt;, ever&lt;/li&gt;
&lt;li&gt;Zod schemas for all runtime validation, co-located with the types they validate&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;frontend-why-astro-vue-tailwind&quot;&gt;Frontend: why Astro + Vue + Tailwind?&lt;/h3&gt;
&lt;p&gt;Astro handles your static/dynamic split cleanly. Static marketing pages are generated at build time. Interactive app components mount as Vue islands with &lt;code&gt;client:only=&quot;vue&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One project, one build command, one output folder. Claude never has to reason about two separate frontend deployments.&lt;/p&gt;
&lt;p&gt;Vue SFCs are naturally Claude-friendly, because each of your &lt;code&gt;.vue&lt;/code&gt; files is already a cohesion unit. Template, script and style are co-located.&lt;/p&gt;
&lt;p&gt;That matches how Claude Code loads context. One file load gives Claude everything it needs to understand and edit that component.&lt;/p&gt;
&lt;p&gt;Tailwind is mechanically optimal for AI generation. Every class is atomic, explicit, and has appeared millions of times in training data.&lt;/p&gt;
&lt;p&gt;Claude can predict &lt;code&gt;flex items-center gap-4&lt;/code&gt; with near certainty. It cannot do the same for your custom CSS class names. Eliminating CSS files removes an entire category of cross-file context lookups.&lt;/p&gt;
&lt;p&gt;Never write a CSS file. Not even one.&lt;/p&gt;
&lt;h3 id=&quot;backend-why-hono-bun-drizzle-postgresql&quot;&gt;Backend: why Hono + Bun + Drizzle + PostgreSQL?&lt;/h3&gt;
&lt;p&gt;Hono is TypeScript-first in a way that Express is not. Types flow through your request/response cycle explicitly:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Express — Claude infers types at runtime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/api/users&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; res&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Response&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Hono — types are explicit at the route definition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/api/users&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zValidator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; createUserSchema&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; body&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;valid&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; fully typed, no inference needed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is not a style preference.&lt;/p&gt;
&lt;p&gt;Explicit typing reduces the probability of Claude generating incorrect property accesses, wrong response shapes, and missed validation checks.&lt;/p&gt;
&lt;p&gt;Bun as the runtime gives you native TypeScript execution (no build step), faster startup, and built-in APIs. It is stable for long-running HTTP servers.&lt;/p&gt;
&lt;p&gt;Now you will say: didn&#39;t the leak report Bun memory leaks? It did. Those reports were specific to Bun running as a long-running CLI tool managing concurrent streams and subagents. A very different workload from an HTTP server.&lt;/p&gt;
&lt;p&gt;Drizzle with a single &lt;code&gt;schema.ts&lt;/code&gt; file is the most important database decision you can make for Claude Code.&lt;/p&gt;
&lt;p&gt;When Claude reads one file and understands your entire data model, every table, every column, every relationship, it can generate correct queries, migrations and service logic without any additional context.&lt;/p&gt;
&lt;p&gt;Scattered migrations, multiple schema files, runtime-inferred schemas: all of them require Claude to do additional work before it can reason about your data.&lt;/p&gt;
&lt;p&gt;PostgreSQL, because it is the most thoroughly represented database in training data. Claude&#39;s Postgres knowledge is deep and reliable.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;how-should-you-lay-out-the-codebase&quot;&gt;How should you lay out the codebase?&lt;/h2&gt;
&lt;h3 id=&quot;which-phase-are-you-in&quot;&gt;Which phase are you in?&lt;/h3&gt;
&lt;p&gt;The optimal architecture depends on which phase of development you are in. These are genuinely different, and applying the wrong one is costly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: MVP / one-shot generation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you are building a new app from a spec, optimize for generation, not navigation. You want working code as fast as possible.&lt;/p&gt;
&lt;p&gt;The optimal structure is one file. Or as few files as you can manage, grouped internally by feature with explicit section markers.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ============================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; AUTH&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ============================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ============================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; PAYMENTS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ============================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ============================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; DASHBOARD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ============================================================&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These markers are not just for human readability. Heading patterns like this appear frequently in training data as organizational markers, and they increase the probability that Claude correctly associates subsequent tokens with the right domain.&lt;/p&gt;
&lt;p&gt;They are a prediction constraint.&lt;/p&gt;
&lt;p&gt;The ceiling for this approach is around the Sonnet 4.6 output token limit of 64,000 tokens, which is enough for most non-trivial apps. For anything larger, use multiple files, but still group by feature rather than by technical layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: iterative development&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once an MVP is validated and you are building features incrementally, switch to Vertical Slice Architecture.&lt;/p&gt;
&lt;p&gt;The critical distinction from conventional VSA is that each slice is ONE FILE, not one directory. Everything your feature needs lives in a single file. Route, service logic, Zod validation, types.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;src/features/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  auth.ts          ← Hono route + service logic + Zod schemas + types&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  payments.ts      ← everything payments in one file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  dashboard.ts     ← everything dashboard in one file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your Vue components follow the same principle, but naturally. Each &lt;code&gt;.vue&lt;/code&gt; SFC is already a single-file cohesion unit containing template, script and style together.&lt;/p&gt;
&lt;p&gt;Why does one file per slice beat one directory per slice? The same reason it beats layered architecture: one task touches one feature, one feature loads one file.&lt;/p&gt;
&lt;p&gt;When Claude edits your auth logic, it reads &lt;code&gt;auth.ts&lt;/code&gt; and has everything. The route definition, the validation schema, the service function, the types. A single context load.&lt;/p&gt;
&lt;p&gt;With a directory-per-slice approach, Claude must open &lt;code&gt;route.ts&lt;/code&gt;, grep for the service in &lt;code&gt;service.ts&lt;/code&gt;, check the schema in &lt;code&gt;schema.ts&lt;/code&gt;. Three tool calls before it has made a single edit.&lt;/p&gt;
&lt;p&gt;The key constraint is pattern uniqueness, not file length. The str_replace tool fails when the same code pattern appears twice in the same file, not when files are long.&lt;/p&gt;
&lt;p&gt;A 500-line file with clean, explicitly named, non-repeating code is better for Claude Code than five 100-line files with similar structure.&lt;/p&gt;
&lt;p&gt;So split your slice into multiple files only when patterns within it start repeating. Not before.&lt;/p&gt;
&lt;p&gt;Then add a single &lt;code&gt;src/features/AI-CONTEXT.md&lt;/code&gt; describing the conventions for all your slices: what each file is responsible for, naming rules, how cross-slice dependencies are handled. Claude Code re-reads CLAUDE.md on every query iteration, and this file gives it the same benefit scoped to the features layer, without requiring a directory per feature.&lt;/p&gt;
&lt;h3 id=&quot;what-goes-in-the-shared-packages&quot;&gt;What goes in the shared packages?&lt;/h3&gt;
&lt;p&gt;In a monorepo, the shared packages are the highest-leverage investment you can make for Claude Code quality:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;packages/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  db/          # Drizzle schema — single source of truth for all DB types&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  api-types/   # Zod schemas + response types shared between frontend and backend&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  config/      # App constants and product config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When Claude reads &lt;code&gt;packages/api-types/index.ts&lt;/code&gt;, it knows what every API endpoint accepts and returns. When it reads &lt;code&gt;packages/db/schema.ts&lt;/code&gt;, it knows your entire data model.&lt;/p&gt;
&lt;p&gt;Those two files give Claude more accurate context than any amount of inline documentation.&lt;/p&gt;
&lt;p&gt;Never duplicate types between frontend and backend. Any type that exists in two places is a type that will eventually diverge, and a diverged type is a context that MISLEADS Claude.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;what-about-devops&quot;&gt;What about DevOps?&lt;/h2&gt;
&lt;h3 id=&quot;does-the-same-principle-apply-to-infrastructure&quot;&gt;Does the same principle apply to infrastructure?&lt;/h3&gt;
&lt;p&gt;It does. Claude Code manages your server through bash commands and file edits, so the optimal infrastructure is one where the entire running state of your system can be understood by reading a small number of declarative files.&lt;/p&gt;
&lt;h3 id=&quot;process-management-why-systemd-over-pm2&quot;&gt;Process management: why systemd over PM2?&lt;/h3&gt;
&lt;p&gt;PM2 stores your running state in memory. To understand what is running, Claude must execute &lt;code&gt;pm2 list&lt;/code&gt; and parse table output. To understand how a service is configured, it must read a separate ecosystem config file.&lt;/p&gt;
&lt;p&gt;The source of truth is split between runtime state and config files.&lt;/p&gt;
&lt;p&gt;systemd is declarative. Every one of your services is a &lt;code&gt;.service&lt;/code&gt; file, and the file IS the source of truth:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Unit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Description&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;myapp backend&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;After&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;network.target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;simple&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;User&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;deploy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WorkingDirectory&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;/home/deploy/myapp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;EnvironmentFile&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;/home/deploy/myapp/.env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ExecStart&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;/usr/bin/bun run src/index.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Restart&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Install&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WantedBy&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;multi-user.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude can read this file once and know exactly how your process runs. It can edit it directly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;systemctl status myapp&lt;/code&gt; gives structured, readable output. &lt;code&gt;systemctl list-units --type=service&lt;/code&gt; gives Claude a complete picture of everything running on the server.&lt;/p&gt;
&lt;p&gt;That is the declarative infrastructure principle applied to process management.&lt;/p&gt;
&lt;h3 id=&quot;reverse-proxy-why-caddy&quot;&gt;Reverse proxy: why Caddy?&lt;/h3&gt;
&lt;p&gt;Caddy over Nginx for one reason: your Caddyfile is readable in a single context load.&lt;/p&gt;
&lt;p&gt;Your Nginx configuration sprawls across &lt;code&gt;sites-available/&lt;/code&gt;, &lt;code&gt;sites-enabled/&lt;/code&gt;, &lt;code&gt;conf.d/&lt;/code&gt;, and include directives. Caddy&#39;s entire reverse proxy config for multiple projects fits in one file:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;myapp.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  handle /api/* {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reverse_proxy localhost:3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  handle {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    root * /var/www/myapp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    file_server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TLS is automatic. No certbot, no renewal cron jobs, no certificate files to manage.&lt;/p&gt;
&lt;h3 id=&quot;hosting-why-a-vps-over-a-paas&quot;&gt;Hosting: why a VPS over a PaaS?&lt;/h3&gt;
&lt;p&gt;A single Hetzner VPS running 10 of your projects costs roughly the same per month as one project on a managed PaaS.&lt;/p&gt;
&lt;p&gt;More importantly for Claude Code, a VPS is a stable, predictable environment. Claude&#39;s bash tool has a reliable surface to work with. There are no platform-specific CLI tools to install, no API keys for infrastructure management, and no vendor-specific configuration formats to learn.&lt;/p&gt;
&lt;p&gt;The total infrastructure state of your multi-project VPS, from Claude&#39;s perspective, is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/systemd/system/*.service&lt;/code&gt;, all running processes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;, all routing&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/shared-infra/SERVER.md&lt;/code&gt;, conventions and project list&lt;/li&gt;
&lt;li&gt;Individual project &lt;code&gt;.env&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Four categories of files. And Claude understands your entire server.&lt;/p&gt;
&lt;h3 id=&quot;what-is-the-server-md-for&quot;&gt;What is the SERVER.md for?&lt;/h3&gt;
&lt;p&gt;This is the most under-appreciated file in your entire setup.&lt;/p&gt;
&lt;p&gt;Claude Code has no persistent memory of your server across sessions. Without a single reference file, it must infer conventions each time. How projects are named, how services are structured, where env files live, how to deploy a new project.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;~/shared-infra/SERVER.md&lt;/code&gt; solves this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt; Server Overview&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt;##&lt;/span&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt; Projects&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Project&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; Domain&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; Port&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; Systemd Service&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; myapp&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; myapp.com&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; 3000&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; myapp.service&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; otherapp&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; otherapp.com&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; 3001&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; otherapp.service&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt;##&lt;/span&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt; Conventions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;-&lt;/span&gt;&lt;span&gt; Projects live in ~/projects/{name}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;-&lt;/span&gt;&lt;span&gt; Systemd services: /etc/systemd/system/{name}.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;-&lt;/span&gt;&lt;span&gt; Static files: /var/www/{name}/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;-&lt;/span&gt;&lt;span&gt; Env files: ~/projects/{name}/.env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt;##&lt;/span&gt;&lt;span class=&quot;z-l-1 z-l-b z-d-4 z-d-b&quot;&gt; Adding a New Project&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;1.&lt;/span&gt;&lt;span&gt; Clone repo to ~/projects/{name}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;2.&lt;/span&gt;&lt;span&gt; Copy .env.example to .env, fill values&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;3.&lt;/span&gt;&lt;span&gt; Create systemd service file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;4.&lt;/span&gt;&lt;span&gt; Add Caddy block for domain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;5.&lt;/span&gt;&lt;span&gt; systemctl enable --now {name}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude reads this once per session and has your entire server mental model loaded.&lt;/p&gt;
&lt;h3 id=&quot;ci-cd-how-much-do-you-need&quot;&gt;CI/CD: how much do you need?&lt;/h3&gt;
&lt;p&gt;A single &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt; that Claude can read and edit entirely in one context load:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;typecheck → build frontend → SSH into VPS → pull → install → restart service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing more for an MVP.&lt;/p&gt;
&lt;p&gt;The feedback comes back as GitHub Actions log stdout, so Claude can read a failed workflow output and self-correct without any additional context.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;what-is-the-whole-picture&quot;&gt;What is the whole picture?&lt;/h2&gt;
&lt;p&gt;Every decision in this stack follows from the same two principles.&lt;/p&gt;
&lt;p&gt;Principle 1: full context in one load. The best edit is the one where Claude has everything it needs in a single context load, with no grepping, no file navigation, no inference. That drives the monorepo with shared packages, feature-grouped files at MVP phase, Caddy over Nginx, systemd over PM2, and the SERVER.md.&lt;/p&gt;
&lt;p&gt;Principle 2: types as prediction constraints. The more explicitly typed your code, the smaller the probability space of what Claude can generate next, and the less likely it is to hallucinate incorrect method names, wrong response shapes, or missing validation. That drives TypeScript strict mode everywhere, Zod for all validation, Drizzle schema as single source of truth, and Hono over Express.&lt;/p&gt;
&lt;p&gt;Everything else follows.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;reference-the-stack-in-one-table&quot;&gt;Reference: the stack in one table&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;/th&gt;&lt;th&gt;Choice&lt;/th&gt;&lt;th&gt;Reason&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Language&lt;/td&gt;&lt;td&gt;TypeScript strict&lt;/td&gt;&lt;td&gt;Types constrain prediction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Frontend framework&lt;/td&gt;&lt;td&gt;Astro 5&lt;/td&gt;&lt;td&gt;Static/dynamic split, one build&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UI framework&lt;/td&gt;&lt;td&gt;Vue 3 client:only&lt;/td&gt;&lt;td&gt;SFC = natural cohesion unit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Styling&lt;/td&gt;&lt;td&gt;Tailwind 4&lt;/td&gt;&lt;td&gt;Atomic, no CSS files&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Data fetching&lt;/td&gt;&lt;td&gt;TanStack Query&lt;/td&gt;&lt;td&gt;Explicit state, well-represented&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Backend framework&lt;/td&gt;&lt;td&gt;Hono&lt;/td&gt;&lt;td&gt;TypeScript-first, typed routes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Runtime&lt;/td&gt;&lt;td&gt;Bun&lt;/td&gt;&lt;td&gt;Native TS, fast&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Validation&lt;/td&gt;&lt;td&gt;Zod 4&lt;/td&gt;&lt;td&gt;Runtime types, co-located&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ORM&lt;/td&gt;&lt;td&gt;Drizzle&lt;/td&gt;&lt;td&gt;Single schema file&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database&lt;/td&gt;&lt;td&gt;PostgreSQL&lt;/td&gt;&lt;td&gt;Best training data coverage&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Architecture (MVP)&lt;/td&gt;&lt;td&gt;Single file, grouped&lt;/td&gt;&lt;td&gt;Full context, one generation pass&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Architecture (iterative)&lt;/td&gt;&lt;td&gt;Vertical slices&lt;/td&gt;&lt;td&gt;One task = one file = one load&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Process manager&lt;/td&gt;&lt;td&gt;systemd&lt;/td&gt;&lt;td&gt;Declarative, file-based&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reverse proxy&lt;/td&gt;&lt;td&gt;Caddy&lt;/td&gt;&lt;td&gt;Single readable config file&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hosting&lt;/td&gt;&lt;td&gt;Hetzner VPS&lt;/td&gt;&lt;td&gt;Predictable, cheap, CLI-first&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI/CD&lt;/td&gt;&lt;td&gt;GitHub Actions + SSH&lt;/td&gt;&lt;td&gt;One workflow file, readable stdout&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h2&gt;
&lt;h3 id=&quot;why-astro-vue-instead-of-next-js-react&quot;&gt;Why Astro + Vue instead of Next.js + React?&lt;/h3&gt;
&lt;p&gt;Next.js and React are not wrong choices. They have enormous training data coverage and Claude handles them well.&lt;/p&gt;
&lt;p&gt;The issue is architectural. Next.js blends server and client code in ways that create ambiguity for Claude. Server Components, Client Components, server actions and API routes all coexist in the same files with implicit boundaries, so Claude has to infer which execution context it is in before it can generate correct code.&lt;/p&gt;
&lt;p&gt;Astro&#39;s static-only mode eliminates that ambiguity entirely. Static pages are static. Interactive components are explicitly marked &lt;code&gt;client:only=&quot;vue&quot;&lt;/code&gt;. There is no blurred boundary.&lt;/p&gt;
&lt;p&gt;Vue SFCs add another layer of clarity: template, script and style are co-located in one file, which is the single-file cohesion principle applied at the component level.&lt;/p&gt;
&lt;p&gt;Already invested in Next.js and React? The architecture principles in this post still apply. Use strict TypeScript, co-locate related code, use Tailwind, keep shared types in one place.&lt;/p&gt;
&lt;p&gt;The framework matters less than the structure.&lt;/p&gt;
&lt;h3 id=&quot;does-this-architecture-work-with-react-or-other-frontend-frameworks&quot;&gt;Does this architecture work with React or other frontend frameworks?&lt;/h3&gt;
&lt;p&gt;Yes. The principles are framework-agnostic.&lt;/p&gt;
&lt;p&gt;What matters is that your components are self-contained cohesion units (React &lt;code&gt;.tsx&lt;/code&gt; files work fine for this), that you are not writing CSS files, and that you are not splitting code across layers for a single feature.&lt;/p&gt;
&lt;p&gt;The specific recommendation of Vue + Astro comes from Vue SFCs being naturally single-file and from Astro&#39;s clean static/dynamic boundary. But a React developer can apply the same principles: one file per feature slice, Tailwind only, strict TypeScript, shared types package.&lt;/p&gt;
&lt;h3 id=&quot;should-i-use-a-single-file-for-the-entire-backend-even-for-a-large-app&quot;&gt;Should I use a single file for the entire backend even for a large app?&lt;/h3&gt;
&lt;p&gt;Only at the MVP phase, and only until you hit the output token ceiling. Sonnet 4.6 has a 64k output token limit, which is enough for most initial builds.&lt;/p&gt;
&lt;p&gt;Once you are iterating on a validated product, switch to one file per feature slice: &lt;code&gt;auth.ts&lt;/code&gt;, &lt;code&gt;payments.ts&lt;/code&gt;, &lt;code&gt;dashboard.ts&lt;/code&gt;. Each file contains its Hono route, service logic, Zod schemas, and types.&lt;/p&gt;
&lt;p&gt;The goal is not to minimize file count forever. It is to make sure any single task Claude performs touches exactly one file. That is when context is most efficient.&lt;/p&gt;
&lt;h3 id=&quot;what-if-i-want-to-use-prisma-instead-of-drizzle&quot;&gt;What if I want to use Prisma instead of Drizzle?&lt;/h3&gt;
&lt;p&gt;Prisma and Drizzle serve the same purpose here: a single schema file that gives Claude your complete data model in one read. Prisma&#39;s &lt;code&gt;schema.prisma&lt;/code&gt; is equally good for this.&lt;/p&gt;
&lt;p&gt;The choice between them is largely a preference question. Drizzle has the slight edge because the schema is TypeScript, so types flow naturally into the rest of your codebase without a generation step.&lt;/p&gt;
&lt;p&gt;But if you know Prisma well, the architectural benefit is identical.&lt;/p&gt;
&lt;h3 id=&quot;why-a-vps-over-railway-fly-io-or-vercel&quot;&gt;Why a VPS over Railway, Fly.io, or Vercel?&lt;/h3&gt;
&lt;p&gt;For Claude Code specifically, the VPS wins because your entire infrastructure state lives in files Claude can read and edit directly. systemd service files, a Caddyfile, a SERVER.md.&lt;/p&gt;
&lt;p&gt;On managed platforms, infrastructure state is split between a CLI, a dashboard, and platform-specific configuration formats. Claude cannot read a Railway dashboard. It can read &lt;code&gt;/etc/systemd/system/myapp.service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The cost argument is also real. A 4GB Hetzner VPS runs 10 of your projects for roughly the same monthly cost as one project on a managed PaaS.&lt;/p&gt;
&lt;p&gt;That said, if you are deploying a single project and want zero server management, Railway or Fly.io are reasonable. The architecture and code structure recommendations in this post apply regardless of where you deploy.&lt;/p&gt;
&lt;h3 id=&quot;does-this-work-for-mobile-apps-too&quot;&gt;Does this work for mobile apps too?&lt;/h3&gt;
&lt;p&gt;The backend and shared package principles apply directly. Hono as the API, Drizzle schema as the single source of truth, &lt;code&gt;@repo/api-types&lt;/code&gt; shared between mobile and backend.&lt;/p&gt;
&lt;p&gt;For the mobile layer, React Native and Expo are well-covered in training data and Claude handles them reliably.&lt;/p&gt;
&lt;p&gt;The one-file-per-slice principle applies to React Native screens the same way it applies to Vue components. Each screen is a cohesion unit that should contain its own data fetching, local state and layout, without reaching into shared layer files for routine operations.&lt;/p&gt;
&lt;p&gt;Happy shipping!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This analysis is based on first-principles reasoning about how LLMs operate, confirmed where possible against the Claude Code source code that was inadvertently leaked on March 31, 2026.&lt;/em&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>PostgreSQL Function Syntax: Demystifying Anonymous vs. Tagged Dollar-Quoting</title>
        <published>2025-07-16T00:00:00+00:00</published>
        <updated>2025-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/postgres-function-syntax-dollar-quoting-explained/"/>
        <id>https://www.webdevluis.com/blog/postgres-function-syntax-dollar-quoting-explained/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/postgres-function-syntax-dollar-quoting-explained/">&lt;p&gt;I am running my DB in Supabase, and one of the triggers that inserted an entry into the public table &lt;code&gt;user_metadata&lt;/code&gt; on new user registration was broken and needed update:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;create or replace function public.handle_new_user() returns trigger as $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  insert into public.user_metadata (user_id, plan_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  values (new.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  return new;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$ language plpgsql security definer;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the code I had in Supabase. The issue was that we pass &lt;code&gt;plan_id&lt;/code&gt;, but &lt;code&gt;user_metadata&lt;/code&gt; does not have such a column, so the trigger fails.&lt;/p&gt;
&lt;p&gt;Supabase has an AI assistant that can fix these errors for you, writing new code. Well, it simply has to remove the &lt;code&gt;plan_id&lt;/code&gt;, right? Well this is what it wrote:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CREATE OR REPLACE FUNCTION public.handle_new_user()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; RETURNS trigger&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; LANGUAGE plpgsql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; SECURITY DEFINER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AS $function$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  insert into public.user_metadata (user_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  values (new.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  return new;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$function$;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uh?? My first though was, what is this? On a closer inspection, there is nothing to be alarmed, because the only real change is indeed getting rid of that &lt;code&gt;plan_id&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But that forced me to actually read the code 😅&lt;/p&gt;
&lt;p&gt;I have basic SQL knowledge so I had never seen this syntax before.&lt;/p&gt;
&lt;p&gt;Let&#39;s review what changed piece by piece:&lt;/p&gt;
&lt;h2 id=&quot;number-1&quot;&gt;NUMBER 1&lt;/h2&gt;
&lt;p&gt;This&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;... language plpgsql security definer;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;somehow moved before the inner function part&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; LANGUAGE plpgsql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; SECURITY DEFINER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;... the nested function&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh... in SQL, clauses can switch places, it&#39;s ok. And, you know, the lowercase or uppercase for SQL syntax, it doesn&#39;t matter. SQL is not case sensitive.&lt;/p&gt;
&lt;p&gt;So those two things are the same.&lt;/p&gt;
&lt;h2 id=&quot;number-2&quot;&gt;NUMBER 2&lt;/h2&gt;
&lt;p&gt;This&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;... as $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ... thing broken ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changed to this&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AS $function$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ... thing fixed ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$function$;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uh? What is &lt;code&gt;$$&lt;/code&gt; and &lt;code&gt;$function$&lt;/code&gt;. I have never seen this. So it&#39;s time to learn some syntax.&lt;/p&gt;
&lt;p&gt;They are both &quot;dollar-quotings&quot;, and solve the same problem, namely:&lt;/p&gt;
&lt;p&gt;Say, you&#39;re trying to write a SQL function. Let&#39;s do it step by step:&lt;/p&gt;
&lt;p&gt;Step 1: scaffold the function&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CREATE FUNCTION my_func() RETURNS text LANGUAGE plpgsql AS&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 2: Put the meat in&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CREATE FUNCTION my_func() RETURNS text LANGUAGE plpgsql AS &amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -- a comment with apostrophe isn&amp;#39;&amp;#39;t easy to write&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  return &amp;#39;&amp;#39;Hello, World!&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously, this function is super simple. Notice the single quotes around the function. Why do we have them? Well... this is SQL for you. The function body must be a string, end of story.&lt;/p&gt;
&lt;p&gt;This forces us to quote every actual single quote in the function body, that is annoying. Also, syntax highlighters will likely show the entire body as uniform yellow, hard to read.&lt;/p&gt;
&lt;p&gt;Here is were &lt;em&gt;DOLLAR-QUOTING&lt;/em&gt; enters the stage to solve all these problems.&lt;/p&gt;
&lt;p&gt;It basically allows us to write the function like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CREATE FUNCTION my_func() RETURNS text LANGUAGE plpgsql AS $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -- a comment with apostrophe isn&amp;#39;t easy to write&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  return &amp;#39;Hello, World!&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See, no more duped single quotes, and we get a beautiful syntax highlighting.&lt;/p&gt;
&lt;p&gt;Now you know what dollar-quoting is. But hold on. We have seen two different versions of dollar quoting: &lt;code&gt;$$&lt;/code&gt; and &lt;code&gt;$function$&lt;/code&gt; . Where do they differ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$$&lt;/code&gt;: This is called &lt;em&gt;anonymous dollar-quoting&lt;/em&gt;. It does exactly what we have seen it do right above. It helps us get rid of the single quotes around the function body.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$function$&lt;/code&gt; : This is called &lt;em&gt;Tagged Dollar-Quoting&lt;/em&gt;. And it does... it does the same! So, then what is the point? It is nesting. With anonymous dollar-quoting, you cannot nest them, because the second &lt;code&gt;$$&lt;/code&gt; will ALWAYS mark the end of the first &lt;code&gt;$$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;... let&amp;#39;s nest&amp;#39;em ... AS $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ... do something&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  AS $$ &amp;lt;-- here we are trying to nest a second $$, but all SQL is seeing is the closing of the first $$ :_(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  begin &amp;lt;-- here SQL will show a syntax error!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ... do something else &amp;lt;-- this will never run!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You see how that will break? We can&#39;t nest &lt;code&gt;$$&lt;/code&gt; !&lt;/p&gt;
&lt;p&gt;But we can nest the tagged version!&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;... let&amp;#39;s try again! ... AS $fnc_a$ &amp;lt;-- see we&amp;#39;re using the tag!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ... do something&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  AS $fnc_b$ &amp;lt;-- no tagging confusion here, we&amp;#39;re good!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  begin &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ... merry fucking xmas!! ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  $fnc_b$ &amp;lt;-- and we close them&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$fnc_a$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Hopefully we are all clear about these little SQL syntax adventures&lt;/p&gt;
&lt;p&gt;Go do some &lt;code&gt;$rock_n_roll$&lt;/code&gt;!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to Make VS Code Search Find Specific Gitignored Files</title>
        <published>2025-04-19T00:00:00+00:00</published>
        <updated>2025-04-19T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/vscode-search-specific-ignored-files/"/>
        <id>https://www.webdevluis.com/blog/vscode-search-specific-ignored-files/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/vscode-search-specific-ignored-files/">&lt;p&gt;VS Code search usually hides files listed in your &lt;code&gt;.gitignore&lt;/code&gt;. This is good for hiding things like &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But sometimes you need VS Code search (&lt;code&gt;Ctrl+P&lt;/code&gt; or &lt;code&gt;Ctrl+Shift+F&lt;/code&gt;) to find a &lt;em&gt;specific&lt;/em&gt; file or folder that is in &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&#39;s how to do it without changing your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;
&lt;h2 id=&quot;the-plan&quot;&gt;The Plan&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Tell VS Code search to &lt;em&gt;stop&lt;/em&gt; using the &lt;code&gt;.gitignore&lt;/code&gt; file automatically.&lt;/li&gt;
&lt;li&gt;Manually list &lt;em&gt;only&lt;/em&gt; the files you &lt;em&gt;still&lt;/em&gt; want hidden inside VS Code&#39;s settings.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means any file &lt;em&gt;not&lt;/em&gt; listed in the settings will show up in search, even if it&#39;s in &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-to-do-it&quot;&gt;How to Do It&lt;/h2&gt;
&lt;p&gt;Use Workspace Settings. This keeps the changes only for your current project.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the Command Palette: &lt;code&gt;Ctrl+Shift+P&lt;/code&gt; (or &lt;code&gt;Cmd+Shift+P&lt;/code&gt; on Mac).&lt;/li&gt;
&lt;li&gt;Search for and select &lt;code&gt;Preferences: Open Workspace Settings (JSON)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This opens (or creates) a &lt;code&gt;.vscode/settings.json&lt;/code&gt; file for your project.&lt;/li&gt;
&lt;li&gt;Add or modify the settings like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 1. Stop VS Code search from using .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;search.useIgnoreFiles&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 2. List ALL patterns you STILL want hidden from search results (Ctrl+Shift+F)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;search.exclude&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Default stuff you likely want hidden&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/node_modules&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/bower_components&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Add other patterns FROM YOUR .gitignore that you want hidden&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; For example:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/dist&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/build&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/*.log&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/coverage&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; --- IMPORTANT ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; DO NOT add the patterns you want VS Code TO FIND here.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Example: If you want &amp;#39;docs/internal/**/*.md&amp;#39; to be searchable,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; make sure it&amp;#39;s NOT listed in this &amp;#39;search.exclude&amp;#39; section.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Because &amp;#39;search.useIgnoreFiles&amp;#39; is false, these files will now appear.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 3. Optional: Control what&amp;#39;s hidden in File Explorer and Ctrl+P Quick Open&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; You might want the same files hidden here as in &amp;#39;search.exclude&amp;#39;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; If you want specific files visible in Ctrl+P, DO NOT list them here.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;files.exclude&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/node_modules&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/bower_components&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/dist&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/build&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/*.log&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;**/coverage&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Again, DO NOT list patterns here that you want to find via Ctrl+P.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;explanation&quot;&gt;Explanation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;search.useIgnoreFiles&quot;: false&lt;/code&gt;: Makes VS Code search ignore &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;search.exclude&quot;&lt;/code&gt;: You must list &lt;em&gt;everything&lt;/em&gt; you want hidden from &lt;strong&gt;Search Results&lt;/strong&gt; (&lt;code&gt;Ctrl+Shift+F&lt;/code&gt;). Copy patterns from &lt;code&gt;.gitignore&lt;/code&gt; here (like &lt;code&gt;node_modules&lt;/code&gt;). &lt;strong&gt;Do not list patterns you want to find.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;files.exclude&quot;&lt;/code&gt; (Optional): Hides files from the &lt;strong&gt;File Explorer sidebar&lt;/strong&gt; and affects &lt;strong&gt;Quick Open&lt;/strong&gt; (&lt;code&gt;Ctrl+P&lt;/code&gt;). Usually, keep it similar to &lt;code&gt;search.exclude&lt;/code&gt;. &lt;strong&gt;Do not list patterns you want to find with &lt;code&gt;Ctrl+P&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;downside&quot;&gt;Downside&lt;/h2&gt;
&lt;p&gt;You now manage ignore rules in two places:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt; (for Git).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.vscode/settings.json&lt;/code&gt; (for VS Code search/visibility).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you update &lt;code&gt;.gitignore&lt;/code&gt;, you might need to update &lt;code&gt;search.exclude&lt;/code&gt; and &lt;code&gt;files.exclude&lt;/code&gt; in VS Code settings too.&lt;/p&gt;
&lt;p&gt;This setup gives you precise control over which ignored files show up in VS Code search.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Mastering React.useEffect for Optimal Component Behavior</title>
        <published>2023-11-11T00:00:00+00:00</published>
        <updated>2023-11-11T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/efficient-use-react-useeffect-best-practices/"/>
        <id>https://www.webdevluis.com/blog/efficient-use-react-useeffect-best-practices/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/efficient-use-react-useeffect-best-practices/">&lt;p&gt;React&#39;s &lt;code&gt;useEffect&lt;/code&gt; hook is a powerful tool for synchronizing your components with external systems. However, its misuse can lead to inefficient and error-prone code. Understanding when and how to use &lt;code&gt;useEffect&lt;/code&gt; is crucial for crafting efficient React applications.&lt;/p&gt;
&lt;h2 id=&quot;understanding-useeffect-s-role&quot;&gt;Understanding useEffect&#39;s Role&lt;/h2&gt;
&lt;h3 id=&quot;the-ideal-use-of-useeffect&quot;&gt;The Ideal Use of useEffect&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;useEffect&lt;/code&gt; is primarily designed to &quot;step out&quot; of the React paradigm and interact with external systems, such as APIs or subscriptions. Its most effective use cases include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executing logic when a component mounts or updates.&lt;/li&gt;
&lt;li&gt;Synchronizing with external data sources or APIs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;common-misuses-of-useeffect&quot;&gt;Common Misuses of useEffect&lt;/h3&gt;
&lt;p&gt;React developers often fall into the trap of using &lt;code&gt;useEffect&lt;/code&gt; for scenarios where it’s unnecessary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React State Synchronization:&lt;/strong&gt; If the goal is to sync React state, &lt;code&gt;useEffect&lt;/code&gt; is likely overkill. Direct state management or context can often suffice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Responding to User Actions:&lt;/strong&gt; Instead of triggering effects in response to user actions, it&#39;s more efficient to handle these within event handlers directly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Combining Unrelated Logic:&lt;/strong&gt; Grouping unrelated logic in a single &lt;code&gt;useEffect&lt;/code&gt; because they run at similar times can lead to tangled code. It&#39;s cleaner to use multiple &lt;code&gt;useEffect&lt;/code&gt; instances for distinct tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;external-subscriptions-and-fetch-requests&quot;&gt;External Subscriptions and Fetch Requests&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;useEffect&lt;/code&gt; can be used for subscribing to external sources, &lt;code&gt;useSyncExternalStore&lt;/code&gt; offers a more robust solution. For data fetching, especially with potential race conditions, consider using libraries like TanStack Query or a suitable framework.&lt;/p&gt;
&lt;h2 id=&quot;useeffect-dependencies-a-critical-consideration&quot;&gt;useEffect Dependencies: A Critical Consideration&lt;/h2&gt;
&lt;p&gt;Correctly managing &lt;code&gt;useEffect&lt;/code&gt; dependencies ensures the effect syncs accurately with your component&#39;s lifecycle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State Variables Only:&lt;/strong&gt; Dependencies should typically be limited to state variables (state, props, computed values).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid Non-Reactive Values:&lt;/strong&gt; Mutable, non-reactive variables like &lt;code&gt;location.pathname&lt;/code&gt; or &lt;code&gt;ref.current&lt;/code&gt; should not be dependencies. Instead, use alternatives like &lt;code&gt;useSyncExternalStore&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;linting-and-useeffect&quot;&gt;Linting and useEffect&lt;/h3&gt;
&lt;p&gt;React&#39;s linter warnings about missing dependencies in &lt;code&gt;useEffect&lt;/code&gt; are often indicative of issues in the effect&#39;s implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Never Suppress Linter Warnings:&lt;/strong&gt; Ignoring these warnings can lead to subtle bugs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Split Up Complex Effects:&lt;/strong&gt; If an effect is doing too much, consider breaking it into smaller, more focused effects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be Cautious with Objects and Functions:&lt;/strong&gt; Using these as dependencies can lead to unnecessary re-renders or stale closures.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;experimental-useeffectevent&quot;&gt;Experimental: useEffectEvent&lt;/h2&gt;
&lt;p&gt;React&#39;s experimental &lt;code&gt;useEffectEvent&lt;/code&gt; API offers a way to create effects that respond to reactive state changes without including the state in the dependency array:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;const onVisit = useEffectEvent(visitedUrl =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  logVisit(visitedUrl, numberOfItems);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;useEffect(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  setTimeout(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    onVisit(url);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }, 5000); // Delay logging visits&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}, [url]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;best-practices-for-useeffect&quot;&gt;Best Practices for useEffect&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Declare Non-Reactive Variables Outside Components:&lt;/strong&gt; This practice keeps your components clean and focused.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Naming Conventions:&lt;/strong&gt; If a function doesn’t call hooks, avoid using the &lt;code&gt;use&lt;/code&gt; prefix, as this is conventionally reserved for hooks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Understanding and applying these principles will help you leverage &lt;code&gt;useEffect&lt;/code&gt; effectively, leading to more maintainable and performant React applications.&lt;/p&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;React Documentation on Effects: &lt;a rel=&quot;external&quot; href=&quot;https://react.dev/reference/react/useEffect&quot;&gt;React Docs: useEffect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Handling External Data Fetching: &lt;a rel=&quot;external&quot; href=&quot;https://tanstack.com/query/v4&quot;&gt;TanStack Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Managing Dependencies in useEffect: &lt;a rel=&quot;external&quot; href=&quot;https://react.dev/reference/react/useEffect#specifying-reactive-dependencies&quot;&gt;React Docs: Effect Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;React&#39;s useSyncExternalStore: &lt;a rel=&quot;external&quot; href=&quot;https://react.dev/reference/react/useSyncExternalStore&quot;&gt;React Docs: useSyncExternalStore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Experimental useEffectEvent: &lt;a rel=&quot;external&quot; href=&quot;https://react.dev/reference/react/experimental_useEffectEvent&quot;&gt;React Experimental useEffectEvent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Decoding Firestore&#39;s Cache Strategies</title>
        <published>2023-06-06T00:00:00+00:00</published>
        <updated>2023-06-06T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/decoding-firestores-cache-strategies/"/>
        <id>https://www.webdevluis.com/blog/decoding-firestores-cache-strategies/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/decoding-firestores-cache-strategies/">&lt;p&gt;I&#39;ll tell you what, I didn&#39;t know there was a Firestore&#39;s cache behavior out of the box. So I read about about it, and I want to share it, let&#39;s get right into it!&lt;/p&gt;
&lt;h2 id=&quot;exploring-firestore-s-cache-strategies&quot;&gt;Exploring Firestore&#39;s Cache Strategies&lt;/h2&gt;
&lt;p&gt;Firestore gives us the power to configure cache behavior with a simple tool - GetOptions. In our toolbox, we&#39;ve got three cache strategies.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Default&lt;/strong&gt;: This little trooper attempts to fetch the latest data from the Firestore server. But, you know what? If it can&#39;t reach the server (because hey, things happen), it won&#39;t leave you hanging. It returns the data from the cache. Reliable, isn&#39;t it?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server&lt;/strong&gt;: The server strategy is a bit more, shall we say, focused. It only fetches from the Firestore server. If it can&#39;t reach the server, it throws in the towel. It&#39;s all or nothing with this one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache&lt;/strong&gt;: Lastly, we have the cache strategy. It only fetches from the cache and will accept defeat if the document isn&#39;t in the cache. It&#39;s got a one-track mind, but it sure is good at what it does.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, wouldn&#39;t it be fun to see this in action? Take a look at this code snippet:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; getDoc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; doc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; getFirestore&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; GetOptions&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;firebase/firestore&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; db&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; getFirestore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; docRef&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; doc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;documentId&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Create a GetOptions object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; options&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; GetOptions&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  source&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; You can also use &amp;quot;default&amp;quot; or &amp;quot;server&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Get the document, forcing the SDK to fetch from the cache.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;getDoc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;docRef&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; options&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;docSnapshot&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;docSnapshot&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;exists&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Document data:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; docSnapshot&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;data&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;No such document!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;catch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Error getting document:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;decoding-stale-cache-data&quot;&gt;Decoding Stale Cache Data&lt;/h2&gt;
&lt;p&gt;Now, you might be wondering, &quot;How does Firestore determine if cache data is stale or not?&quot; Good question! Firestore doesn&#39;t give us explicit controls for this. Instead, it&#39;s always keeping our cache synchronized with the server whenever our network connection is on point.&lt;/p&gt;
&lt;p&gt;Firestore tries its best to provide up-to-date results from the cache when you perform a query. But, if the required data is not available or if it thinks the cache data may be a bit behind the times (like, it&#39;s been awhile since the last synchronization), it&#39;ll attempt to fetch the fresh data from the server.&lt;/p&gt;
&lt;h2 id=&quot;a-quick-note-on-offline-persistence&quot;&gt;A Quick Note on Offline Persistence&lt;/h2&gt;
&lt;p&gt;Before we wrap up, let&#39;s not forget about offline persistence. Firestore offers offline support for mobile (iOS and Android) and web clients. Just remember, offline persistence is not a fan of Node.js client libraries - it&#39;s not supported there.&lt;/p&gt;
&lt;p&gt;On Android and iOS, offline persistence is enabled by default (convenient, eh?), but on the web, it likes to be formally invited - you&#39;ve got to enable it explicitly. Also, Firestore is a self-starter and manages the offline cache, including deleting older, unused data. But if you want to get your hands dirty, you can manually clear the cache.&lt;/p&gt;
&lt;p&gt;For the finer details and limitations of Firestore offline persistence, you&#39;d do well to refer to the &lt;a rel=&quot;external&quot; href=&quot;https://firebase.google.com/docs/firestore/manage-data/enable-offline&quot;&gt;official Firestore documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hope this helped!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>TypeScript Object Types: Extracting Types from Keys and Values</title>
        <published>2023-03-15T00:00:00+00:00</published>
        <updated>2023-03-15T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/typescript-object-types-extracting-types-from-keys-and-values/"/>
        <id>https://www.webdevluis.com/blog/typescript-object-types-extracting-types-from-keys-and-values/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/typescript-object-types-extracting-types-from-keys-and-values/">&lt;p&gt;As a TypeScript developer, you&#39;ll often find yourself in situations where you need to extract the types of the keys and values from an object. Fortunately, TypeScript provides some utility types that can help you achieve this.&lt;/p&gt;
&lt;p&gt;Say, you&#39;re building an app that needs to display some user information, and you have an object that contains all the user data. You need to extract specific types from this object, including the types of both the keys and values.&lt;/p&gt;
&lt;p&gt;How?&lt;/p&gt;
&lt;p&gt;Enter TypeScript&#39;s &lt;code&gt;keyof&lt;/code&gt; and &lt;code&gt;typeof&lt;/code&gt; operators.&lt;/p&gt;
&lt;p&gt;Here&#39;s an example of how you can use these operators to extract types from both keys and values of an object:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; user&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;John&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  age&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; UserKey&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; keyof&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; typeof&lt;/span&gt;&lt;span&gt; user&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; &amp;quot;name&amp;quot; | &amp;quot;age&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; UserValue&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; typeof&lt;/span&gt;&lt;span&gt; user&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;UserKey&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; string | number&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we define an object &lt;code&gt;user&lt;/code&gt; with two properties: &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt;. We then use the &lt;code&gt;keyof&lt;/code&gt; operator to extract the keys of the &lt;code&gt;user&lt;/code&gt; object and assign it to the &lt;code&gt;UserKey&lt;/code&gt; type. This results in a union type of &lt;code&gt;&quot;name&quot;&lt;/code&gt; and &lt;code&gt;&quot;age&quot;&lt;/code&gt;. Next, we use the &lt;code&gt;typeof user[UserKey]&lt;/code&gt; expression to access all possible values of the &lt;code&gt;user&lt;/code&gt; object. This results in a union type of &lt;code&gt;string&lt;/code&gt; and &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&#39;s take a closer look at &lt;code&gt;keyof&lt;/code&gt; and &lt;code&gt;typeof&lt;/code&gt; operators:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;keyof&lt;/code&gt;: This operator returns a union type of all the keys in an object. In the example above, we used &lt;code&gt;keyof user&lt;/code&gt; to extract the keys from the &lt;code&gt;user&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typeof&lt;/code&gt;: This operator returns the type of a value. In the example above, we used &lt;code&gt;typeof user[UserKey]&lt;/code&gt; to extract a union of the types of the values in &lt;code&gt;user&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using &lt;code&gt;keyof&lt;/code&gt; and &lt;code&gt;typeof&lt;/code&gt; operators, you can extract specific types from objects and build more robust and reliable TypeScript applications.&lt;/p&gt;
&lt;p&gt;Hope this helps!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Using createGetInitialProps with NextJS Functional Components</title>
        <published>2023-03-08T00:00:00+00:00</published>
        <updated>2023-03-08T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/nextjs-functional-components-creategetinitialprops/"/>
        <id>https://www.webdevluis.com/blog/nextjs-functional-components-creategetinitialprops/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/nextjs-functional-components-creategetinitialprops/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://mantine.dev/&quot;&gt;Mantine&lt;/a&gt; is a UI component library I love.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://mantine.dev/guides/next/#generate-new-application&quot;&gt;Mantine docs&lt;/a&gt; show how to use Mantine in combination with NextJS. You have to use a &lt;code&gt;createGetInitialProps&lt;/code&gt; function in the &lt;code&gt;pages/_document.tsx&lt;/code&gt;, but their example uses a React class component.&lt;/p&gt;
&lt;p&gt;No one likes class components.&lt;/p&gt;
&lt;p&gt;So I&#39;ll show you how to do it with a functional component.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;I assume you&#39;re somewhat familiar with NextJS and are trying to configure Mantine. The &lt;code&gt;_document.tsx&lt;/code&gt; file is just a part of the NextJS file structure (more info in the &lt;a rel=&quot;external&quot; href=&quot;https://nextjs.org/docs/advanced-features/custom-document&quot;&gt;NextJS docs&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using &lt;code&gt;createGetInitialProps&lt;/code&gt; with a functional component is pretty straightforward. All you have to do is define a &lt;code&gt;getInitialProps&lt;/code&gt; function inside your component.&lt;/p&gt;
&lt;p&gt;Here&#39;s an example of how your &lt;code&gt;pages/_documents.tsx&lt;/code&gt; file could look like:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; Html&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; Head&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; Main&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; NextScript&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;next/document&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; createGetInitialProps&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;@mantine/next&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; getInitialProps&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; createGetInitialProps&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Props&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;  initialProps&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Document&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; initialProps&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Props&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Html&lt;/span&gt;&lt;span&gt; lang&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Head&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;Main&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;NextScript&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span&gt;Html&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getInitialProps&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; getInitialProps&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; default&lt;/span&gt;&lt;span&gt; Document&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the function &lt;code&gt;getInitialProps&lt;/code&gt; returned by &lt;code&gt;createGetInitialProps()&lt;/code&gt; is attached to the &lt;code&gt;Document&lt;/code&gt; as a static method.&lt;/p&gt;
&lt;p&gt;And that&#39;s it! You now have a &lt;code&gt;Document&lt;/code&gt; functional component with &lt;code&gt;getInitialProps&lt;/code&gt; support.&lt;/p&gt;
&lt;p&gt;I hope this helps!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building the Flatten Type with Typescript: A Tutorial for Beginners</title>
        <published>2023-01-01T00:00:00+00:00</published>
        <updated>2023-01-01T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/flatten-type-typescript-tutorial-beginners/"/>
        <id>https://www.webdevluis.com/blog/flatten-type-typescript-tutorial-beginners/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/flatten-type-typescript-tutorial-beginners/">&lt;p&gt;Typescript learning can be challenging but rewarding. To improve your understanding and skills, you can try building types and analyzing them piece by piece.&lt;/p&gt;
&lt;p&gt;In this post we&#39;ll build a &lt;code&gt;Flatten&lt;/code&gt; type.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Flatten&lt;/code&gt; type is a utility type that takes an array of arrays and flattens it into a single array. It can be used to simplify the process of working with nested arrays.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flattened&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; [1, 2, 3, 4, 5]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have to somehow look inside the input array recursively, and spread any elements we find to be arrays.&lt;/p&gt;
&lt;p&gt;Let&#39;s see how to do it step by step:&lt;/p&gt;
&lt;p&gt;First, define the &lt;code&gt;Flatten&lt;/code&gt; type as a generic type. It needs to take a type parameter which is the array we will provide when using the type.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; implementation goes here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;T extends any[]&lt;/code&gt; we enforce the parameter &lt;code&gt;T&lt;/code&gt; to be of type &lt;code&gt;any[]&lt;/code&gt; .&lt;/p&gt;
&lt;p&gt;Next, we&#39;re going to &quot;look into&quot; the array and get the values. We can do this with the &lt;code&gt;infer&lt;/code&gt; keyword, which is used in the context of a conditional (&lt;code&gt;extends&lt;/code&gt; keyword).&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; S&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; S&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Test&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; this is just going to be [1, 2]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The spread operator (the 3 dots in &lt;code&gt;...infer S&lt;/code&gt; ) means that we are &quot;grabbing&quot; &lt;strong&gt;all&lt;/strong&gt; values inside the array, not just one.&lt;/p&gt;
&lt;p&gt;For the false branch let&#39;s simply return &lt;code&gt;[]&lt;/code&gt;, as it is the case of &lt;code&gt;T&lt;/code&gt; being an empty array.&lt;/p&gt;
&lt;p&gt;Now, we want to look at the values inside the input array one by one, and check if they are arrays or not, because if they are arrays we want to flatten them. To do this, we can split the contents of the array into two inferred types: &lt;code&gt;First&lt;/code&gt; and &lt;code&gt;Rest&lt;/code&gt; :&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Test&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; this will now be 1, as the type is only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;returning the first value in the array &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we used &lt;a rel=&quot;external&quot; href=&quot;https://www.typescriptlang.org/docs/handbook/variable-declarations.html#tuple-destructuring&quot;&gt;tuple destructuring&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now let&#39;s check if the first element is an array. We need a conditional, which in typescript is done with &lt;code&gt;extends&lt;/code&gt; .&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; unknown&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    ?&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; true branch, First is an array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    :&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; false branch, First is not an array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the true branch, &lt;code&gt;First&lt;/code&gt; is an array, so we want to recursively call the &lt;code&gt;Flatten&lt;/code&gt; type (we want to flatten it):&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; unknown&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;First&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    :&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; false branch, First is not an array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the false branch, we could try to just use &lt;code&gt;First&lt;/code&gt; without the need to flatten it. Let&#39;s try it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; unknown&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;First&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    :&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might be noticing something. Right now, all this type is doing is returning only the &lt;code&gt;First&lt;/code&gt; element of the array. What about the &lt;code&gt;Rest&lt;/code&gt; of the array? We&#39;re never doing anything with it. When &lt;code&gt;First&lt;/code&gt; is an array, it makes sense to spread the &lt;code&gt;First&lt;/code&gt; along with the &lt;code&gt;Rest&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; unknown&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    :&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What about when &lt;code&gt;First&lt;/code&gt; is not an array? How to take into account the &lt;code&gt;Rest&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Can we do this?&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; unknown&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No. It is obvious that &lt;code&gt;[First, ...Rest]&lt;/code&gt; will result in the same input array. This is not enough. &lt;code&gt;Rest&lt;/code&gt; is the same input array minus the &lt;code&gt;First&lt;/code&gt; element, which has already been processed. Even if the &lt;code&gt;First&lt;/code&gt; element is not an array, we need to recursively check the &lt;code&gt;Rest&lt;/code&gt; of the array elements to check which ones are arrays.&lt;/p&gt;
&lt;p&gt;How can we continue flattening the &lt;code&gt;Rest&lt;/code&gt; of array elements?&lt;/p&gt;
&lt;p&gt;Any idea comes to mind?&lt;/p&gt;
&lt;p&gt;Yes, we can just use the type we&#39;re creating!&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;infer&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Rest&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; First&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; unknown&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    ?&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Rest&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;First&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Rest&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  :&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This might look slightly convoluted at this point, but &lt;code&gt;Flatten&amp;lt;Rest&amp;gt;&lt;/code&gt; is taking the first element of the &lt;code&gt;Rest&lt;/code&gt; array and flattening it. This is done recursively until &lt;code&gt;Rest&lt;/code&gt; is an empty array or, what is the same, until all elements of the &lt;code&gt;T&lt;/code&gt; array have been processed.&lt;/p&gt;
&lt;p&gt;Now we can verify that the type works, for example, with:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flattened&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Flatten&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; [1, 2, 3, 4, 5]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope this tutorial has helped you understand how to build the Flatten type with typescript. Happy coding!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Easily Transfer Money from Toptal Pay to Your European Bank Account in 2023</title>
        <published>2023-01-01T00:00:00+00:00</published>
        <updated>2023-01-01T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/transfer-money-from-toptal-pay-to-european-bank-account-2023/"/>
        <id>https://www.webdevluis.com/blog/transfer-money-from-toptal-pay-to-european-bank-account-2023/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/transfer-money-from-toptal-pay-to-european-bank-account-2023/">&lt;p&gt;If you are based in Europe and working for &lt;a rel=&quot;external&quot; href=&quot;https://www.toptal.com/NJyb2L/worlds-top-talent&quot;&gt;Toptal&lt;/a&gt;, like me, you will probably want to transfer money from Toptal Pay to your European bank account. There are several ways to do this, but some options are more cost-effective than others.&lt;/p&gt;
&lt;p&gt;Since I didn&#39;t want to lose half my money in fees before even being able to use it, I did some tests, research and asking around and found what seems to be the cheapest, fee-light way.&lt;/p&gt;
&lt;p&gt;All you need is a &lt;a rel=&quot;external&quot; href=&quot;https://wise.com/invite/u/luism662&quot;&gt;Wise account&lt;/a&gt; and a &lt;a rel=&quot;external&quot; href=&quot;https://revolut.com/referral/luisy5ax0!DEC1-22-AR-H1&quot;&gt;Revolut account&lt;/a&gt;, which you should be able to get in a few days with just an internet connection.&lt;/p&gt;
&lt;p&gt;Here&#39;s a step-by-step guide to help you make the most efficient and cost-effective transfer. It feels fairly convoluted at first glance but it&#39;s easy-peasy to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign up for a &lt;a rel=&quot;external&quot; href=&quot;https://wise.com/invite/u/luism662&quot;&gt;Wise account&lt;/a&gt; and create a &lt;strong&gt;US bank account&lt;/strong&gt;. This will be the intermediary account for your transfer.&lt;/li&gt;
&lt;li&gt;Sign up for a &lt;a rel=&quot;external&quot; href=&quot;https://revolut.com/referral/luisy5ax0!DEC1-22-AR-H1&quot;&gt;Revolut account&lt;/a&gt;. Revolut is great because it offers low currency exchange rates. You will most likely upgrade to the &lt;strong&gt;premium plan&lt;/strong&gt;, which costs 8 Euro per month. You want to do this because the premium account offers lower currency exchange rates as long as you transfer more than $1000 per month. In practice, this means that if you make more than $1800 per month you&#39;re better off with the Premium account.&lt;/li&gt;
&lt;li&gt;Transfer the money from your Toptal Pay account to your Wise US bank account. There is a flat fee of $0.5 for this transfer, and it will take 2-3 days to complete.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transfer the USD&lt;/strong&gt; from your Wise US bank account to your Revolut account. This transfer will also take 1-2 days to complete. Here, make sure to transfer USD to USD. Do not convert the currency to Euro, because the Wise exchange fees are higher.&lt;/li&gt;
&lt;li&gt;Use your Revolut account to &lt;strong&gt;exchange your USD for EUR&lt;/strong&gt; at the market rate plus a small fee.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transfer the EUR&lt;/strong&gt; from your Revolut account to your European bank account. The transfer should arrive immediately. You could also use Revolut as your main bank account, if you prefer that.&lt;/li&gt;
&lt;li&gt;Enjoy your hard-earned funds! 💸&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By following the steps above, you can transfer money from Toptal Pay to your European bank account efficiently and at a low cost.&lt;/p&gt;
&lt;p&gt;I hope it was useful to you!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to implement the Pick generic in Typescript</title>
        <published>2022-10-29T00:00:00+00:00</published>
        <updated>2022-10-29T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/typescript-pick-implementation/"/>
        <id>https://www.webdevluis.com/blog/typescript-pick-implementation/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/typescript-pick-implementation/">&lt;p&gt;The TypeScript utility &lt;code&gt;Pick&lt;/code&gt; constructs a type from a set of properties from another type. For example:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type Book {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  title: string;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  author: string;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  price: number;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;// Here we &amp;quot;pick&amp;quot; title and author, but not price&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type TitleOrAuthor = Pick&amp;lt;Book, &amp;#39;title&amp;#39; | &amp;#39;author&amp;#39;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;const titleOrAuthor: TitleOrAuthor = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  title: &amp;#39;The Lord of The Rings&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  author: &amp;#39;J.R.R. Tokien&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note how the type &lt;code&gt;TitleOrAuthor&lt;/code&gt; does not have a &lt;code&gt;price&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;How would we implement the generic type &lt;code&gt;Pick&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Let&#39;s see how, step by step.&lt;/p&gt;
&lt;p&gt;First, we know from the definition that the generic will accept two types:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type MyPick&amp;lt;T, K&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we need to look at each property and compare it to &lt;code&gt;K&lt;/code&gt;. So we start by mapping (or &quot;looping over&quot;) &lt;code&gt;T&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type MyPick&amp;lt;T, K&amp;gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [Key in keyof T]: any&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here &lt;code&gt;Key in keyof T&lt;/code&gt; can be read as &quot;I am looking at each key of &lt;code&gt;T&lt;/code&gt;, and I will assign it to &lt;code&gt;Key&lt;/code&gt;&quot;. Kind of like a loop.&lt;/p&gt;
&lt;p&gt;The type of each property is going to be unchanged, which means: it&#39;s going to simply be the property &lt;code&gt;Key&lt;/code&gt; of the object &lt;code&gt;T&lt;/code&gt;. Or &lt;code&gt;T[Key]&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type MyPick&amp;lt;T, K&amp;gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [Key in keyof T]: T[Key]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, we basically end up with the same type. In other words:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type MyPick&amp;lt;T, K&amp;gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [Key in keyof T]: T[Key]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type TypeA = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  foo: &amp;#39;bar&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type TypeB = MyPick&amp;lt;TypeA, any&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;// TypeA and TypeB are the same type&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s add the cherry on the cake that will make it work.&lt;/p&gt;
&lt;p&gt;We need a way to say: &quot;if the current &lt;code&gt;Key&lt;/code&gt; is one of those in &lt;code&gt;K&lt;/code&gt; pick it, otherwise, ignore it&quot;. If... In other words, we are going to need a &lt;a rel=&quot;external&quot; href=&quot;https://www.typescriptlang.org/docs/handbook/2/conditional-types.html&quot;&gt;conditional type&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Conditional types follow this syntax: &lt;code&gt;condition ? true expression : false expression&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In our case, the condition is &lt;code&gt;Key extends K&lt;/code&gt;. If &lt;code&gt;K&lt;/code&gt; is a union type, for example, &lt;code&gt;&#39;title&#39; | &#39;author&#39;&lt;/code&gt;, then, if &lt;code&gt;Key&lt;/code&gt; is either &lt;code&gt;&#39;title&#39;&lt;/code&gt; or &lt;code&gt;&#39;author&#39;&lt;/code&gt; then the condition will be true.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&#39;title&#39; extends &#39;title&#39; | &#39;author&#39;&lt;/code&gt;, this is true.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&#39;age&#39; extends &#39;title&#39; | &#39;author&#39;&lt;/code&gt;, this is false.&lt;/p&gt;
&lt;p&gt;Let&#39;s go to our type and plug this condition in there.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type MyPick&amp;lt;T, K&amp;gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [Key in keyof T as Key extends K]: T[Key]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the keyword &lt;code&gt;as&lt;/code&gt;. This is necessary if we want to tell TypeScript that we want to do something with the &lt;code&gt;Key&lt;/code&gt;. If we wanted to use the &lt;code&gt;Key&lt;/code&gt; as it is, as key, we wouldn&#39;t need it. But in this case, we want to do something with it (compare it to &lt;code&gt;K&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The above alone with not work though. We&#39;re missing the true and false expressions. Let&#39;s add them:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type MyPick&amp;lt;T, K&amp;gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [Key in keyof T as Key extends K ? Key : ??? ]: T[Key]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the true expression, it should be clear that we just want &lt;code&gt;Key&lt;/code&gt;, because we want the same keys as the original object. But what for the false expression? What goes in there?&lt;/p&gt;
&lt;p&gt;In the false expression, we want to tell TypeScript &quot;since this &lt;code&gt;Key&lt;/code&gt; is not one of &lt;code&gt;K&lt;/code&gt;, just ignore it. Skip it!&quot;. TypeScript has a specific type for things that do not return any value. It is &lt;code&gt;never&lt;/code&gt;. That&#39;s what we need there.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type MyPick&amp;lt;T, K&amp;gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [Key in keyof T as Key extends K ? Key : never ]: T[Key]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s similar to how functions with return type &lt;code&gt;never&lt;/code&gt;, never reach the end of their execution.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/typescript-pick-implementation/giorno-ger.webp&quot; alt=&quot;Giorno Gold Experience Requiem says: &amp;quot;Your Key will never reach the end of the iteration&amp;quot;&amp;quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now we&#39;re done. If we used the type, it would work!&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type TitleOrAuthor = MyPick&amp;lt;Book, &amp;#39;title&amp;#39; | &amp;#39;author&amp;#39;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Web Accessibility Guidelines 2022 Update</title>
        <published>2022-09-13T00:00:00+00:00</published>
        <updated>2022-09-13T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/web-accessibility-guidelines-2022-update/"/>
        <id>https://www.webdevluis.com/blog/web-accessibility-guidelines-2022-update/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/web-accessibility-guidelines-2022-update/">&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;This is an extended version of &lt;a href=&quot;https://www.webdevluis.com/blog/a11y/&quot;&gt;my previous post&lt;/a&gt;, which was slightly outdated.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/blog/web-accessibility-guidelines-2022-update/so-you-want-a11y.webp&quot; alt=&quot;Charlie in the Chocolate Factory meme: So you want to make your website accessible? Tell me again how many times you&amp;#39;ve used a screen reader&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So you want to make your website accessible, or &quot;a11y&quot;? (accessible and a11y are the same thing).&lt;/p&gt;
&lt;p&gt;Good for you!&lt;/p&gt;
&lt;p&gt;Ideally, applying a11y guidelines should be a part of the development process, rather than an afterthought. I&#39;m going to go over some steps you can follow to make your site a11y.&lt;/p&gt;
&lt;p&gt;But first... If you are not familiar with the concept of accessibility, you should at least read &lt;a rel=&quot;external&quot; href=&quot;https://a11y.coffee/&quot;&gt;a basic intro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Okay, we&#39;re good? We&#39;re good. Let&#39;s continue.&lt;/p&gt;
&lt;h2 id=&quot;steps-to-perform-an-a11y-audit&quot;&gt;Steps to perform an a11y audit&lt;/h2&gt;
&lt;h3 id=&quot;automatic-testing&quot;&gt;Automatic testing&lt;/h3&gt;
&lt;p&gt;There are a handful of these types of tools. All of them run an audit check and provide a list of things to change. Some of these tools are described here in more detail: https://a11y.coffee/start-testing&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://wave.webaim.org&quot;&gt;WAVE Web Accessibility Evaluation Tool&lt;/a&gt;: in my opinion it is the tool that gives a quickest and most visual overview. I’d start here. It shows landmarks, a11y features, and a11y errors, among other things.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Additional tools that provide similar information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Firefox a11y audit in Firefox developer tools (also recommended)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In a11y dev tools tab, select: “All issues”, and “show tabbing order”. You can also simulate color deficiency.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.deque.com/axe/browser-extensions/&quot;&gt;Axe Chrome extension&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Chrome&#39;s lighthouse&lt;/a&gt; audit in chrome developer tools&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://accessibilityinsights.io&quot;&gt;Accessibility insights&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/dequelabs/axe-core-npm/blob/develop/packages/cli/README.md&quot;&gt;Axe cli&lt;/a&gt;. Yes! You can run a11y tests from the command line like so: &lt;code&gt;axe luis-martinez.net/blog/a11y&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My recommendation is to use the &lt;a rel=&quot;external&quot; href=&quot;https://wave.webaim.org&quot;&gt;WAVE Web Accessibility Evaluation Tool&lt;/a&gt; to get a quick, visual idea of the a11y issues in the page, and then fix them.&lt;/p&gt;
&lt;h3 id=&quot;a11y-checklist&quot;&gt;A11y checklist&lt;/h3&gt;
&lt;p&gt;Use an a11y checklist to discover issues that automated tools might have overlooked: &lt;a rel=&quot;external&quot; href=&quot;https://www.a11yproject.com/checklist&quot;&gt;https://www.a11yproject.com/checklist&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/standards/wcag/checklist&quot;&gt;https://webaim.org/standards/wcag/checklist&lt;/a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https://www.wuhcag.com/wcag-checklist/&quot;&gt;https://www.wuhcag.com/wcag-checklist/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go through one of the checklists and make sure your website passes every item.&lt;/p&gt;
&lt;h3 id=&quot;manual-testing&quot;&gt;Manual testing&lt;/h3&gt;
&lt;p&gt;Lastly, cross-check the following use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interactive elements should follow ARIA guidelines. Explore the list of &lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/&quot;&gt;WAI ARIA patterns&lt;/a&gt; and make sure the ones you use in your page follow these guidelines&lt;/li&gt;
&lt;li&gt;Add a &quot;skip to content link&quot;, if suitable. Suggested read: &lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/skipnav&quot;&gt;https://webaim.org/techniques/skipnav&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Navigate each page with the keyboard. Tab through the site and make sure every interactive element is focusable and has a distinct focus state. Suggested reads: &lt;a rel=&quot;external&quot; href=&quot;https://web.dev/how-to-review/#start-with-the-keyboard,%20https://webaim.org/techniques/keyboard/&quot;&gt;https://web.dev/how-to-review/#start-with-the-keyboard, https://webaim.org/techniques/keyboard/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Each page should be screen reader navigable (see &lt;a href=&quot;https://www.webdevluis.com/blog/web-accessibility-guidelines-2022-update/#about-screen-readers&quot;&gt;screen readers&lt;/a&gt; section)&lt;/li&gt;
&lt;li&gt;If you are building custom widgets, make sure they are a11y (see &lt;a href=&quot;https://www.webdevluis.com/blog/web-accessibility-guidelines-2022-update/#custom-widgets&quot;&gt;Custom widgets&lt;/a&gt; section)&lt;/li&gt;
&lt;li&gt;Touch points (interactive elements on mobile devices) should have appropriate dimensions on touch device sizes: at minimum 48x48 pt for touchable areas, and 32 pt of space in between touchable elements.&lt;/li&gt;
&lt;li&gt;View the site in both grayscale and high contrast and make sure it is readable. I recommend the &lt;a rel=&quot;external&quot; href=&quot;https://chrome.google.com/webstore/detail/high-contrast/djcfdncoelnlbldjfhinnjlhdjlikmph&quot;&gt;High Contrast chrome extension&lt;/a&gt;, which will enable you to select a handful of color modes. On Firefox, you can use the accessibility tab in the dev tools. Otherwise, google &quot;website grayscale + your browser&quot; and see what you find.&lt;/li&gt;
&lt;li&gt;View the site at 200% magnification and make sure everything still works. For this, simply use the native zoom of the browser, usually &lt;code&gt;ctrl&lt;/code&gt;/&lt;code&gt;cmd&lt;/code&gt; and the &lt;code&gt;+&lt;/code&gt; key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;about-screen-readers&quot;&gt;About screen readers&lt;/h2&gt;
&lt;p&gt;A screen reader is a software that reads the content of the screen for users that, for example, are visually impaired. Become familiar with at least one screen reader.&lt;/p&gt;
&lt;p&gt;Depending on the operative system, there are different screen readers available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows users have &lt;a rel=&quot;external&quot; href=&quot;https://www.nvaccess.org/&quot;&gt;NVDA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mac uses have &lt;a rel=&quot;external&quot; href=&quot;https://www.apple.com/voiceover/info/guide/_1121.html&quot;&gt;VoiceOver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ubuntu users have &lt;a rel=&quot;external&quot; href=&quot;https://docs.oracle.com/cd/E36784_01/html/E36850/ats-2.html#scrolltoc&quot;&gt;Orca&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For chrome, there is the &lt;a rel=&quot;external&quot; href=&quot;https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn/related?hl=en&quot;&gt;Screen Reader extension&lt;/a&gt;, which is like a lite version of the above. If you use this extension, you can listen to the screen reader announce the interactive elements by tabbing. If you want the screen reader to read non focusable content, you will have to select it by clicking with your mouse.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If for some reason you can&#39;t use a screen reader, you still have the accessibility tree view: it is what screen readers read when navigating a site. You can see it in the Chrome dev tools, by clicking on the button with an icon of a person, in the &lt;code&gt;Elements&lt;/code&gt; tab.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/web-accessibility-guidelines-2022-update/a11y-tree-view.webp&quot; alt=&quot;Accessibility tree in Google Chrome dev tools&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In Firefox, the a11y tree is displayed in the &quot;accessibility&quot; tab of the dev tools.&lt;/p&gt;
&lt;p&gt;I won&#39;t go into how screen readers work. Get familiar with one of them, and use it to test your page for a11y.&lt;/p&gt;
&lt;p&gt;To give you an idea, the screen reader reads:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;role or type&lt;/strong&gt; of the element. For example, a link, an article...&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;accessible name&lt;/strong&gt; of the element. For a link, it would normally be its text content&lt;/li&gt;
&lt;li&gt;The element&#39;s &lt;strong&gt;value&lt;/strong&gt;, if there is any. For example, the current value in a text input&lt;/li&gt;
&lt;li&gt;The element&#39;s &lt;strong&gt;state&lt;/strong&gt;, if there is any. For example, the checked state in a checkbox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Screen reader users usually navigate a page using headings and landmarks, so review those. Use the &lt;code&gt;h1-h6&lt;/code&gt; hierarchy and landmarks correctly. Use &lt;a rel=&quot;external&quot; href=&quot;https://www.scottohara.me/blog/2018/03/03/landmarks.html&quot;&gt;accessible landmarks&lt;/a&gt; as a reference.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All images should have a descriptive &lt;code&gt;alt&lt;/code&gt; text, which describes the image to someone who cannot see it. However, if they are only decorational, leave it empty: &lt;code&gt;alt=&quot;&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All controls should have a label.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All custom controls should have a role and any required ARIA attributes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As screen reader users navigate forms control to control, place any instructions inside the label of the associated control form.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The keyboard navigation order should make sense.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All visible content of the page should be accessible to screen readers (except anything that is only visually aesthetic).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Handle content depending on its required visibility:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If content is hidden &lt;strong&gt;both&lt;/strong&gt; from screen readers and visually, use &lt;code&gt;display: none&lt;/code&gt; or &lt;code&gt;visibility: hidden&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If content is visually present, but hidden from screen readers, use &lt;code&gt;aria-hidden=&quot;true&quot;&lt;/code&gt; and possibly &lt;code&gt;tabindex=&quot;-1&quot;&lt;/code&gt;, to prevent keyboard focus.&lt;/li&gt;
&lt;li&gt;If content is visually hidden, but accessible to screen readers, use css similar to this (copied from &lt;a rel=&quot;external&quot; href=&quot;https://tailwindcss.com/docs/screen-readers&quot;&gt;tailwind docs&lt;/a&gt;):&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.sr-only {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  position: absolute;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  width: 1px;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  height: 1px;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  padding: 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  margin: -1px;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  overflow: hidden;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  clip: rect(0, 0, 0, 0);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  white-space: nowrap;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  border-width: 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;custom-widgets&quot;&gt;Custom widgets&lt;/h2&gt;
&lt;p&gt;Semantic html (&lt;code&gt;article&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;button&lt;/code&gt;, etc. as opposed to &lt;code&gt;div&lt;/code&gt; and &lt;code&gt;span&lt;/code&gt;) has by and large built-in accessibility, so try to use native html as much as possible. However, if you&#39;re building a custom widget without semantic html, you might need to take care of accessibility entirely on your own. Research online how to make the widget a11y, according to the &lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/TR/wai-aria/&quot;&gt;W3C specification&lt;/a&gt; and using the appropriate &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA&quot;&gt;ARIA attributes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.webdevluis.com/blog/web-accessibility-guidelines-2022-update/#automatic-testing&quot;&gt;Automated tools&lt;/a&gt; might not detect your custom widget, so you are entirely on your own.&lt;/p&gt;
&lt;p&gt;Things that you will need to consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#roles&quot;&gt;role&lt;/a&gt; does the widget fulfill?&lt;/li&gt;
&lt;li&gt;What &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#states_and_properties&quot;&gt;states and properties&lt;/a&gt; does the widget need?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finding a similar widget in &lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/&quot;&gt;WAI ARIA patterns&lt;/a&gt; and using it as reference might be a good start. Then follow the checklists and manual steps described in this post.&lt;/p&gt;
&lt;h2 id=&quot;a11y-libraries&quot;&gt;A11y libraries&lt;/h2&gt;
&lt;p&gt;If you have a site built on just html and css, the library &lt;a rel=&quot;external&quot; href=&quot;https://github.com/component-driven/cypress-axe&quot;&gt;cypress-axe&lt;/a&gt; lets you run a11y checks in cypress tests. Take a look at this repo to play with it yourself: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/luismartinezs/axe-a11y-html&quot;&gt;https://github.com/luismartinezs/axe-a11y-html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you use a frontend library, such as React or Vue, you can incorporate a11y libraries in your development process. For example, for React you can run automated a11y tests in cypress, or identify a11y issues with Storybook. Check out this repo to see it all working together with React: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/luismartinezs/react-a11y-axe&quot;&gt;https://github.com/luismartinezs/react-a11y-axe&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-next&quot;&gt;What next?&lt;/h2&gt;
&lt;p&gt;There is much more to a11y than this post can encapsulate. You might start by reading about &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element&quot;&gt;all html elements&lt;/a&gt;, to see what is actually possible in terms of native html. Then, take a look at &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes&quot;&gt;all ARIA attributes&lt;/a&gt; as well as &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles&quot;&gt;all ARIA roles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check also the list of resources at the bottom, in particular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read the recommended readings&lt;/li&gt;
&lt;li&gt;Take the free &lt;a rel=&quot;external&quot; href=&quot;https://www.udacity.com/course/web-accessibility--ud891&quot;&gt;Udacity course on accessibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;h3 id=&quot;recommended-readings&quot;&gt;Recommended readings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://a11y.coffee&quot;&gt;https://a11y.coffee&lt;/a&gt; Quick intro to the concept of a11y&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://web.dev/how-to-review&quot;&gt;https://web.dev/how-to-review&lt;/a&gt; Great summary about a11y testing&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.paciellogroup.com/blog/2017/04/what-is-an-accessible-name&quot;&gt;https://www.paciellogroup.com/blog/2017/04/what-is-an-accessible-name&lt;/a&gt; What is an accessible name?&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/ARIA/apg/practices/read-me-first&quot;&gt;https://www.w3.org/WAI/ARIA/apg/practices/read-me-first&lt;/a&gt; No ARIA is better than bad ARIA&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/formvalidation&quot;&gt;https://webaim.org/techniques/formvalidation&lt;/a&gt; Accessible form validation&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.scottohara.me/blog/2018/03/03/landmarks.html&quot;&gt;https://www.scottohara.me/blog/2018/03/03/landmarks.html&lt;/a&gt; Get familiar with what landmarks are&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://reactjs.org/docs/accessibility.html&quot;&gt;https://reactjs.org/docs/accessibility.html&lt;/a&gt; Good starting point if you work with React&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;other-resources&quot;&gt;Other resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/TR/wai-aria-practices/&quot;&gt;https://www.w3.org/TR/wai-aria-practices/&lt;/a&gt; Main resource for WAI-ARIA guidelines&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/skipnav&quot;&gt;https://webaim.org/techniques/skipnav&lt;/a&gt; Skip navigation link&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/css/invisiblecontent&quot;&gt;https://webaim.org/techniques/css/invisiblecontent&lt;/a&gt; Invisible content for screen readers&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://css-tricks.com/accessible-svgs&quot;&gt;https://css-tricks.com/accessible-svgs&lt;/a&gt; A11y for svg&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/fundamentals/accessibility-principles&quot;&gt;https://www.w3.org/WAI/fundamentals/accessibility-principles&lt;/a&gt; W3C A11y principles&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/standards-guidelines/aria&quot;&gt;https://www.w3.org/WAI/standards-guidelines/aria&lt;/a&gt; WAI-ARIA overview&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTML/Element&lt;/a&gt; Reference for HTML elements&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/tutorials/forms/labels&quot;&gt;https://www.w3.org/WAI/tutorials/forms/labels&lt;/a&gt; Different ways to label form inputs&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/forms/controls&quot;&gt;https://webaim.org/techniques/forms/controls&lt;/a&gt; A11y for form controls&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/tutorials/forms/notifications&quot;&gt;https://www.w3.org/WAI/tutorials/forms/notifications&lt;/a&gt; A11y user feedback and notifications&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/keyboard&quot;&gt;https://webaim.org/techniques/keyboard&lt;/a&gt; Keyboard navigation&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets&lt;/a&gt; Making custom widgets a11y&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/TR/wai-aria/#roles&quot;&gt;https://www.w3.org/TR/wai-aria/#roles&lt;/a&gt; List of WAI-ARIA roles&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/TR/wai-aria/#states_and_properties&quot;&gt;https://www.w3.org/TR/wai-aria/#states_and_properties&lt;/a&gt; List of WAI-ARIA states and properties&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns&quot;&gt;https://www.w3.org/WAI/ARIA/apg/patterns&lt;/a&gt; Authoring practices for widgets&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://a11yproject.com/posts/what-is-color-contrast&quot;&gt;https://a11yproject.com/posts/what-is-color-contrast&lt;/a&gt; What is color contrast?&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://inclusive-components.design&quot;&gt;https://inclusive-components.design&lt;/a&gt; In-depth building of accessible components&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.tpgi.com/the-browser-accessibility-tree/&quot;&gt;https://www.tpgi.com/the-browser-accessibility-tree/&lt;/a&gt; About the web browser accessibility tree&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.adrianbolonio.com/blog/accessibility-with-storybook&quot;&gt;https://www.adrianbolonio.com/blog/accessibility-with-storybook&lt;/a&gt; A11y and Storybook&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes&lt;/a&gt; List of all ARIA attributes&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles&lt;/a&gt; List of all ARIA roles&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions&lt;/a&gt; ARIA live regions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tools-and-extensions&quot;&gt;Tools and extensions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://wave.webaim.org/&quot;&gt;https://wave.webaim.org/&lt;/a&gt; Wave a11y evaluation tool&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;https://developers.google.com/web/tools/lighthouse&lt;/a&gt; Chrome lighthouse&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://accessibilityinsights.io/&quot;&gt;https://accessibilityinsights.io/&lt;/a&gt; microsoft accessibility insights, can be also used to test a11y for desktop and mobile apps&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn&quot;&gt;Screen reader chrome extension&lt;/a&gt; (You can&#39;t &quot;disable&quot; it. Just remove it when you don&#39;t use it)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=deque-systems.vscode-axe-linter&quot;&gt;https://marketplace.visualstudio.com/items?itemName=deque-systems.vscode-axe-linter&lt;/a&gt; Axe VSCode extension&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/dequelabs/axe-core-npm/tree/develop/packages/cli&quot;&gt;https://github.com/dequelabs/axe-core-npm/tree/develop/packages/cli&lt;/a&gt; Axe-cli&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/dequelabs/axe-core&quot;&gt;https://github.com/dequelabs/axe-core&lt;/a&gt; Axe-core library for continuous integration environment&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/component-driven/cypress-axe&quot;&gt;https://github.com/component-driven/cypress-axe&lt;/a&gt; Axe library to use with Cypress testing library&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/dequelabs/axe-core-npm/blob/develop/packages/react/README.md&quot;&gt;https://github.com/dequelabs/axe-core-npm/blob/develop/packages/react/README.md&lt;/a&gt; Test a React app with axe-core&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://colorable.jxnblk.com&quot;&gt;https://colorable.jxnblk.com&lt;/a&gt; Find colors that have enough contrast&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/jsx-eslint/eslint-plugin-jsx-a11y&quot;&gt;https://github.com/jsx-eslint/eslint-plugin-jsx-a11y&lt;/a&gt; ESLint plugin to detect a11y issues in your code&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;screen-readers&quot;&gt;Screen readers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nvaccess.org/&quot;&gt;NVDA&lt;/a&gt; (Windows)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.freedomscientific.com/Products/software/JAWS/&quot;&gt;JAWS&lt;/a&gt; (Windows)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.apple.com/voiceover/info/guide/_1121.html&quot;&gt;VoiceOver&lt;/a&gt; (Mac)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://docs.oracle.com/cd/E36784_01/html/E36850/ats-2.html#scrolltoc&quot;&gt;Orca&lt;/a&gt; (Ubuntu)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn/related?hl=en&quot;&gt;Screen Reader extension&lt;/a&gt; for Chrome&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;checklists&quot;&gt;Checklists&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.a11yproject.com/checklist&quot;&gt;https://www.a11yproject.com/checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/standards/wcag/checklist&quot;&gt;https://webaim.org/standards/wcag/checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.wuhcag.com/wcag-checklist&quot;&gt;https://www.wuhcag.com/wcag-checklist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;courses&quot;&gt;Courses&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.udacity.com/course/web-accessibility--ud891&quot;&gt;https://www.udacity.com/course/web-accessibility--ud891&lt;/a&gt; (free)&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to use different firebase hosting environments with React and Vite</title>
        <published>2022-08-04T00:00:00+00:00</published>
        <updated>2022-08-04T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/firebase-hosting-environments-react-vite/"/>
        <id>https://www.webdevluis.com/blog/firebase-hosting-environments-react-vite/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/firebase-hosting-environments-react-vite/">&lt;p&gt;Imagine you&#39;re building a &lt;strong&gt;React&lt;/strong&gt; app with &lt;strong&gt;Vite&lt;/strong&gt; and powered by &lt;strong&gt;Firebase&lt;/strong&gt;, and you use &lt;strong&gt;Firebase hosting&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For local development you can emulate Firebase, but you can also have a LIVE Firebase project for development.&lt;/p&gt;
&lt;p&gt;For this, you need to create a new Firebase project, you need to build the app locally using the correct environment variables, and lastly you deploy it.&lt;/p&gt;
&lt;p&gt;This is how you make it work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new Firebase project for development from the &lt;a rel=&quot;external&quot; href=&quot;https://console.firebase.google.com/&quot;&gt;Firebase Console&lt;/a&gt;. You might call it &lt;em&gt;my-app-development&lt;/em&gt;, &lt;em&gt;my-app-staging&lt;/em&gt;, or whatever you want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inside the Firebase project, create a new web app and take note of the config values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the local project in your computer, add the new Firebase project, running this in the root of the project: &lt;code&gt;firebase use --add&lt;/code&gt;, select the new project you just created and give it any alias you want (&lt;code&gt;dev&lt;/code&gt; or whatever)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file named &lt;code&gt;.env.[mode].local&lt;/code&gt;, where &lt;code&gt;[mode]&lt;/code&gt; is the name of the new environment. For example: &lt;code&gt;.env.development.local&lt;/code&gt;, you can read more about &lt;a rel=&quot;external&quot; href=&quot;https://vitejs.dev/guide/env-and-mode.html#env-files&quot;&gt;how Vite handles environment variables&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the config values from the Firebase app (and any other env variables you need) to the new env file&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a build script to your &lt;code&gt;package.json&lt;/code&gt; file looking like this: &lt;code&gt;&quot;build-dev: &quot;tsc &amp;amp;&amp;amp; vite build --mode development&quot;&lt;/code&gt;. If your environment is named different than development (e.g. staging), then use it instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the build script you just created: &lt;code&gt;pnpm build-dev&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy to Firebase: &lt;code&gt;firebase deploy -P dev&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You could also have a deploy script: &lt;code&gt;&quot;fb:deploy-dev&quot;: &quot;pnpm build-dev &amp;amp;&amp;amp; firebase deploy -P dev&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;If you use the &lt;code&gt;firebase deploy&lt;/code&gt; command, remember to &lt;code&gt;build&lt;/code&gt; your project locally first! I spent 2 hours once trying to understand with my LIVE project wouldn&#39;t update after a deploy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Congratulations, you did it!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Fix import.meta.env Unexpected token error in Vite</title>
        <published>2022-08-01T00:00:00+00:00</published>
        <updated>2022-08-01T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/fix-import-meta-env-unexpected-token-error-in-vite/"/>
        <id>https://www.webdevluis.com/blog/fix-import-meta-env-unexpected-token-error-in-vite/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/fix-import-meta-env-unexpected-token-error-in-vite/">&lt;p&gt;If you use Vite with environment variables which sometimes are undefined depending on the build and environment, you might be getting an error like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; during&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; build:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Error:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Unexpected&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; token&lt;/span&gt;&lt;span&gt; (Note&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; that&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; you&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; need&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; plugins&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; import&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; files&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; that&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; are&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; not&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; JavaScript&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the cause is an environment variable that is not defined.&lt;/p&gt;
&lt;p&gt;The code that triggered such error in my case was:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; useEmulator&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;VITE_FIREBASE_EMULATOR&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; string&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix an error like this, change the above code into:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; useEmulator&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; boolean&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  return&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;VITE_FIREBASE_EMULATOR&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; undefined&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;VITE_FIREBASE_EMULATOR&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Fix: Cannot find module &#39;MyComponent.mdx&#39; or its corresponding type declarations in React</title>
        <published>2022-08-01T00:00:00+00:00</published>
        <updated>2022-08-01T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/fix-typescript-cannot-find-module-declaration-error-mdx-react/"/>
        <id>https://www.webdevluis.com/blog/fix-typescript-cannot-find-module-declaration-error-mdx-react/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/fix-typescript-cannot-find-module-declaration-error-mdx-react/">&lt;p&gt;If you are trying to import &lt;code&gt;.mdx&lt;/code&gt; files with Typescript in your React project, you might be getting an error looking like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Fix:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Cannot&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; find&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; module&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;MyComponent.mdx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; or&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; its&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; corresponding&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; declarations&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; React&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s how to fix it:&lt;/p&gt;
&lt;p&gt;First, install &lt;code&gt;@types/mdx&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;pnpm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; @types/mdx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, inside your project, add a types folder with a &lt;code&gt;mdx.d.ts&lt;/code&gt; file (e.g. &lt;code&gt;./src/types/mdx.d.ts&lt;/code&gt;), and paste this in the file:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;declare&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; module&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;*.mdx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  let&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; MDXComponent&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; any&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; JSX&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Element&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  export&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; default&lt;/span&gt;&lt;span&gt; MDXComponent&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now open your &lt;code&gt;tsconfig.json&lt;/code&gt; file, and do these two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;&quot;mdx&quot;&lt;/code&gt; inside &lt;code&gt;&quot;types&quot;&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;compilerOptions&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;react/next&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;react-dom/next&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;mdx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;  .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Make sure the path of the &lt;code&gt;mdx.d.ts&lt;/code&gt; file is included. If you added it inside the &lt;code&gt;src&lt;/code&gt; folder, this should be enough:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;  .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;  .&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-7 z-l-i z-d-11 z-d-i&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should work now. Good luck!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to use VSCode to make Anki cards easily</title>
        <published>2022-04-30T00:00:00+00:00</published>
        <updated>2022-04-30T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/anki-vscode/"/>
        <id>https://www.webdevluis.com/blog/anki-vscode/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/anki-vscode/">&lt;h2 id=&quot;why-a-new-post-about-anki&quot;&gt;Why a new post about Anki?&lt;/h2&gt;
&lt;p&gt;For the past years I&#39;ve used Anki to learn programming effectively.&lt;/p&gt;
&lt;p&gt;Since I became a web developer, I&#39;ve added a few tools to my arsenal. So I thought about leveraging them to ease (and improve!) the creation of Anki cards.&lt;/p&gt;
&lt;p&gt;Can I create new cards and add them to my deck faster? Can I make the code in the cards look nicer? Can I create the cards with my favorite text editor, VSCode? Can I ...?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/jotaro-yes.webp&quot; alt=&quot;The answer is yes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The answer is yes, yes and yes.&lt;/p&gt;
&lt;p&gt;Let me show you what I found.&lt;/p&gt;
&lt;h2 id=&quot;how-will-it-work&quot;&gt;How will it work?&lt;/h2&gt;
&lt;p&gt;We&#39;re going to connect our favorite text editor (&lt;em&gt;cough&lt;/em&gt;) VSCode (&lt;em&gt;cough&lt;/em&gt;) to Anki, as we will use it to create new cards. Why? Because with VSCode we can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Auto-format code&lt;/li&gt;
&lt;li&gt;Use snippets to create cards more quickly&lt;/li&gt;
&lt;li&gt;Use extensions that do some of the work for us&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will write our cards in a markdown file in VSCode, then we will run a command that adds the cards to our Anki deck. Simple, yet powerful.&lt;/p&gt;
&lt;h2 id=&quot;install-the-stuff-you-need&quot;&gt;Install the stuff you need&lt;/h2&gt;
&lt;h3 id=&quot;install-anki-desktop-app-and-setup-an-account&quot;&gt;Install Anki desktop app (and setup an account)&lt;/h3&gt;
&lt;p&gt;First of all, if you didn&#39;t use Anki before, create an account here: &lt;a rel=&quot;external&quot; href=&quot;https://Ankiweb.net&quot;&gt;https://Ankiweb.net&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You will use the same credentials to login in the Anki desktop app, and in the mobile app if you use it. The mobile app is optional but recommended since it&#39;s much more comfortable to study Anki on the phone.&lt;/p&gt;
&lt;p&gt;Next, install the Anki desktop app: Go here &lt;a rel=&quot;external&quot; href=&quot;https://apps.Ankiweb.net&quot;&gt;https://apps.Ankiweb.net&lt;/a&gt; and click on the download button, then install the app for your operative system.&lt;/p&gt;
&lt;h3 id=&quot;in-the-anki-desktop-app-install-the-addon-ankiconnect&quot;&gt;In the Anki desktop app, install the addon AnkiConnect&lt;/h3&gt;
&lt;p&gt;The add-on AnkiConnect will &lt;em&gt;automagically&lt;/em&gt; transfer Anki cards from VSCode to Anki.&lt;/p&gt;
&lt;p&gt;Open the Anki app, and go to &lt;code&gt;Tools &amp;gt; Add-ons&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Click on the Get Add-ons button, paste the code &lt;code&gt;2055492159&lt;/code&gt; in the input and click ok. You should see AnkiConnect added to the list of add-ons&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/install-ankiconnect-addon.webp&quot; alt=&quot;Install AnkiConnect add-on&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;If the code is wrong for whatever reason, you can get it from here: &lt;a rel=&quot;external&quot; href=&quot;https://Ankiweb.net/shared/info/2055492159&quot;&gt;https://Ankiweb.net/shared/info/2055492159&lt;/a&gt;, or googling for AnkiConnect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;install-vscode&quot;&gt;Install VSCode&lt;/h3&gt;
&lt;p&gt;If you don&#39;t have VSCode installed, you will need to do so. Go here and do the thing &lt;a rel=&quot;external&quot; href=&quot;https://code.visualstudio.com&quot;&gt;https://code.visualstudio.com&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;download-my-repo&quot;&gt;Download my repo&lt;/h2&gt;
&lt;p&gt;I&#39;ve created a repo with everything configured so that you can just go and create cards in a breeze.&lt;/p&gt;
&lt;p&gt;Download it from here: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/luismartinezs/Anki&quot;&gt;https://github.com/luismartinezs/Anki&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(click on the green &quot;code&quot; button)&lt;/p&gt;
&lt;p&gt;If you use &lt;code&gt;git&lt;/code&gt;, clone the project. If you don&#39;t (why not?), just download the zip file into your computer and unzip it. Then, open the project folder with VSCode.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;When you open the project with VSCode, you might be prompted to install the recommended extensions. Accept it! If you don&#39;t receive that prompt, make sure you have the extensions that you need installed. The extensions needed are listed in &lt;code&gt;.vscode &amp;gt; extensions.json&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;create-new-cards&quot;&gt;Create new cards&lt;/h2&gt;
&lt;p&gt;In the repo there is a &lt;code&gt;README.md&lt;/code&gt; file with a detailed explanation, you can read it but I&#39;ll give you here the short version so you get into creating cards quickly.&lt;/p&gt;
&lt;p&gt;Follow me here.&lt;/p&gt;
&lt;p&gt;Open the file &lt;code&gt;Anki.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Type a &lt;code&gt;#&lt;/code&gt; followed by the name of the Anki deck where you want to add your cards, e.g. &lt;code&gt;# My Dev Deck&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/new-deck-title.webp&quot; alt=&quot;New Anki deck title&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;add-cards-with-no-code&quot;&gt;Add cards with no code&lt;/h3&gt;
&lt;p&gt;To add a card, add two empty lines, and in the second one just type &lt;code&gt;anki&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You should see two options.&lt;/p&gt;
&lt;p&gt;Choose &lt;code&gt;anki-new-card&lt;/code&gt; to add a card with no code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/new-card-template.webp&quot; alt=&quot;New card template&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The cursor will automatically position itself in the &quot;front&quot; so just write down the front part of the card (e.g. What is the programming language of the web?).&lt;/p&gt;
&lt;p&gt;If you hit the &lt;code&gt;tab&lt;/code&gt; key, the cursor will move to the &quot;back&quot;. Write it down (e.g. JavaScript).&lt;/p&gt;
&lt;p&gt;Then the &lt;code&gt;tab&lt;/code&gt; key again will highlight the tag. Fill it up (e.g. js)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/sample-card.webp&quot; alt=&quot;Sample card&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can add multiple tags like so: &lt;code&gt;[#tag-1]() [#tag-2]()&lt;/code&gt;. Mind the &lt;code&gt;#&lt;/code&gt; symbol before the tag.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;Leave a space in between tags! Otherwise, you&#39;ll get one single ugly-looking tag like &lt;code&gt;tag-1()[#tag-2]()&lt;/code&gt;...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;add-cards-with-code-blocks&quot;&gt;Add cards with code blocks&lt;/h3&gt;
&lt;p&gt;If you want to have code in your card, add two more empty lines, type &lt;code&gt;anki&lt;/code&gt; and select &lt;code&gt;anki-new-card-wth-code&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You will see this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/anki-card-with-code.webp&quot; alt=&quot;Anki card with code&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The triple ` marks the start and end of a code block. In Anki, this will be replaced by a block of code. Syntax highlighting --fancy colors that make the code readable, also works!&lt;/p&gt;
&lt;p&gt;Type the short label for the language, e.g. &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;css&lt;/code&gt;, &lt;code&gt;js&lt;/code&gt;, &lt;code&gt;ts&lt;/code&gt;, etc. That will act as a tag too.&lt;/p&gt;
&lt;p&gt;Hit tab to highlight the front, then the back. You got it. Or feel free to edit the text normally.&lt;/p&gt;
&lt;p&gt;After creating your cards with and without code, you could end up with something like this, for example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/anki-cards-full-example-1.webp&quot; alt=&quot;Anki cards full example 1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki-vscode/anki-cards-full-example-2.webp&quot; alt=&quot;Anki cards full example 2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Obviously, it is possible to have a code block only in the front or only in the back.&lt;/p&gt;
&lt;h2 id=&quot;auto-format-the-cards&quot;&gt;Auto-format the cards&lt;/h2&gt;
&lt;p&gt;Go to &lt;code&gt;View &amp;gt; Command Palette...&lt;/code&gt; and type &quot;format&quot;, then select &lt;code&gt;Format Document&lt;/code&gt;. Or use the shortcut &lt;code&gt;Ctrl+Shift+I&lt;/code&gt; (Linux, Windows). That should minimize the length of each line so that it fits the card on mobile.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;Leave an empty line in between each part of the card, so that auto-formatting doesn&#39;t think they belong to the same paragraph and wrap them together.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;add-the-new-cards-to-your-anki-deck&quot;&gt;Add the new cards to your Anki deck&lt;/h2&gt;
&lt;p&gt;Almost done! You have your &lt;code&gt;Anki.md&lt;/code&gt; file with the cards you want to add to your deck. In the first line, after the &lt;code&gt;#&lt;/code&gt;, you wrote the name of your deck (if it doesn&#39;t exist, Anki will create it).&lt;/p&gt;
&lt;p&gt;Open the Anki desktop app, if it isn&#39;t open already.&lt;/p&gt;
&lt;p&gt;Go back to VSCode and open the command palette (&lt;code&gt;ctrl+shift+P&lt;/code&gt;), or &lt;code&gt;View &amp;gt; Command Palette...&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then, type &lt;code&gt;Anki&lt;/code&gt;, and select &lt;code&gt;Send to own Deck&lt;/code&gt;. The new cards should show up in your deck, and the code blocks should look nicely!&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;If you get an error like this: &lt;code&gt;connect ECONNREFUSED 127.0.0.1:8765&lt;/code&gt;, try to restart the Anki app&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Happy studying!&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Read the &lt;code&gt;README.md&lt;/code&gt; file in my repo &lt;a rel=&quot;external&quot; href=&quot;https://github.com/luismartinezs/anki&quot;&gt;https://github.com/luismartinezs/anki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Read the documentation of the Anki VSCode extension &lt;a rel=&quot;external&quot; href=&quot;https://github.com/jasonwilliams/Anki&quot;&gt;https://github.com/jasonwilliams/Anki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Web Accessibility Guidelines</title>
        <published>2022-02-20T00:00:00+00:00</published>
        <updated>2022-02-20T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/a11y/"/>
        <id>https://www.webdevluis.com/blog/a11y/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/a11y/">&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;Make sure to check my blogpost &lt;a href=&quot;https://www.webdevluis.com/blog/web-accessibility-guidelines-2022-update/&quot;&gt;Web Accessibility Guidelines 2022 Update&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ideally, applying a11y (accessibility) guidelines should be a part of the development process, rather than an afterthought.&lt;/p&gt;
&lt;p&gt;I propose the following steps:&lt;/p&gt;
&lt;h2 id=&quot;steps-to-perform-an-a11y-audit&quot;&gt;Steps to perform an a11y audit&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1/ Automatic testing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are a handful of these types of tools. All of them run an audit check and provide a list of things to change. These tools are described here in more detail: &lt;a rel=&quot;external&quot; href=&quot;https://a11y.coffee/start-testing&quot;&gt;https://a11y.coffee/start-testing&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;WAVE Web Accessibility Evaluation Tool&lt;/strong&gt;: in my opinion it is the tool that gives a quickest and most visual overview. I’d start here. &lt;a rel=&quot;external&quot; href=&quot;https://wave.webaim.org/&quot;&gt;https://wave.webaim.org/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Additional tools that provide similar information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Firefox a11y audit&lt;/strong&gt; in Firefox developer tools (recommended)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In a11y dev tools tab, select: “All issues”, “show tabbing order”, and color deficiency simulations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chrome&#39;s lighthouse audit in chrome developer tools&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.deque.com/axe/&quot;&gt;Axe browser extension&lt;/a&gt; https://www.deque.com/axe/&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://accessibilityinsights.io/&quot;&gt;Accessibility insights&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In my opinion, running one of the above should be enough to cover this point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2/ A11y checklist&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use an a11y checklist to discover issues that automated tools might have overlooked: &lt;a rel=&quot;external&quot; href=&quot;https://www.a11yproject.com/checklist&quot;&gt;https://www.a11yproject.com/checklist&lt;/a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/standards/wcag/checklist&quot;&gt;https://webaim.org/standards/wcag/checklist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;3/ Manual testing&lt;/p&gt;
&lt;p&gt;Cross-check the following use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interactive elements should follow ARIA guidelines: &lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/TR/wai-aria-practices/&quot;&gt;https://www.w3.org/TR/wai-aria-practices/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Each page should be keyboard navigable&lt;/li&gt;
&lt;li&gt;Each page should be screen reader navigable (navigate with closed eyes using keyboard and a screen reader)&lt;/li&gt;
&lt;li&gt;Focus styles should be apparent&lt;/li&gt;
&lt;li&gt;Touch points should have appropriate dimensions on touch device sizes: 48x48pt minimum for touchable areas, and 32pt of space in between touchable elements&lt;/li&gt;
&lt;li&gt;View site in grayscale, make sure it is readable (use nocoffee from the resources section)&lt;/li&gt;
&lt;li&gt;View site in high contrast mode, make sure it is readable (use nocoffee from the resources section)&lt;/li&gt;
&lt;li&gt;View site at 200% magnification&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;h3 id=&quot;general-resources&quot;&gt;General resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.w3.org/TR/wai-aria-practices/&quot;&gt;https://www.w3.org/TR/wai-aria-practices/&lt;/a&gt; Main resource for WAI-ARIA guidelines&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://a11y.coffee&quot;&gt;https://a11y.coffee&lt;/a&gt; Quick intro to the concept of a11y&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/skipnav&quot;&gt;https://webaim.org/techniques/skipnav&lt;/a&gt; Skip navigation link&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/techniques/css/invisiblecontent&quot;&gt;https://webaim.org/techniques/css/invisiblecontent&lt;/a&gt; Invisible content for screen readers&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://css-tricks.com/accessible-svgs&quot;&gt;https://css-tricks.com/accessible-svgs&lt;/a&gt; A11y for svg&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tools-and-extensions&quot;&gt;Tools and extensions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://wave.webaim.org/&quot;&gt;https://wave.webaim.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Chrome lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.deque.com/axe/&quot;&gt;AXE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://accessibilityinsights.io/&quot;&gt;Microsoft accessibility insights&lt;/a&gt;, can be also used to test a11y for desktop and mobile apps&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://accessgarage.wordpress.com/2013/02/09/458/&quot;&gt;https://accessgarage.wordpress.com/2013/02/09/458/&lt;/a&gt; (download chrome extension nocoffee and use it to simulate different levels of visual impairment)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn&quot;&gt;https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn&lt;/a&gt; Screen reader chrome extension (annoying, will force you to reinstall / uninstall each time to keep your sanity)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=MaxvanderSchee.web-accessibility&quot;&gt;https://marketplace.visualstudio.com/items?itemName=MaxvanderSchee.web-accessibility&lt;/a&gt; VSCode extension&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;checklists&quot;&gt;Checklists&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.a11yproject.com/checklist&quot;&gt;https://www.a11yproject.com/checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webaim.org/standards/wcag/checklist&quot;&gt;https://webaim.org/standards/wcag/checklist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;courses&quot;&gt;Courses&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.udacity.com/course/web-accessibility--ud891&quot;&gt;https://www.udacity.com/course/web-accessibility--ud891&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&quot;markdown-alert-warning&quot;&gt;
&lt;p&gt;The NoCoffee extension for Chrome seems gone. For high contrast mode, you can use this &lt;a rel=&quot;external&quot; href=&quot;https://chrome.google.com/webstore/detail/high-contrast/djcfdncoelnlbldjfhinnjlhdjlikmph&quot;&gt;High Contrast extension for Chrome&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>15 lessons I learned from playing Starcraft 2</title>
        <published>2021-09-05T00:00:00+00:00</published>
        <updated>2021-09-05T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/starcraft-lessons/"/>
        <id>https://www.webdevluis.com/blog/starcraft-lessons/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/starcraft-lessons/">&lt;p&gt;I love &lt;strong&gt;Starcraft&lt;/strong&gt; since 1999.&lt;/p&gt;
&lt;p&gt;Starcraft is a real-time strategy game where you build an economy and an army to beat your opponent.&lt;/p&gt;
&lt;p&gt;During play, you juggle your focus between developing your economy (known as &quot;macro&quot;) and micro-managing your army (known as &quot;micro&quot;). And &lt;a rel=&quot;external&quot; href=&quot;https://youtu.be/bexWuHmV32A?t=669&quot;&gt;it&#39;s not easy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Learning to play Starcraft means learning a &lt;strong&gt;very complex skill&lt;/strong&gt;. So complex, that it feels almost impossible to do well as a beginner.&lt;/p&gt;
&lt;p&gt;Web development is also a &lt;strong&gt;very complex skill&lt;/strong&gt;. Lots of moving pieces.&lt;/p&gt;
&lt;p&gt;So, as a fan of Starcraft, I (naturally) thought:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How can I generalize the complex process of learning to play Starcraft to other skills?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In what is perhaps a fun thought experiment more than anything, I came up with the following.&lt;/p&gt;
&lt;p&gt;Before that, a few disclaimers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;When I write Starcraft I&#39;m actually referring to Stracraft 2. I didn&#39;t play the first game for ages. But for the purposes of this article, it doesn&#39;t make any difference.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;By writing this I don&#39;t mean to suggest I&#39;m any good at playing this game. I&#39;m atrocious at it. But I thought some concepts of the game are interesting enough to write about.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Grab your nachos and enjoy.&lt;/p&gt;
&lt;h2 id=&quot;1-you-don-t-learn-to-play-the-game-you-learn-a-specific-way-to-play-the-game&quot;&gt;1. You don&#39;t learn to play the game, you learn a &lt;em&gt;specific way&lt;/em&gt; to play the game&lt;/h2&gt;
&lt;p&gt;In Starcraft there are 3 races you can play, and each has its own strategies.&lt;/p&gt;
&lt;p&gt;Commonly, you don&#39;t learn to execute all possible strategies for all 3 races flawlessly. Instead, you pick one race. Then, you get very good at a specific way to play it.&lt;/p&gt;
&lt;p&gt;Likewise, a developer doesn&#39;t try to learn all technologies, all web stacks, how to make all types of websites and applications... Devs learn a specific set of technologies, learn to develop a specific type of applications in a specific way and with a specific structure.&lt;/p&gt;
&lt;p&gt;If there&#39;s a need to change and adapt, it&#39;s always possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal of the game is not to learn everything&lt;/strong&gt;. It is to win more over time.&lt;/p&gt;
&lt;p&gt;Likewise, the goal of learning development is not to know everything, it is to produce high quality work efficiently.&lt;/p&gt;
&lt;p&gt;That doesn&#39;t mean one should reject aquiring a broad knowledge. &lt;strong&gt;It means that one shouldn&#39;t try to excel with all tech stacks&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;2-redefine-winning-into-something-that-benefits-you&quot;&gt;2. Redefine &quot;winning&quot; into something that benefits you&lt;/h2&gt;
&lt;p&gt;Typically, the goal of any individual game is to defeat the opponent. Turns out, &lt;strong&gt;focusing on winning is not useful to learn&lt;/strong&gt; to play. That won&#39;t help you improve, except by coincidence. You won&#39;t know where to focus your energy.&lt;/p&gt;
&lt;p&gt;Even worse.&lt;/p&gt;
&lt;p&gt;If your goal is winning at something and you fail, you might feel frustrated and stop.&lt;/p&gt;
&lt;p&gt;Having the goal to &lt;em&gt;win the game&lt;/em&gt; is the same as having the goal to &lt;em&gt;play very well&lt;/em&gt;. Too generic and overwhelming. Where to focus? What to do next?&lt;/p&gt;
&lt;p&gt;Winning or losing one game is irrelevant. Increasing your number of wins over time is what matters.&lt;/p&gt;
&lt;p&gt;Any one individual loss doesn&#39;t matter as long as you don&#39;t define success that way.&lt;/p&gt;
&lt;p&gt;So don&#39;t do that.&lt;/p&gt;
&lt;p&gt;Instead, ask: What is the next thing you need / want to improve on?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then, redefine a win as having practiced that one thing with focus, ignoring the rest&lt;/strong&gt;. That is your success, because by doing this, you will get better at that one thing. And by getting better at that one thing, you&#39;ll win more in the long run.&lt;/p&gt;
&lt;h2 id=&quot;3-learning-something-complex-is-within-your-grasp-if-you-take-it-one-bit-at-a-time&quot;&gt;3. Learning something complex is within your grasp, if you take it one bit at a time&lt;/h2&gt;
&lt;p&gt;Faced with a huge task like learning to play Starcraft, or learning web development, might be so daunting that it feels impossible. As if you were in front of an insurmountable mountain.&lt;/p&gt;
&lt;p&gt;It isn&#39;t.&lt;/p&gt;
&lt;p&gt;Understand that no matter how complex a skill might look, it is within your grasp to learn it, if you commit, persevere and are patient.&lt;/p&gt;
&lt;p&gt;Do not try to learn everything at once. &lt;strong&gt;Focus on one tiny aspect of the skill and allow yourself to completely suck at everything except at that thing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Practice that one tiny thing many, many times, until you develop &lt;strong&gt;muscle memory&lt;/strong&gt; (the ability to do that well without even thinking about it). As that happens, you&#39;ll be able to do the thing without speding mental energy on it, so you&#39;ll be able to focus on the next thing.&lt;/p&gt;
&lt;h2 id=&quot;4-review-your-mistakes-to-figure-out-what-you-need-to-improve-but-be-gentle-with-yourself&quot;&gt;4. Review your mistakes to figure out what you need to improve, but be gentle with yourself&lt;/h2&gt;
&lt;p&gt;When things go wrong, when you lose, look back. &lt;strong&gt;No matter how much you want to blame the universe, almost always there&#39;s something you could have done to prevent losing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So be critical of yourself and don&#39;t shy away from your mistakes. Look as closely as you can into them, to figure out what went wrong.&lt;/p&gt;
&lt;p&gt;What went wrong? Why? What did you do well despite losing?&lt;/p&gt;
&lt;p&gt;Learn what you can from it. Figure out what is causing your losses, and use that knowledge to decide what you need to improve next.&lt;/p&gt;
&lt;p&gt;But equally importantly, never beat yourself up for your mistakes. Be gentle with yourself. Mistakes are unavoidable, we&#39;re all human. Mistakes are part of the game.&lt;/p&gt;
&lt;p&gt;Always remember that &lt;strong&gt;learning from a loss is more important than a win&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;5-start-by-fixing-the-first-thing-you-did-wrong&quot;&gt;5. Start by fixing the first thing you did wrong&lt;/h2&gt;
&lt;p&gt;Also known as &lt;em&gt;The Principle of the First Cause&lt;/em&gt; (I probably made up that name).&lt;/p&gt;
&lt;p&gt;When something goes wrong, we have a tendency to blame whatever happened immediately before the disaster.&lt;/p&gt;
&lt;p&gt;Don&#39;t do that.&lt;/p&gt;
&lt;p&gt;Right before a loss, things were probably too messed up already due to lots of mistakes being piled up. It&#39;s likely that at that moment there&#39;s nothing you could have done anymore to prevent your loss.&lt;/p&gt;
&lt;p&gt;Thus, blaming the last thing that happened can be not only pointless, but can also hide the true cause of the loss.&lt;/p&gt;
&lt;p&gt;So look again, look better, look further back.&lt;/p&gt;
&lt;p&gt;Find the first thing you did wrong that led to the failure, and fix &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you fuck up, ask yourself:&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;What started this mess?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;6-good-habits-are-king-don-t-let-anything-interfere-with-them&quot;&gt;6. Good habits are king, don&#39;t let anything interfere with them&lt;/h2&gt;
&lt;p&gt;Starcraft tells you that &lt;em&gt;good habits&lt;/em&gt; are king, and without them you&#39;re hopelessly doomed.&lt;/p&gt;
&lt;p&gt;To understand why, let&#39;s take a look at the concepts of &lt;em&gt;macro&lt;/em&gt; and &lt;em&gt;micro&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In Starcraft, players split their attention between &lt;em&gt;macro&lt;/em&gt; and &lt;em&gt;micro&lt;/em&gt; tasks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Macro = grow your economy and spend your resources&lt;/li&gt;
&lt;li&gt;Micro = control and manage your army units in the battlefield&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Macro&lt;/strong&gt; is equivalent to important tasks performed over and over whose effect compounds and in the long run mark all the difference. Think of them as good habits, like exercising, investing and healthy eating. Neglecting them will have a disastrous result over time and during your entire life.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Micro&lt;/strong&gt; is equivalent to tasks that have an immediate positive effect, but almost always have very little impact in the long run. Think of these as the little random things that creep up in daily life.&lt;/p&gt;
&lt;p&gt;Starcraft teaches you that neglecting &lt;em&gt;macro&lt;/em&gt; to spend more time in &lt;em&gt;micro&lt;/em&gt; is almost always a bad idea, and should be avoided. I think this is true in real life too.&lt;/p&gt;
&lt;p&gt;Don&#39;t let the &lt;em&gt;micro&lt;/em&gt; of life distract you from staying on top of your &lt;em&gt;macro&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Give your important habits priority over the chaotic and random events of life.&lt;/p&gt;
&lt;h2 id=&quot;7-establish-habits-designed-to-move-you-forward-in-life&quot;&gt;7. Establish habits designed to move you forward in life&lt;/h2&gt;
&lt;p&gt;To be on top of their &lt;em&gt;macro&lt;/em&gt;, players perform a series of tasks or &lt;em&gt;rotations&lt;/em&gt; routinely every few seconds. These rotations are known as a &lt;em&gt;macro cycles&lt;/em&gt;. This is essential for a good game.&lt;/p&gt;
&lt;p&gt;Success in life too needs &lt;em&gt;habit rotations&lt;/em&gt; that performed routinely move you forward and lead to good outcomes.&lt;/p&gt;
&lt;p&gt;How you define &lt;em&gt;success in life&lt;/em&gt; is entirely up to you of course.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;macro cycles&lt;/em&gt; of your life would be a set of habits you do repeatedly and that you have purposefully chosen based on your definition of success.&lt;/p&gt;
&lt;p&gt;For example, a very basic &lt;em&gt;habit rotation&lt;/em&gt; would be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exercise every 3 days&lt;/li&gt;
&lt;li&gt;Fast once a week&lt;/li&gt;
&lt;li&gt;Read 1 hour per day&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don&#39;t go aimless on your day to day without knowing what to do. On the contrary, &lt;strong&gt;mindfully pick a&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;habit rotation&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;and prioritize doing it, because its compound effect will have a profound impact over time&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;8-get-rid-of-bad-habits&quot;&gt;8. Get rid of bad habits&lt;/h2&gt;
&lt;p&gt;In Starcraft, &lt;em&gt;F2&lt;/em&gt; selects your entire army. It&#39;s very tempting for beginners to do that because it&#39;s easy. However, it is a bad habit that will get on the way of improving and will have to be unlearned.&lt;/p&gt;
&lt;p&gt;Find the things you do often that get on your way. Things that are a waste of time and energy. It&#39;s very tempting to be lazy and to keep on doing things the bad way just because you started doing them that way (maybe you didn&#39;t know better).&lt;/p&gt;
&lt;p&gt;Are you repeating a manual task over and over? Don&#39;t be lazy, automate it.&lt;/p&gt;
&lt;p&gt;Do you mindlessly watch YouTube or play too many video games, and that leads to excessive distraction? Stop.&lt;/p&gt;
&lt;p&gt;Kill or minimize your bad habits.&lt;/p&gt;
&lt;p&gt;(That time when Starcraft teaches you to stop playing Starcraft, &lt;em&gt;cough&lt;/em&gt;...)&lt;/p&gt;
&lt;h2 id=&quot;9-put-your-habits-or-your-decision-about-them-on-auto-pilot&quot;&gt;9. Put your habits (or your decision about them) on auto-pilot&lt;/h2&gt;
&lt;p&gt;Once you&#39;ve decided:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What habits you&#39;ll do&lt;/li&gt;
&lt;li&gt;How often you will do each&lt;/li&gt;
&lt;li&gt;That you will give them priority&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&#39;s really no decision-making left about that, and it should make things easier. You don&#39;t have to think whether you do them or not. And it doesn&#39;t matter what else you think you have to do, or whether you&#39;re &lt;em&gt;busy&lt;/em&gt; with something else.&lt;/p&gt;
&lt;p&gt;Simply do them.&lt;/p&gt;
&lt;p&gt;You will have more &lt;em&gt;decision-power&lt;/em&gt; to spend on other things.&lt;/p&gt;
&lt;p&gt;I suggest that for each habit you set some kind of timer, so that you can keep track of when you have to do them without having to think.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://play.google.com/store/apps/details?id=com.jee.timer&amp;amp;hl=en&amp;amp;gl=US&quot;&gt;multitimer android app&lt;/a&gt; works for me.&lt;/p&gt;
&lt;h2 id=&quot;10-to-improve-quickly-practice-often&quot;&gt;10. To improve quickly, practice often&lt;/h2&gt;
&lt;p&gt;Practice smart, not hard. Sure. But practice.&lt;/p&gt;
&lt;p&gt;And practice often.&lt;/p&gt;
&lt;p&gt;Starcraft pro-gamers practice 8 to 12 hours a day. There&#39;s probably no need to reach this number of hours when learning programming.&lt;/p&gt;
&lt;p&gt;But the simple truth is that lots of cumulative practice will bring you where you want to be faster. You get good at what you do often. The more you practice, the better you&#39;ll get.&lt;/p&gt;
&lt;p&gt;And frequent practice will help.&lt;/p&gt;
&lt;p&gt;Practicing one skill daily, or even multiple times a day, will skyrocket the speed at which you learn. Don&#39;t let lots of time go between practice sessions.&lt;/p&gt;
&lt;p&gt;And of course, simply &lt;em&gt;thinking&lt;/em&gt; that you want to be a good developer without follow up action won&#39;t bring you anywhere. Make sure you&#39;re not doing that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Massive results require massive action&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;11-accept-that-you-will-suck-at-first&quot;&gt;11. Accept that you will suck at first&lt;/h2&gt;
&lt;p&gt;When you start practicing a new skill, or anything new that you&#39;ve never done before and are not familiar with, you will feel unconfortable. This will happen particularly when learning a complex skill composed of many sub-skills, like Starcraft or programming.&lt;/p&gt;
&lt;p&gt;You&#39;ll feel deep inside that you suck. That you&#39;re hopeless. It will feel like going uphill when you only want to slide down into comfort.&lt;/p&gt;
&lt;p&gt;Allow yourself to suck. Allow yourself to suck badly, and accept that it is perfectly fine for you to suck now. Because eventually you won&#39;t.&lt;/p&gt;
&lt;p&gt;Simply push through that phase.&lt;/p&gt;
&lt;h2 id=&quot;12-accept-that-losing-is-part-of-the-game-concede-when-you-can-t-win&quot;&gt;12. Accept that losing is part of the game, concede when you can&#39;t win&lt;/h2&gt;
&lt;p&gt;Accept defeat as a natural part of life. Try to win, try to face the odds looking for that chance of victory, even if it&#39;s small. Struggle in front of challenges.&lt;/p&gt;
&lt;p&gt;But sometimes there&#39;s a point when defeat is inevitable, regardless of what you do from that point onwards. In that case, any more energy spent in that one battle is a waste.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you realize you can&#39;t win, accept defeat. Give up. Learn from what caused your defeat. And move on to the next chance of winning.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A simple but hard piece advice.&lt;/p&gt;
&lt;h2 id=&quot;13-practice-when-sharp-rest-when-tired&quot;&gt;13. Practice when sharp, rest when tired&lt;/h2&gt;
&lt;p&gt;Without a fresh or rested mind, you can&#39;t perform well. Working to exhaustion, burning the midnight oil, can be damaging to the quality of your work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do mentally demanding tasks only when your mind is sharp&lt;/strong&gt;. If you are tired, it&#39;s much better to take a rest, sleep, take a walk... switch off your brain for a while.&lt;/p&gt;
&lt;h2 id=&quot;14-tweak-your-lifestlye-don-t-settle-for-unoptimal&quot;&gt;14. Tweak your lifestlye, don&#39;t settle for unoptimal&lt;/h2&gt;
&lt;p&gt;Starcraft comes out of the box with default &lt;em&gt;settings&lt;/em&gt;. These are ineffective and can lead to bad habits. So players tweak them to be able to play more effectively.&lt;/p&gt;
&lt;p&gt;They generally copy the settings of a pro-gamer and go from there. Later customizing it more to their playstyle, once they understand better how the game works.&lt;/p&gt;
&lt;p&gt;Society too seems to be &lt;em&gt;configured out of the box&lt;/em&gt; with some defaults. In general terms: go to school, go to university, get a job, and work for 40 years while you pay taxes. Whereas this doesn&#39;t need to be terrible, let&#39;s not overestimate how good it can be, and realize that you don&#39;t need to follow all these things for your life to be good.&lt;/p&gt;
&lt;p&gt;Look for &lt;em&gt;pros&lt;/em&gt;: people who are in a situation that you desire. This could be a successful freelancer, a strong athlete, a charismatic and brave person, someone you admire.&lt;/p&gt;
&lt;p&gt;Copy their habits and mindset, learn from what they do as a first approach. That will probably put you in a better path than before.&lt;/p&gt;
&lt;p&gt;Then don&#39;t get forever stuck in doing what they do. Start doing things your way.&lt;/p&gt;
&lt;h2 id=&quot;15-your-skill-at-doing-something-is-really-your-skill-at-doing-it-in-less-than-ideal-circumstances&quot;&gt;15. Your skill at doing something is really your skill at doing it in less than ideal circumstances&lt;/h2&gt;
&lt;p&gt;The controlled environment where you train and practice your skills and real life are two different things. Real life is unpredictable and wild. Sometimes shit hits the fan, the storm hits.&lt;/p&gt;
&lt;p&gt;How to confront that?&lt;/p&gt;
&lt;p&gt;Level up your skill, not to the bare minimum, not to deal with hypotheticals where everything goes according to plan, but to the level where you can deal with messy real-life scenarios.&lt;/p&gt;
&lt;p&gt;Over-practice. Over-prepare. Be able to perform on the theoretical scenarios blind-folded.&lt;/p&gt;
&lt;p&gt;Then, when shit hits the fan don&#39;t freak out, don&#39;t feel like a failure, because that&#39;s normal. Do your best, then pick up the pieces after the storm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&#39;t expect your successes to be elegant, expect them to be messy.&lt;/strong&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Using default function parameters wrong and right in JavaScript</title>
        <published>2021-06-03T00:00:00+00:00</published>
        <updated>2021-06-03T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/default-function-parameters/"/>
        <id>https://www.webdevluis.com/blog/default-function-parameters/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/default-function-parameters/">&lt;p&gt;This is a short story of two &lt;strong&gt;JavaScript functions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What they do is not important. I want to illustrate something about &lt;strong&gt;default function parameters&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;First, I had a function &lt;code&gt;getColorOnBackground&lt;/code&gt;, which took the parameters &lt;code&gt;backgroundColor&lt;/code&gt; and, optionally, &lt;code&gt;dark&lt;/code&gt; and &lt;code&gt;light&lt;/code&gt;. It returned either &lt;code&gt;dark&lt;/code&gt; or &lt;code&gt;light&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;function getColorOnBackground (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  backgroundColor,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  dark = &amp;#39;text-gray-900&amp;#39;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  light = &amp;#39;text-white&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // `dark` and `light` are CSS classes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // Do some logic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // Return either the `dark` or `light` values&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It needed to set &lt;strong&gt;default parameters&lt;/strong&gt; for &lt;code&gt;dark&lt;/code&gt; and &lt;code&gt;light&lt;/code&gt;, otherwise, they couldn&#39;t be optional.&lt;/p&gt;
&lt;p&gt;The other function is &lt;code&gt;getTitleColorFromData&lt;/code&gt;, and it calls the first one, &lt;code&gt;getColorOnBackground&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;function getTitleColorFromData (data) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  if (data.backgroundColor) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    // here call the other function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    return getColorOnBackground(data.backgroundColor)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // do other things&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function has at least one limitation, the option to use custom values for &lt;code&gt;light&lt;/code&gt; and &lt;code&gt;dark&lt;/code&gt; is lost. So, naively, what we want is:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;function getTitleColorFromData (data, dark, light) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  if (data.backgroundColor) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    // here call the other function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    return getColorOnBackground(data.backgroundColor, dark, light)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // do other things&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will work.&lt;/p&gt;
&lt;p&gt;However, as the function does more things, I expect that the number of arguments might increase. When this happens, I like to turn the optional arguments into an &lt;strong&gt;object&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This was my (dumb) try:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;function getTitleColorFromData (data, { dark = null, light = null }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  if (data.backgroundColor) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    // here call the other function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    return getColorOnBackground(data.backgroundColor, dark, light)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // do other things&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I replaced &lt;code&gt;dark, light&lt;/code&gt; by &lt;code&gt;{ dark = null, light = null }&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here I was thinking: &quot;if I don&#39;t pass &lt;code&gt;light&lt;/code&gt; or &lt;code&gt;dark&lt;/code&gt; values, just use the fallback set in the &lt;code&gt;getColorOnBackground&lt;/code&gt; function&quot;. Setting the default values to &lt;code&gt;null&lt;/code&gt; was a naive attempt to make them be &lt;code&gt;falsy&lt;/code&gt; and thus fallback to the &lt;strong&gt;default parameters&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That was very wrong.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters#passing_undefined_vs._other_falsy_values&quot;&gt;Default parameters - JavaScript | MDN&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If default parameters exist, e.g. &lt;code&gt;function myFunction (value = &#39;default&#39;) {...}&lt;/code&gt; they will only be used in two cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;strong&gt;no value&lt;/strong&gt; is passed when we call the function&lt;/li&gt;
&lt;li&gt;If a value is passed and is explicitly set to &lt;code&gt;undefined&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;null&lt;/code&gt; is not the same as &lt;code&gt;undefined&lt;/code&gt;, so default parameters are NOT used if the passed value is &lt;code&gt;null&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;So, in my (dumb) attempt above, the nested function &lt;code&gt;getColorOnBackground&lt;/code&gt; would always return &lt;code&gt;null&lt;/code&gt;. :(&lt;/p&gt;
&lt;p&gt;How to fix it?&lt;/p&gt;
&lt;p&gt;An obvious solution would be to set the &lt;strong&gt;default parameters&lt;/strong&gt; to &lt;code&gt;undefined&lt;/code&gt; explicitly:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;function getTitleColorFromData (data, { dark = undefined, light = undefined }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  if (data.backgroundColor) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    // here call the other function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    return getColorOnBackground(data.backgroundColor, dark, light)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // do other things&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s not a good solution though. It carries one problem: I&#39;m forced to always pass a second argument, like so: &lt;code&gt;getTitleColorFromData(myData, {})&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Otherwise I get an error:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Cannot destructure property &amp;#39;dark&amp;#39; of &amp;#39;undefined&amp;#39; as it is undefined.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plus, it&#39;s also verbose.&lt;/p&gt;
&lt;p&gt;Fortunately, there&#39;s a good solution to this small conundrum:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;function getTitleColorFromData (data, { dark, light } = {}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  if (data.backgroundColor) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    // here call the other function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    return getColorOnBackground(data.backgroundColor, dark, light)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  // do other things&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;{ dark, light } = {}&lt;/code&gt; means: &quot;Use an empty object as a &lt;strong&gt;default parameter&lt;/strong&gt; if no argument is provided&quot;. With this, &lt;code&gt;light&lt;/code&gt; and &lt;code&gt;dark&lt;/code&gt; will automatically be &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Not only that works, but is also more flexible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The second argument is totally optional. This will work: &lt;code&gt;getTitleColorFromData(myData)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;We can set the value of both parameters. This will also work: &lt;code&gt;getTitleColorFromData(myData, { dark: &#39;text-gray-900&#39;, light: &#39;text-gray-100&#39; })&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;We can set a value for one of the parameters: &lt;code&gt;getTitleColorFromData(myData, { light: &#39;text-gray-100&#39; })&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Isn&#39;t the power of &lt;strong&gt;default function parameters&lt;/strong&gt; wonderful?&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How I use Anki to study programming: Anki setup and card creation</title>
        <published>2020-08-07T00:00:00+00:00</published>
        <updated>2020-08-07T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/anki/"/>
        <id>https://www.webdevluis.com/blog/anki/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/anki/">&lt;p&gt;Anki is a software to use flashcards (question and answer cards) on the computer or mobile. I use it to review what I learn when I am studying programming. Anki is an &lt;strong&gt;extremely valuable tool to prevent forgetting what I have studied in the past&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to using Anki daily for a long time, I can recall many concepts of javascript and other technologies even if I don&#39;t use them in my daily work, and &lt;strong&gt;even if I have never used them&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Anki reviews are a great way to avoid feeling lost among so many new things to remember while studying programming.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Anki is particularly useful to effectively digest the fundamentals of programming, when one gets started on this field.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I will explain how I setup Anki and how I create cards.&lt;/p&gt;
&lt;p&gt;If you don&#39;t know what Anki is, you can get an overview &lt;a rel=&quot;external&quot; href=&quot;https://docs.Ankiweb.net/#/getting-started&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;download-anki&quot;&gt;Download Anki&lt;/h2&gt;
&lt;p&gt;Head to &lt;a rel=&quot;external&quot; href=&quot;https://apps.Ankiweb.net/&quot;&gt;AnkiWeb&lt;/a&gt; and download the software.&lt;/p&gt;
&lt;p&gt;Most likely, you will want to create the cards in your computer and study them in your mobile.&lt;/p&gt;
&lt;p&gt;So, download and install both the desktop version and the mobile app for whatever operative systems you use. Once you have installed the app, &lt;strong&gt;create an account&lt;/strong&gt;. That will allow you to &lt;strong&gt;synchronize&lt;/strong&gt; your cards and decks across devices.&lt;/p&gt;
&lt;p&gt;Once you have an account, you&#39;ll do some initial setup.&lt;/p&gt;
&lt;h2 id=&quot;initial-setup&quot;&gt;Initial setup&lt;/h2&gt;
&lt;p&gt;We&#39;re going to setup Anki for the first time. You only need to do this once.&lt;/p&gt;
&lt;figure class=&quot;video&quot;&gt;
&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/mr_RzKEExoY?start=116&amp;amp;cc_load_policy=1&amp;amp;rel=0&quot; title=&quot;Anki initial setup&quot; loading=&quot;lazy&quot; allow=&quot;accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;Anki initial setup&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;create-a-new-deck&quot;&gt;Create a new deck&lt;/h3&gt;
&lt;p&gt;You will be adding cards to something called a &quot;deck&quot;. The first time you open Anki, you won&#39;t have any deck, so you&#39;ll have to create one.&lt;/p&gt;
&lt;p&gt;Simply click on &lt;code&gt;Create deck&lt;/code&gt;, and give it a name. Done. Easy, right?&lt;/p&gt;
&lt;h3 id=&quot;customize-deck-options&quot;&gt;Customize deck options&lt;/h3&gt;
&lt;p&gt;Optional, but highly recommended.&lt;/p&gt;
&lt;p&gt;The default Anki settings might be &quot;optimal&quot; to memorize things, but have two fairly big disadvantages, in my opinion:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The review rate for cards is high. This means that you &lt;strong&gt;review a given card quite often&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The number of reviews per session is high. In a given (daily) session you will be &lt;strong&gt;reviewing lots of cards&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes the daily review session quite long. In between 20 and 40 minutes, or more. Honestly, I&#39;m too lazy to spend that much time reviewing cards.&lt;/p&gt;
&lt;p&gt;So, &lt;strong&gt;I tweaked the settings so that review sessions are shorter.&lt;/strong&gt; About 10 to 20 minutes long.&lt;/p&gt;
&lt;p&gt;To customize the deck options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click on the small gear icon next to the deck&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Options&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;At the top, next to options group, click on &lt;code&gt;Manage&lt;/code&gt; and then &lt;code&gt;Add&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Name the new options group whatever you like (&quot;quick review&quot;, for example)&lt;/li&gt;
&lt;li&gt;Use the following options:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki/anki-newcards.webp&quot; alt=&quot;anki new cards&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki/anki-reviews.webp&quot; alt=&quot;anki reviews&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These settings will give you effective review sessions which are not too long.&lt;/p&gt;
&lt;h2 id=&quot;create-the-cards&quot;&gt;Create the cards&lt;/h2&gt;
&lt;p&gt;Your setup is ready. But your deck has no cards. Kinda useless.&lt;/p&gt;
&lt;p&gt;But we&#39;re going to fix that.&lt;/p&gt;
&lt;p&gt;If you know that you can import decks created by other people, you may be thinking: &lt;em&gt;I&#39;ll import someone else&#39;s deck&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You could indeed do that, but I discourage it. &lt;strong&gt;Creating your own cards will help you study and force you to summarize what you are learning&lt;/strong&gt;. And the cards will &quot;make more sense&quot; to you.&lt;/p&gt;
&lt;p&gt;In short: &lt;strong&gt;create your own cards&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I concede that creating the cards can be a bit tedious sometimes, but it pays off. Consider it part of studying.&lt;/p&gt;
&lt;p&gt;You can create cards in the app itself, using the &lt;code&gt;add&lt;/code&gt; button. However, this is &lt;strong&gt;not&lt;/strong&gt; how I do it most of the time, I prefer another method that for me is faster and more practical.&lt;/p&gt;
&lt;p&gt;Let&#39;s jump into it.&lt;/p&gt;
&lt;h2 id=&quot;how-do-i-add-cards-to-anki-easily&quot;&gt;How do I add cards to Anki easily?&lt;/h2&gt;
&lt;figure class=&quot;video&quot;&gt;
&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/mr_RzKEExoY?start=318&amp;amp;cc_load_policy=1&amp;amp;rel=0&quot; title=&quot;Create Anki cards easily&quot; loading=&quot;lazy&quot; allow=&quot;accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;Create Anki cards easily&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Let&#39;s imagine you&#39;re studying &lt;code&gt;array methods&lt;/code&gt; from the website &lt;a rel=&quot;external&quot; href=&quot;https://javascript.info/&quot;&gt;javascript.info&lt;/a&gt;, and you see the following piece of information.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- arr.push(...items) – adds items to the end of an array&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you want to create an Anki card to remember this &lt;code&gt;arr.push()&lt;/code&gt; method (don&#39;t worry if you have no idea what this is, it&#39;s just an example).&lt;/p&gt;
&lt;p&gt;Now, &lt;strong&gt;open a text editor&lt;/strong&gt;. Any will do. I use &lt;a rel=&quot;external&quot; href=&quot;https://www.sublimetext.com/&quot;&gt;Sublime&lt;/a&gt;. You can use the same, or &lt;code&gt;textedit&lt;/code&gt;, or &lt;code&gt;notepad&lt;/code&gt; or whatever.&lt;/p&gt;
&lt;p&gt;In the editor, you will write something like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Method that adds items to the end of an array&quot;arr.push(...items)&quot;js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki/joseph-joestar.webp&quot; alt=&quot;I know what you&amp;#39;re gonna say next&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now you will say: &lt;code&gt;What on earth is this, Luis!?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will be the content of &lt;strong&gt;one&lt;/strong&gt; Anki card. There are &lt;strong&gt;3 fields&lt;/strong&gt;, the &lt;strong&gt;front&lt;/strong&gt; of the card, the &lt;strong&gt;back&lt;/strong&gt; of the card, and the &lt;strong&gt;tags&lt;/strong&gt;. In that order.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Front&lt;/strong&gt;: &lt;code&gt;Method that adds items to the end of an array&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Back&lt;/strong&gt;: &lt;code&gt;arr.push(...items)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tags&lt;/strong&gt;: &lt;code&gt;js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To indicate where the &lt;strong&gt;front&lt;/strong&gt; ends and where the &lt;strong&gt;back&lt;/strong&gt; starts, you need a &lt;strong&gt;separator&lt;/strong&gt;, which could be any character. I use one &lt;strong&gt;double quote&lt;/strong&gt;. I could use something else, but a double quote works just fine for me in 99% of the cases.&lt;/p&gt;
&lt;p&gt;So, while you study, you will keep on adding cards in that same format, based on the things that you want to remember.&lt;/p&gt;
&lt;p&gt;You might end up with something like this (you could have many more lines, the number doesn&#39;t matter):&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Method that adds items to the end of an array&amp;quot;arr.push(...items)&amp;quot;js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Method that extracts an item from the end&amp;quot;arr.pop()&amp;quot;js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;const element = _____Hello, world!_____ // Component renders a h1 heading&amp;quot;const element = &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&amp;quot;react&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;a _____&amp;gt;Website&amp;lt;/a&amp;gt; // Links to https://example.com&amp;quot;&amp;lt;a href=&amp;quot;https://example.com&amp;quot;&amp;gt;Website&amp;lt;/a&amp;gt;&amp;quot;html&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;em&gt;You do NOT need to add these cards to your Anki deck! This is just a random example!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I want to point out three things here, that you may notice if you look at the previous example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;new line&lt;/strong&gt;, or line break indicates a &lt;strong&gt;new card&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;I use &lt;strong&gt;empty gaps&lt;/strong&gt; like &lt;code&gt;_____&lt;/code&gt; very often in the &lt;strong&gt;front&lt;/strong&gt; of the card. It&#39;s a simple way to indicate that there&#39;s something there that I am supposed to remember.&lt;/li&gt;
&lt;li&gt;If you look at the last &quot;card&quot; (line), there&#39;s something wrong in there. As I mentioned we&#39;re using the &lt;strong&gt;double quote &quot;&lt;/strong&gt; as a field separator. However, in the last card, &lt;em&gt;the answer itself contains double quotes&lt;/em&gt;. If we try to add that card like that, Anki will get confused and won&#39;t be able to tell what are the front, back and tags. &lt;strong&gt;We cannot use the separator character inside the fields themselves&lt;/strong&gt;. The correct version of the last card would look like the following:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;a _____&amp;gt;Website&amp;lt;/a&amp;gt; // Links to https://example.com&amp;quot;&amp;lt;a href=&amp;#39;https://example.com&amp;#39;&amp;gt;Website&amp;lt;/a&amp;gt;&amp;quot;html&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that I replaced the &lt;strong&gt;double quotes&lt;/strong&gt; inside the &lt;strong&gt;back&lt;/strong&gt; field by &lt;strong&gt;single quotes&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;import-the-cards-in-the-text-file-to-anki&quot;&gt;Import the cards in the text file to Anki&lt;/h3&gt;
&lt;figure class=&quot;video&quot;&gt;
&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/mr_RzKEExoY?start=740&amp;amp;cc_load_policy=1&amp;amp;rel=0&quot; title=&quot;How to import cards to Anki&quot; loading=&quot;lazy&quot; allow=&quot;accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;How to import cards to Anki&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To add these as cards to Anki, we need to &lt;strong&gt;import&lt;/strong&gt; the text file we have created.&lt;/p&gt;
&lt;p&gt;Go to the Anki app, click on &lt;code&gt;file&lt;/code&gt;, then &lt;code&gt;import...&lt;/code&gt;. Then select the text file.&lt;/p&gt;
&lt;p&gt;You will see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki/anki-import-1.webp&quot; alt=&quot;Anki import cards, step 1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You will have to select the right deck where to import the cards. As for the card type, you should choose &lt;code&gt;Basic&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Click on &lt;code&gt;Fields separated by: Space&lt;/code&gt;, and here write a &lt;strong&gt;double quote&lt;/strong&gt;. You probably figured out that this should be the &lt;strong&gt;separator&lt;/strong&gt; we used in the text file.&lt;/p&gt;
&lt;p&gt;If you didn&#39;t use a double quote as separator, then enter whatever you used.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki/anki-import-3.webp&quot; alt=&quot;Anki import cards, step 3&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you click &lt;code&gt;ok&lt;/code&gt;, Anki will automatically identify the 3 fields.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/anki/anki-import-4.webp&quot; alt=&quot;Anki import cards, step 3&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you click on &lt;code&gt;Import&lt;/code&gt;, you are done.&lt;/p&gt;
&lt;p&gt;Congratulations! You&#39;ve just added cards to the deck and you can begin studying.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Using &lt;strong&gt;Anki to learn programming&lt;/strong&gt; is no secret. I got inspiration from others that were doing it long before me. &lt;a rel=&quot;external&quot; href=&quot;https://sivers.org/srs&quot;&gt;Derek Sivers&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://www.jackkinsella.ie/articles/jAnki-method&quot;&gt;Jack Kinsella&lt;/a&gt; are two examples.&lt;/p&gt;
&lt;p&gt;I used to review the cards while stretching my legs by taking a walk around my neighborhood.&lt;/p&gt;
&lt;p&gt;If you skip a day it is not a big deal, but &lt;strong&gt;try to review your deck daily&lt;/strong&gt;. You should notice positive effects almost immediately. After a few weeks of study in combination with daily Anki you will be surprised of how much you&#39;re capable of recalling.&lt;/p&gt;
&lt;p&gt;You will almost feel like you had a superpower.&lt;/p&gt;
&lt;p&gt;As they say in Japan, &lt;em&gt;Ganbatte!&lt;/em&gt; (do your best)&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Is higher education (e.g. a PhD) necessary to get a job as a junior developer?</title>
        <published>2020-04-12T00:00:00+00:00</published>
        <updated>2020-04-12T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/do-you-need-a-phd/"/>
        <id>https://www.webdevluis.com/blog/do-you-need-a-phd/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/do-you-need-a-phd/">&lt;p&gt;tl,dr: No. You need commitment and technical skills. Though a PhD helps your resume stand out, upping your chances to get interviews.&lt;/p&gt;
&lt;h2 id=&quot;why-do-i-write-this&quot;&gt;Why do I write this?&lt;/h2&gt;
&lt;p&gt;In October 2019 I submitted a post on reddit that got some attention (also in &lt;a href=&quot;https://www.webdevluis.com/blog/from-jobless-to-junior-frontend-developer-in-5-months/&quot;&gt;this blog&lt;/a&gt;. I also have a PhD in computational chemistry. A handful of commenters assumed that my PhD got me the job. I am thankful to them for making me aware of that point of view (I wasn&#39;t). I think it is a reasonable assumption. I also think it&#39;s mistaken, and I will now give my view on this point.&lt;/p&gt;
&lt;p&gt;Keep in mind that the context is specifically getting a first job as a &lt;em&gt;junior&lt;/em&gt; frontend developer.&lt;/p&gt;
&lt;p&gt;My conclusions are quite obvious. Don&#39;t expect to find any &quot;magic knowledge&quot;.&lt;/p&gt;
&lt;h2 id=&quot;did-a-phd-give-me-the-job-in-what-way-did-a-phd-help-me&quot;&gt;Did a PhD give me the job? In what way did a PhD help me?&lt;/h2&gt;
&lt;p&gt;No, it did not give me the job. I know because I asked the person who interviewed me.&lt;/p&gt;
&lt;p&gt;A PhD &lt;strong&gt;helped my resume stand out&lt;/strong&gt; in a positive way. It suggests intelligence, and the ability to work persistently in a complicated problem. Both are valuable to employers and make you look like a shiny pokemon. That made it easier to get an interview. However, once in the interview, what mattered was no longer the PhD, but my attitude and my technical skills. Imagining that my PhD gave me the job would be mistaken.&lt;/p&gt;
&lt;p&gt;I hope it&#39;s obvious that a PhD will not miraculously help you nail the interview. So a more relevant question is...&lt;/p&gt;
&lt;h2 id=&quot;do-you-need-a-phd-or-higher-education-to-get-an-interview&quot;&gt;Do you need a PhD or higher education to GET an interview?&lt;/h2&gt;
&lt;p&gt;You may say: &lt;em&gt;&quot;Without the PhD you would have never got an interview&quot;&lt;/em&gt;. I don&#39;t think it&#39;s true. We&#39;re hiring junior devs right now (February 2020). None of the candidates has a PhD. Half of them got an interview.&lt;/p&gt;
&lt;p&gt;So, no (duh).&lt;/p&gt;
&lt;p&gt;If a company filtered only resumes with PhD levels of education, they would run out of candidates very quickly. A PhD is rare and gets attention from employers, 100 billion %. It may give you an interview, but &lt;strong&gt;you don&#39;t need one&lt;/strong&gt; to get an interview. And even if you get the interview, a PhD will not guarantee you the job if e.g. you lack technical skills. After all, 3 companies rejected me during my job hunting. I believe the reason is that I was too unskilled.&lt;/p&gt;
&lt;p&gt;In sum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A PhD is &lt;strong&gt;neither a requirement nor a guarantee&lt;/strong&gt; to get a job.&lt;/li&gt;
&lt;li&gt;To increase your chances to get an interview you should put something in your resume that makes it &lt;strong&gt;stand out in a unique and positive way&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-do-you-need-to-put-in-your-resume-to-get-an-interview&quot;&gt;What do you need to put in your resume to get an interview?&lt;/h2&gt;
&lt;p&gt;Give employers what they want.&lt;/p&gt;
&lt;p&gt;In my opinion and experience, that is &lt;strong&gt;commitment&lt;/strong&gt; and fundamental &lt;strong&gt;technical knowledge&lt;/strong&gt;. Show that in your resume.&lt;/p&gt;
&lt;h3 id=&quot;commitment-show-that-you-are-motivated-and-committed-to-this-career-path&quot;&gt;Commitment: show that you are motivated and committed to this career path.&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Mention in your resume that you want to become a &lt;strong&gt;senior developer&lt;/strong&gt; (take a look at &lt;a rel=&quot;external&quot; href=&quot;http://bit.ly/38wlTPF&quot;&gt;the resume I used back then&lt;/a&gt; to see an example).&lt;/li&gt;
&lt;li&gt;If you&#39;ve done something in the past that required &lt;strong&gt;effort for a prolonged time&lt;/strong&gt;, that shows that you are persistent and able to commit. Mention it. Examples are having worked for the same company for more than 18 months, a big personal project that took months to complete, an entrepreneurial activity, winning a prize in a competition...&lt;/li&gt;
&lt;li&gt;If you decided to pay for one insultingly expensive bootcamp (which is unnecessary), make sure the employer can find their website so that they are able to check the price of admission. Your willingness to cash out suggests commitment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fundamental-technical-knowledge&quot;&gt;Fundamental technical knowledge:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Add that you know &lt;strong&gt;HTML5, CSS3, JavaScript and React&lt;/strong&gt; (as a frontend dev --- and yes, another framework will do). I go into a bit more detail on what technical skills are needed &lt;a href=&quot;https://www.webdevluis.com/blog/from-jobless-to-junior-frontend-developer-in-5-months/&quot;&gt;in my previous post&lt;/a&gt; (look for &lt;em&gt;&quot;the minimum to get a first job&quot;&lt;/em&gt;). Don&#39;t bother lying about these, you wouldn&#39;t pass the interview anyway.&lt;/li&gt;
&lt;li&gt;You can add MongoDB and NodeJS/ExpressJS if it is true that you know how to use them (every bootcamp seems to include these), but they are not necessary because as a junior frontend developer you wouldn&#39;t be doing anything with the backend and databases anyway.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;combine-both-things&quot;&gt;Combine both things!&lt;/h3&gt;
&lt;p&gt;Show both commitment and knowledge at the same time.&lt;/p&gt;
&lt;p&gt;If you learned by yourself, autonomously and using free resources (like I did) AND you have a solid foundation of HTML5, CSS3, JavaScript ES6 and React (or Vue, or Angular), that makes you an excellent candidate for a junior position. Automatically, you prove that you are committed and motivated, because otherwise you wouldn&#39;t have found the energy to push yourself through many hours of intense studying. Plus, you show that you will continue learning on your own, which is the icing on the cake in this ever-evolving field. In other words, it makes you exceptionally valuable.&lt;/p&gt;
&lt;p&gt;Even if you feel that you lack in formal education, don&#39;t let it be a deterrent to pursue a career in development. The world needs you!&lt;/p&gt;
&lt;h2 id=&quot;useful-links&quot;&gt;Useful links:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.webdevluis.com/blog/from-jobless-to-junior-frontend-developer-in-5-months/&quot;&gt;My previous post: &quot;From jobless to junior frontend developer in 5 months&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://bit.ly/38wlTPF&quot;&gt;My sample resume&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>From jobless to junior developer in 5 months</title>
        <published>2020-02-22T00:00:00+00:00</published>
        <updated>2020-02-22T00:00:00+00:00</updated>
        
        <author>
          <name>Luis Martinez</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.webdevluis.com/blog/from-jobless-to-junior-frontend-developer-in-5-months/"/>
        <id>https://www.webdevluis.com/blog/from-jobless-to-junior-frontend-developer-in-5-months/</id>
        
        <content type="html" xml:base="https://www.webdevluis.com/blog/from-jobless-to-junior-frontend-developer-in-5-months/">&lt;h2 id=&quot;tl-dr&quot;&gt;Tl,dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;From jobless and with no specific frontend knowledge to an entry level job in 5 months&lt;/li&gt;
&lt;li&gt;Money spent: 0&lt;/li&gt;
&lt;li&gt;Time spent studying: roughly 2.5 months, around 5 hours per day.&lt;/li&gt;
&lt;li&gt;Study material: mainly freecodecamp modules 1 to 3 and javascript.info&lt;/li&gt;
&lt;li&gt;Focused studying, with emphasis on deep understanding&lt;/li&gt;
&lt;li&gt;Use a review system for the things you learn, Anki cards or something else&lt;/li&gt;
&lt;li&gt;Put some effort in structuring well your resume so that it&#39;s relevant and easy to read&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I am going to explain how I transitioned from unemployed living in Barcelona and with barely any front end knowledge to employed in Amsterdam as an entry level front end developer, within 5 months. The main reason why I am writing this post is because, while learning, I met other front end students and many of them saw things unnecessarily hard.&lt;/p&gt;
&lt;p&gt;I will explain what I did.&lt;/p&gt;
&lt;p&gt;I hope this is useful to you.&lt;/p&gt;
&lt;h2 id=&quot;why-studying-programming&quot;&gt;Why studying programming?&lt;/h2&gt;
&lt;p&gt;I think it is important to start with a positive mindset that somehow you can do this and it is a good idea. I didn&#39;t decide to learn front end development right off the bat, I did it because I clearly saw lots of opportunities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using LinkedIn, I compared the amount of job offers (demand) with the amount of people with developer skills (offer). A simple LinkedIn search will give you some rough numbers. Programming had by far the best ratio of demand VS offer, compared to other markets I explored. In my eyes, programming became an obvious choice very, very quickly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the current market, as a developer odds are in your favor.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I talked to a few HR firms, and they all told me that becoming a developer is a great idea because there is a lot of demand, and therefore I would find a job in no time.&lt;/li&gt;
&lt;li&gt;It&#39;s just common sense. Most companies, large or small, need some kind of web presence. Web technologies are constantly evolving. This means that you have endless opportunities to find new work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes clear that web developer skills are currently very valuable, and that could help you find the idea of learning web development exciting.&lt;/p&gt;
&lt;p&gt;Disclaimer:&lt;/p&gt;
&lt;p&gt;I have no computer science degree, but I am more computer versed than the average person. During my PhD (in scientific research) I used linux all the time and became well acquainted with the unix shell (the command console) and the bare basics of programming languages. That was certainly useful to lose the fear to commands and consoles, but it is nothing you cannot acquire in a few days of studying.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I&#39;ve been told recently that having a PhD helped catch the eye of the interviewer.&lt;/p&gt;
&lt;h2 id=&quot;why-front-end-web-development&quot;&gt;Why front end web development?&lt;/h2&gt;
&lt;p&gt;I did compare offer VS demand ratios but it was not very obvious which field of engineering was a better choice. Other fields could have been an equally good or even better choice: data analysis, backend engineering or dev ops to name some. So I talked to people in the sector, asking them what field would bring me a job the fastest, and a couple of them recommended me to look into front end.&lt;/p&gt;
&lt;p&gt;Literally one person told me: &quot;learn HTML, CSS, JavaScript and React, and you&#39;re good to get a starting job&quot;.&lt;/p&gt;
&lt;p&gt;He was not wrong.&lt;/p&gt;
&lt;p&gt;Dev ops salaries are actually higher than in other fields. I would say ultimately depends on your own preferences and previous knowledge. For example, I picked front end for another reason, I am attracted to creating artwork and &quot;visual works&quot;, so it was a more obvious choice for me. But that doesn&#39;t mean it&#39;s the best pick for everyone.&lt;/p&gt;
&lt;h2 id=&quot;how-did-i-study-what-resources-did-i-use&quot;&gt;How did I study? What resources did I use?&lt;/h2&gt;
&lt;h3 id=&quot;freecodecamp&quot;&gt;FreeCodeCamp&lt;/h3&gt;
&lt;p&gt;I spent most of my waking hours studying in my room, since I was jobless anyway. I picked &lt;a rel=&quot;external&quot; href=&quot;https://www.freecodecamp.org/&quot;&gt;FreeCodeCamp&lt;/a&gt;, which I had seen recommended online, and followed its program for the first 3 modules. It is an entirely free (with optional donations if you want to help support it), very complete, &quot;study at your own pace&quot; introductory course.&lt;/p&gt;
&lt;p&gt;I simply followed the program in FreeCodeCamp. It took me around one month to study the first three modules, which include HTML5, CSS3, JavaScript ES6, React/Redux, and a tiny bit of jQuery, Bootstrap and Sass. It is an excellent introduction to front end. You are going to be using most of these every day in your coding life.&lt;/p&gt;
&lt;h3 id=&quot;anki-for-reviews&quot;&gt;Anki for reviews&lt;/h3&gt;
&lt;p&gt;One important thing I did right from the start was to take notes of what I studied to later convert them into &lt;a rel=&quot;external&quot; href=&quot;https://apps.ankiweb.net/&quot;&gt;Anki cards&lt;/a&gt;. Initially I also wrote summaries &lt;a rel=&quot;external&quot; href=&quot;https://drive.google.com/drive/folders/1YNLZp0YglbjIDhMlyJNClsxasXxK3z_Q?usp=sharing&quot;&gt;you can see them here&lt;/a&gt;, but I don&#39;t think these are worth the time spent on them. The Anki cards however were incredibly valuable. I created the Anki cards at the same time I studied, then every single day I studied the Anki deck. The main reason why Anki cards were so useful is that as you progress through the lessons, information keeps on piling up, and there&#39;s gonna be A LOT of new information you will have to learn. Without a review method, I would have forgotten most of the things I studied. Anki allowed me to remember almost everything in a very time-effective way.&lt;/p&gt;
&lt;p&gt;I did not use Anki&#39;s default configuration. That would be worth its own article, but for starters you could read about the &lt;a rel=&quot;external&quot; href=&quot;http://www.jackkinsella.ie/articles/janki-method&quot;&gt;Janki method&lt;/a&gt;. Making Anki cards is an art, and it is possible to make it wrong. There are 3 main things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One card = one and only one item to remember&lt;/li&gt;
&lt;li&gt;Find a quick way to create the cards (Now I write the questions and answers in a plain text file and import the file)&lt;/li&gt;
&lt;li&gt;Lower the repetition thresholds so that cards show up less frequently (to reduce the time spent in reviewing the cards)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&#39;s all I did for about 4 or 5 weeks. I would say I spent on average 4 or 5 hours studying every single day, and it was hard work. If I had been working at the same time in a 40 hour/week job, I would have gone much slower. It is NOT EASY. It required full focus for hours, every day during weeks.&lt;/p&gt;
&lt;p&gt;Anyway. I completed the first 3 modules of FreeCodeCamp. There are 7 modules in total, but I don&#39;t believe the last 4 are essential to find a job. They would be nice to have though, and would definitely increase your odds. But part of the reason why I am writing this, is to tell you what is the minimum you should do.&lt;/p&gt;
&lt;h3 id=&quot;javascript-info&quot;&gt;JavaScript.info&lt;/h3&gt;
&lt;p&gt;After completing the freecodecamp modules I decided I should become a &quot;JavaScript expert&quot;, so I began reading &lt;a rel=&quot;external&quot; href=&quot;http://javascript.info/&quot;&gt;javascript.info&lt;/a&gt;. This is a very in-depth journey into JavaScript, and of course, it is 100% free. I found this resource very useful, and took the same approach of studying while making Anki cards. Why did I want to learn more about JavaScript? I felt that learning one programming language very well was the best use of my time. Tools, frameworks, these come and go, but JavaScript has come to stay. At least for quite a long while. Having a good grasp of JavaScript is a great use of your time, and when job interviews come, they will test your knowledge about the language. Understand well its nuances: the keyword &quot;this&quot;, the differences between arrow functions and function declarations, asynchronous code and promises, spread operators, destructuring assignments... sometimes it&#39;s gonna be a bit hard for you to tell what is important or what is not when it comes to JavaScript. Particularly in the beginning. In case of doubt, learn it. It won&#39;t hurt you.&lt;/p&gt;
&lt;h2 id=&quot;what-else-did-i-do-besides-studying&quot;&gt;What else did I do besides studying?&lt;/h2&gt;
&lt;p&gt;Besides studying frontend technologies, I also talked to people who worked as front end developers, recruiters, and bootcamp teachers. I talked to a handful of recruiters about my decision to move into the development world. This is the key information I got from them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is indeed a huge need for developers in the market.&lt;/li&gt;
&lt;li&gt;Particularly, the Netherlands is a place in Europe where there is a developer shortage. Companies from this country go to other countries&#39;s job fairs to try and recruit talent. Me managing to easily find a job in Amsterdam supports this point.&lt;/li&gt;
&lt;li&gt;A recruiter told me &quot;the only thing you&#39;re missing is some kind of course certificate in your CV. It doesn&#39;t need to be official or expensive, anything does it&quot;. The reason? A certificate or course makes it easier for me to sell my CV to companies, given that I have no work experience as a developer. To them, it shows commitment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, I found a government-funded bootcamp in my city which costed me zero money (only a few coins spent on the coffee machine). The quality was very low. I completed it nonetheless and added it to my CV. All in all, the bootcamp was not a very useful way to spend my time and I&#39;m not sure if it factored in getting a job in any significant way. My guess is it didn&#39;t, so I won&#39;t expand into it. There&#39;s not much of a take away here. Ultimately, what interviewees care most about is whether you can use front end tools or not. In sum, having a course of sorts in your CV is not going to hurt you, but you don&#39;t NEED it so don&#39;t let it be a blocker.&lt;/p&gt;
&lt;p&gt;How do you know when you are ready to look for a job? I had no idea back then. In retrospective, I would say that the minimum to get a first job is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get well acquainted with HTML5, CSS3, JavaScript/ES6 and the fundamentals of one framework (React or Vue). This will take the longest time by far.&lt;/li&gt;
&lt;li&gt;Feel comfortable with JS asynchronous code: that means Promises and async/await syntax.&lt;/li&gt;
&lt;li&gt;Understand the basics about fetching data as JSON from external resources via APIs.&lt;/li&gt;
&lt;li&gt;Goes without saying, but become familiar with an IDE, npm, git, github and some basic bash/unix commands. You&#39;ll get this knowledge while you learn the rest, so don&#39;t worry too much.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-did-i-look-for-a-job&quot;&gt;How did I look for a job?&lt;/h2&gt;
&lt;p&gt;Nothing special. I searched with LinkedIn, Indeed and Google, and in a couple of local job boards of the Netherlands and Denmark.&lt;/p&gt;
&lt;p&gt;I was very careful in crafting my resume. One friend (thank you Miguel) had fine tuned his by getting feedback from many recruiters over the years, and I used the same structure. The advice I got could be summarized as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Put the most relevant thing on top. Usually, relevant work experience is the most important thing. For your first job, it might be education instead. The most recent things should also go on top (always from newest to oldest).&lt;/li&gt;
&lt;li&gt;Limit your CV to one single A4 page in one side. That will force you to remove the unimportant.&lt;/li&gt;
&lt;li&gt;For each item in the CV (job experience, course...) use 2-4 bullet points to indicate what relevant things YOU (not your team) did. Be technical, brief and specific. Do not write long paragraphs, no one is going to read them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I sent my resume as if I was shooting a machine gun, and I didn&#39;t use a motivation letter.&lt;/p&gt;
&lt;p&gt;This is the resume I sent, in case you&#39;re curious (&lt;a rel=&quot;external&quot; href=&quot;https://drive.google.com/open?id=1AcQR3Voug9TeGqZBDG0SMxc7uQSvrV4e&quot;&gt;resume&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Finding a job was easy. My current job came out of spending one hour sending resumes. I only got a confirmation after 1 month, so I still put aside around 2 hours every 3 or 4 days to batch send resumes.&lt;/p&gt;
&lt;h2 id=&quot;how-did-the-job-interviews-go&quot;&gt;How did the job interviews go?&lt;/h2&gt;
&lt;p&gt;The 4 interviews I did followed the same structure.&lt;/p&gt;
&lt;p&gt;The first part was about getting to know you. You are asked why you applied to the job, what&#39;s your background and what are your expectations. Companies like smart employees that have a &quot;will do / will fix&quot; positive attitude and that don&#39;t plan to leave the company in 6 months. Try to show these traits while you tell your story and in the way you communicate. For example, I was warned that the rental market in Amsterdam is hard to navigate. I immediately replied that I&#39;ve had to look for a flat abroad in other occasions, and that I was sure I could figure that out.&lt;/p&gt;
&lt;p&gt;The second part was the technical interview. The key here is that, if you have studied enough, this will simply be about showing what you already know well, so you should not be worried about it. I faced 3 types of technical interviews, either explaining the code of a project you have done, being asked technical questions, or getting a homework project. Let&#39;s look at each one by one:&lt;/p&gt;
&lt;h3 id=&quot;describing-the-code-of-one-of-your-projects&quot;&gt;Describing the code of one of your projects&lt;/h3&gt;
&lt;p&gt;I was asked to pick up any one of my recent projects, go through the code and explain it. At some point I was also asked to do a bit of code refactoring (improving your code for better readability and structure). In my opinion this is the most logical type of technical interview, because the fundamental knowledge is implied, and you are showing that you have technical knowledge. If you&#39;re going to face this interview, you must have learned well HTML, CSS, JavaScript ES6 and one frontend framework, and then you must have done 2 or 3 TINY projects with it. Think at the widget level more than at the full website level. This is the project I used in the interview: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/luismartinezs/studio-ghibli-books&quot;&gt;https://github.com/luismartinezs/studio-ghibli-books&lt;/a&gt;, as you can see it is very damn simple, but it was enough to show that I had a decent grasp of the fundamentals.&lt;/p&gt;
&lt;h3 id=&quot;technical-questions&quot;&gt;Technical questions&lt;/h3&gt;
&lt;p&gt;I was asked about ES6 JavaScript features. What does it mean adding &quot;...&quot; in front of an array or an object? What are the differences between a function declaration and an arrow function? This type of evaluation is a bit trickier in my opinion, because you may get lucky or unlucky with the questions and your knowledge might be either overestimated or underestimated as a consequence.&lt;/p&gt;
&lt;h3 id=&quot;homework-projects&quot;&gt;Homework projects&lt;/h3&gt;
&lt;p&gt;I was handed a project after an interview with Accenture. It took me around 20 hours to complete, and I only got a reply after one entire month (they replied that they wanted someone with more experience). This type of tests are usually asked for when the interviewer doesn&#39;t have technical knowledge and cannot evaluate you on the spot, so they pass the results of the test to the technical team for evaluation. I would suggest declining this type of tests. A company asking you to spend even 4 hours to solve a test just to evaluate you, rather than having someone from their technical team interview you, is asking you to waste your time. Only do the test if you have nothing better to do and you see the test as practice.&lt;/p&gt;
&lt;p&gt;The bottom line is very simple: understand well the fundamentals, and the rest will follow.&lt;/p&gt;
&lt;h2 id=&quot;what-would-i-do-differently&quot;&gt;What would I do differently?&lt;/h2&gt;
&lt;p&gt;There&#39;s very little I would have done different. There&#39;s only one way I could have gone faster: first, skip the part about jQuery in freecodecamp. After finishing the first 3 modules of freecodecamp, I would have skipped the bootcamp and begun building 2 or 3 sample projects with React. These projects should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Look like something you might see in a website&lt;/li&gt;
&lt;li&gt;They should be very simple, e.g. 2-3 days per project, which at your initial level will be the time you&#39;ll need to do a very simple widget from scratch&lt;/li&gt;
&lt;li&gt;Need to incorporate proper calls to external APIs, using fetch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once I had these done, I would have begun looking for a job, as I would know everything required to perform decently well in an interview.&lt;/p&gt;
&lt;p&gt;Thant&#39;s all for now. I&#39;m happy to reply to any questions or comments that you may have.&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.freecodecamp.org/&quot;&gt;FreeCodeCamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://apps.ankiweb.net/&quot;&gt;Anki cards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://www.jackkinsella.ie/articles/janki-method&quot;&gt;Janki method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://javascript.info/&quot;&gt;javascript.info&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/luismartinezs/studio-ghibli-books&quot;&gt;A project I used in a job interview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://drive.google.com/open?id=1AcQR3Voug9TeGqZBDG0SMxc7uQSvrV4e&quot;&gt;Resume&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
</feed>
