<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Thomas Seeley</title>
    <subtitle>Posts about software, design, and making things.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://www.tseeley.com/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://www.tseeley.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-08-05T00:00:00+00:00</updated>
    <id>https://www.tseeley.com/atom.xml</id>
    <entry xml:lang="en">
        <title>How To Onboard</title>
        <published>2025-08-05T00:00:00+00:00</published>
        <updated>2025-08-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Thomas Seeley
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.tseeley.com/posts/onboarding/"/>
        <id>https://www.tseeley.com/posts/onboarding/</id>
        
        <content type="html" xml:base="https://www.tseeley.com/posts/onboarding/">&lt;ol&gt;
&lt;li&gt;Optimize for &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Time_to_value&quot;&gt;time-to-value&lt;&#x2F;a&gt;, not signup completion&lt;&#x2F;li&gt;
&lt;li&gt;Never show a completely empty workspace&lt;&#x2F;li&gt;
&lt;li&gt;Single, opinionated first action&lt;&#x2F;li&gt;
&lt;li&gt;Personalize only when the answer changes the experience&lt;&#x2F;li&gt;
&lt;li&gt;Teach by doing, not by tooltips&lt;&#x2F;li&gt;
&lt;li&gt;Earn complexity over time&lt;&#x2F;li&gt;
&lt;li&gt;Define and obsess over one activation milestone&lt;&#x2F;li&gt;
&lt;li&gt;White-glove first to learn, then productize&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;

&lt;hr&gt;
&lt;p&gt;&lt;code&gt;ux&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tseeley.com/about/&quot;&gt;Thomas Seeley&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/tseeley.com&quot;&gt;@tseeley.com on Bluesky&lt;/a&gt;, &lt;a href=&quot;https://mastodon.social/@iamseeley&quot;&gt;@iamseeley on Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/iamseeley&quot;&gt;@iamseeley on GitHub&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Inferring types from usage</title>
        <published>2025-04-01T00:00:00+00:00</published>
        <updated>2025-04-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Thomas Seeley
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.tseeley.com/posts/inferring-state-types/"/>
        <id>https://www.tseeley.com/posts/inferring-state-types/</id>
        
        <content type="html" xml:base="https://www.tseeley.com/posts/inferring-state-types/">&lt;p&gt;Membrane programs have a &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.membrane.io&#x2F;guides&#x2F;state&quot;&gt;persistent &lt;code&gt;state&lt;&#x2F;code&gt; object&lt;&#x2F;a&gt; that survives across deploys. You import it and use it like a regular JavaScript object:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;font-style: italic;&quot;&gt;membrane&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ??=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;export function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; increment&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ++&lt;&#x2F;span&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The entire JS heap is durable, but &lt;code&gt;state&lt;&#x2F;code&gt; is the conventional way to pass data between code versions. The question is: what type is &lt;code&gt;state&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;We could make developers define a &lt;code&gt;State&lt;&#x2F;code&gt; type for every program. But that’s boilerplate, and for quick scripts and prototypes it slows you down. We already had a TypeScript language service plugin for Membrane’s graph types, so I added state type inference to it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-idea&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-idea&quot; aria-label=&quot;Anchor link for: the-idea&quot;&gt;The Idea&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;If you write &lt;code&gt;state.count ??= 0&lt;&#x2F;code&gt;, we know &lt;code&gt;state.count&lt;&#x2F;code&gt; is a &lt;code&gt;number&lt;&#x2F;code&gt;. If you write &lt;code&gt;state.apiKey = args.apiKey&lt;&#x2F;code&gt; and &lt;code&gt;args.apiKey&lt;&#x2F;code&gt; is a &lt;code&gt;string&lt;&#x2F;code&gt;, we know &lt;code&gt;state.apiKey&lt;&#x2F;code&gt; is a &lt;code&gt;string&lt;&#x2F;code&gt;. Walk the abstract syntax tree (&lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Abstract_syntax_tree&quot;&gt;AST&lt;&#x2F;a&gt;), find every assignment to a &lt;code&gt;state&lt;&#x2F;code&gt; property, extract the type from the right-hand side, and generate a type declaration.&lt;&#x2F;p&gt;
&lt;p&gt;The generated type looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; State&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; = {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;: number;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  apiKey&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;: string;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;  [&lt;&#x2F;span&gt;&lt;span&gt;key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;: string]: any;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That gets injected into the generated &lt;code&gt;membrane.d.ts&lt;&#x2F;code&gt; file. You get autocomplete and type checking on &lt;code&gt;state&lt;&#x2F;code&gt; without writing any type annotations. If you do want to define the type explicitly, export a &lt;code&gt;State&lt;&#x2F;code&gt; type or interface from &lt;code&gt;index.ts&lt;&#x2F;code&gt; and the inference backs off.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-tricky-parts&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-tricky-parts&quot; aria-label=&quot;Anchor link for: the-tricky-parts&quot;&gt;The Tricky Parts&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The idea is simple, but as always implementation had more edge cases than expected.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;assignment-patterns&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#assignment-patterns&quot; aria-label=&quot;Anchor link for: assignment-patterns&quot;&gt;Assignment Patterns&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Turns out people assign to &lt;code&gt;state&lt;&#x2F;code&gt; in a lot of different ways:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt; &#x2F;&#x2F; direct assignment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ??=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt; &#x2F;&#x2F; nullish coalescing assignment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ||=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt; &#x2F;&#x2F; logical OR assignment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt; &#x2F;&#x2F; compound assignment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; number;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt; &#x2F;&#x2F; type assertion&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The AST visitor needs to catch all of these. Direct assignments and compound assignments (&lt;code&gt;+=&lt;&#x2F;code&gt;, &lt;code&gt;-=&lt;&#x2F;code&gt;, etc.) fall in a range of operator token kinds between &lt;code&gt;FirstAssignment&lt;&#x2F;code&gt; and &lt;code&gt;LastAssignment&lt;&#x2F;code&gt;, which makes them easy to handle together. Nullish coalescing assignment (&lt;code&gt;??=&lt;&#x2F;code&gt;) is its own token kind and needs a separate check:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;isBinaryExpression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;operatorToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;SyntaxKind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;FirstAssignment&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;operatorToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;lt;=&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;SyntaxKind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;LastAssignment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;  processStateAssignment&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; else if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;isBinaryExpression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;operatorToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;SyntaxKind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;QuestionQuestionEqualsToken&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;  processStateAssignment&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;nullish-coalescing-and-any&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#nullish-coalescing-and-any&quot; aria-label=&quot;Anchor link for: nullish-coalescing-and-any&quot;&gt;Nullish Coalescing and &lt;code&gt;any&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;state.count ??= 0&lt;&#x2F;code&gt; is interesting because the right-hand side is a binary expression with &lt;code&gt;??&lt;&#x2F;code&gt;. The left side is &lt;code&gt;state.count&lt;&#x2F;code&gt;, which might be &lt;code&gt;any&lt;&#x2F;code&gt; if we haven’t inferred a type for it yet. The right side is &lt;code&gt;0&lt;&#x2F;code&gt;, which is &lt;code&gt;number&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If we naively get the type of the whole expression, TypeScript might give us &lt;code&gt;any&lt;&#x2F;code&gt; because one side is &lt;code&gt;any&lt;&#x2F;code&gt;. So we check both sides individually:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;operatorToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;SyntaxKind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;QuestionQuestionToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;operatorToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;kind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;SyntaxKind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;BarBarToken&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; leftType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; checker&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;getTypeAtLocation&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; rightType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; checker&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;getTypeAtLocation&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; isLeftAny&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; = (&lt;&#x2F;span&gt;&lt;span&gt;leftType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;getFlags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;() &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;TypeFlags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;Any&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) !==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; isRightAny&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; = (&lt;&#x2F;span&gt;&lt;span&gt;rightType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;getFlags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;() &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;TypeFlags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;Any&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) !==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;isLeftAny&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;amp;&amp;amp; !&lt;&#x2F;span&gt;&lt;span&gt;isRightAny&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; return&lt;&#x2F;span&gt;&lt;span&gt; rightType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (!&lt;&#x2F;span&gt;&lt;span&gt;isLeftAny&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; isRightAny&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; return&lt;&#x2F;span&gt;&lt;span&gt; leftType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; checker&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;getTypeAtLocation&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If one side is &lt;code&gt;any&lt;&#x2F;code&gt; and the other is concrete, use the concrete type. If both are concrete, use the union. This is what makes &lt;code&gt;state.count ??= 0&lt;&#x2F;code&gt; correctly infer &lt;code&gt;number&lt;&#x2F;code&gt; instead of &lt;code&gt;any&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;explicit-vs-inferred-priority&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#explicit-vs-inferred-priority&quot; aria-label=&quot;Anchor link for: explicit-vs-inferred-priority&quot;&gt;Explicit vs Inferred Priority&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Consider this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ??=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt; &#x2F;&#x2F; inferred: number&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; getCount&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;();&lt;&#x2F;span&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt; &#x2F;&#x2F; explicit: number (from getCount&amp;#39;s return type)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Both assignments tell us &lt;code&gt;state.count&lt;&#x2F;code&gt; is a &lt;code&gt;number&lt;&#x2F;code&gt;, but they have different confidence levels. A direct assignment (&lt;code&gt;=&lt;&#x2F;code&gt;) is explicit. A nullish coalescing assignment (&lt;code&gt;??=&lt;&#x2F;code&gt;) or logical OR assignment (&lt;code&gt;||=&lt;&#x2F;code&gt;) is inferred, because they’re fallback patterns that might not represent the primary type.&lt;&#x2F;p&gt;
&lt;p&gt;When we see a new assignment to a property we’ve already typed, explicit assignments override inferred ones. More specific types override &lt;code&gt;any&lt;&#x2F;code&gt;. This means the order of assignments in your code doesn’t matter as much as the kind of assignment.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;type-assertions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#type-assertions&quot; aria-label=&quot;Anchor link for: type-assertions&quot;&gt;Type Assertions&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;If you write &lt;code&gt;state.data = response as ApiResponse&lt;&#x2F;code&gt;, the type should be &lt;code&gt;ApiResponse&lt;&#x2F;code&gt;, not whatever TypeScript infers from &lt;code&gt;response&lt;&#x2F;code&gt;. The plugin checks for &lt;code&gt;as&lt;&#x2F;code&gt; expressions and type assertion expressions, unwrapping parenthesized expressions along the way:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; getAssertedType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; Expression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;):&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; Type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; | undefined {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;isAsExpression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) ||&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;isTypeAssertionExpression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; checker&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;getTypeAtLocation&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;isParenthesizedExpression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; getAssertedType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;&quot;&gt;expression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #808080;font-style: italic;&quot;&gt;  &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;how-it-fits-together&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-it-fits-together&quot; aria-label=&quot;Anchor link for: how-it-fits-together&quot;&gt;How It Fits Together&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The inference hooks into &lt;code&gt;getSemanticDiagnostics&lt;&#x2F;code&gt;. Every time the editor updates diagnostics, the plugin walks the AST, collects &lt;code&gt;state&lt;&#x2F;code&gt; assignments, and generates the &lt;code&gt;State&lt;&#x2F;code&gt; type string. If it changed since last time, &lt;code&gt;membrane.d.ts&lt;&#x2F;code&gt; gets regenerated.&lt;&#x2F;p&gt;
&lt;p&gt;If a developer exports their own &lt;code&gt;State&lt;&#x2F;code&gt; type:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;export interface&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt; State&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;: number;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  apiKey&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;: string;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The plugin detects that and imports it instead of generating one. Explicit always wins.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-general-problem&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-general-problem&quot; aria-label=&quot;Anchor link for: the-general-problem&quot;&gt;The General Problem&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The technique here isn’t specific to state. It’s a general approach: infer the type of an untyped object by walking all the places it’s assigned to across a codebase. You could apply the same pattern to config objects, environment variables, feature flags, or any global singleton that accumulates properties over time.&lt;&#x2F;p&gt;
&lt;p&gt;TypeScript itself has &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;TypeScript&#x2F;issues&#x2F;34738&quot;&gt;open issues&lt;&#x2F;a&gt; requesting “infer type from usage” for implicit &lt;code&gt;any&lt;&#x2F;code&gt; parameters, but it’s scoped to a single function body, not whole-program analysis. &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;flow.org&#x2F;&quot;&gt;Flow&lt;&#x2F;a&gt; does whole-program type inference, but that’s an entire type system. &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JoshuaKGoldberg&#x2F;TypeStat&quot;&gt;TypeStat&lt;&#x2F;a&gt; infers types from usage patterns and writes annotations back into source files, but it targets individual variables and parameters, not a shared object.&lt;&#x2F;p&gt;
&lt;p&gt;The architecture closest to ours is &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vuejs&#x2F;language-tools&quot;&gt;Volar&lt;&#x2F;a&gt; (Vue Language Tools), which generates virtual TypeScript files from &lt;code&gt;.vue&lt;&#x2F;code&gt; single-file components. The pattern is the same: take a non-standard source of type information, generate a &lt;code&gt;.d.ts&lt;&#x2F;code&gt; that the TypeScript language service can understand, and keep it updated as the source changes.&lt;&#x2F;p&gt;
&lt;p&gt;We’re doing a narrower version of that. Walk assignments to one object, build a type from what we find, inject it into a generated declaration file.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-learned&quot; aria-label=&quot;Anchor link for: what-i-learned&quot;&gt;What I Learned&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Working on this gave me a better feel for &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;TypeScript&#x2F;wiki&#x2F;Using-the-Compiler-API&quot;&gt;TypeScript’s compiler API&lt;&#x2F;a&gt; than anything else I’ve done. The &lt;code&gt;TypeChecker&lt;&#x2F;code&gt; is powerful but not always intuitive. &lt;code&gt;getTypeAtLocation&lt;&#x2F;code&gt; can return &lt;code&gt;any&lt;&#x2F;code&gt; in cases where you’d expect something more specific, especially when the thing you’re checking is a property of an untyped object (which &lt;code&gt;state&lt;&#x2F;code&gt; is, until we infer its type). The &lt;code&gt;getBaseTypeOfLiteralType&lt;&#x2F;code&gt; call is important too. Without it, &lt;code&gt;state.count = 0&lt;&#x2F;code&gt; would infer the literal type &lt;code&gt;0&lt;&#x2F;code&gt; instead of &lt;code&gt;number&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The biggest takeaway: type inference is a game of priorities! You have multiple sources of type information (direct assignments, fallback patterns, type assertions, compound assignments) and you need rules for which one wins. Getting those priority rules right is what makes the experience feel natural instead of surprising.&lt;&#x2F;p&gt;

&lt;hr&gt;
&lt;p&gt;&lt;code&gt;typescript&lt;/code&gt; &lt;code&gt;membrane&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tseeley.com/about/&quot;&gt;Thomas Seeley&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/tseeley.com&quot;&gt;@tseeley.com on Bluesky&lt;/a&gt;, &lt;a href=&quot;https://mastodon.social/@iamseeley&quot;&gt;@iamseeley on Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/iamseeley&quot;&gt;@iamseeley on GitHub&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hello, Membrane</title>
        <published>2024-11-10T00:00:00+00:00</published>
        <updated>2024-11-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Thomas Seeley
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.tseeley.com/posts/hello-membrane/"/>
        <id>https://www.tseeley.com/posts/hello-membrane/</id>
        
        <content type="html" xml:base="https://www.tseeley.com/posts/hello-membrane/">&lt;p&gt;It’s been a busy, exciting past few months. I joined &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;membrane.io&#x2F;&quot;&gt;membrane.io&lt;&#x2F;a&gt; as an intern in September, and I’m having a great time making improvements to the platform and building things in user space.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-membrane&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-s-membrane&quot; aria-label=&quot;Anchor link for: what-s-membrane&quot;&gt;What’s Membrane?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Membrane is a serverless platform for developing JavaScript&#x2F;TypeScript programs.&lt;&#x2F;p&gt;
&lt;p&gt;Every program you create in your Membrane workspace becomes part of your graph, and the programs can connect to each other through this graph.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video controls width=100% src=&#x27;https:&#x2F;&#x2F;docs.membrane.io&#x2F;videos&#x2F;add-connections.mp4#t=0.1&#x27;&gt;&lt;&#x2F;video&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There are a few key things that set Membrane apart from other serverless platforms:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Programs have built-in state - no need to set up a database&lt;&#x2F;li&gt;
&lt;li&gt;Everything gets logged before it happens - if it’s not in the logs, it didn’t happen&lt;&#x2F;li&gt;
&lt;li&gt;Each program gets an email address - handle incoming emails by exporting an &lt;code&gt;email&lt;&#x2F;code&gt; function&lt;&#x2F;li&gt;
&lt;li&gt;Each program gets its own endpoint - handle HTTP requests by exporting an &lt;code&gt;endpoint&lt;&#x2F;code&gt; function&lt;&#x2F;li&gt;
&lt;li&gt;Programs can use other programs as building blocks through graph connections&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-i-ve-been-working-on&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-ve-been-working-on&quot; aria-label=&quot;Anchor link for: what-i-ve-been-working-on&quot;&gt;What I’ve Been Working On.&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;span name=&quot;what-ive-been-working-on&quot;&gt;Most of my time has been spent on Membrane’s API drivers. Drivers let you interact with external APIs through Membrane’s graph.&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’ve been improving existing drivers like &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.membrane.io&#x2F;share&#x2F;membrane&#x2F;discord&quot;&gt;Discord&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.membrane.io&#x2F;share&#x2F;membrane&#x2F;anthropic&quot;&gt;Anthropic&lt;&#x2F;a&gt; and building a &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;membrane.io&#x2F;share&#x2F;iamseeley&#x2F;driver-generator&quot;&gt;tool&lt;&#x2F;a&gt; to generate new ones. The more drivers on Membrane, the more useful tools and automations we can build!&lt;&#x2F;p&gt;
&lt;p&gt;A driver exposes API endpoints as nodes (fields, actions, events) that you can reference in your graph with a simple syntax.&lt;&#x2F;p&gt;
&lt;p&gt;For example, &lt;code&gt;github:users.one(name:&quot;membrane-io&quot;)&lt;&#x2F;code&gt; tells Membrane to get a specific GitHub user - but here’s what’s cool: even though that API call returns all of the user’s data, you can selectively reference just the pieces you need in other programs, like if we want to get their avatar: &lt;code&gt;github:users.one(name:&quot;membrane-io&quot;).avatar_url&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Other Projects and Improvements:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Implemented type inference for Membrane &lt;code&gt;state&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Dynamic OG images for the changelog, packages, and docs&lt;&#x2F;li&gt;
&lt;li&gt;Made a driver for the Membrane API: &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.membrane.io&#x2F;share&#x2F;iamseeley&#x2F;mem&quot;&gt;mem&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I’m excited to start building more drivers and example tools&#x2F;automations :).&lt;&#x2F;p&gt;
&lt;p&gt;If you’d like to learn more about Membrane check these out:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;notes.just-be.dev&#x2F;Membrane%2C+A+Living+Computational+Environment&quot;&gt;Justin Bennett: Membrane, A Living Computational Environment&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.devtools.fm&#x2F;episode&#x2F;46&quot;&gt;Juan Campa - devtoolsFM episode&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.membrane.io&#x2F;blog&#x2F;changelog-0.9&quot;&gt;Membrane Changelog&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.membrane.io&#x2F;getting-started&#x2F;intro&#x2F;&quot;&gt;Membrane Docs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And join the &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discord.gg&#x2F;gBK9xP3z&quot;&gt;Discord&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;want-to-help-shape-membrane&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#want-to-help-shape-membrane&quot; aria-label=&quot;Anchor link for: want-to-help-shape-membrane&quot;&gt;&lt;strong&gt;Want to Help Shape Membrane?&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We’re looking for developers to try out Membrane and share their feedback!&lt;&#x2F;p&gt;
&lt;p&gt;If you’re interested in participating in user interviews, reach out to me at &lt;a href=&quot;mailto:thomas@membrane.io&quot;&gt;thomas@membrane.io&lt;&#x2F;a&gt;. We’d love to hear your thoughts and help you get started building tools for yourself or for your company.&lt;&#x2F;p&gt;
&lt;aside&gt;Be on the lookout for some updates coming to Membrane...soon you&amp;#x27;ll be able to create UIs with the programs on your graph!&lt;&#x2F;aside&gt;

&lt;hr&gt;
&lt;p&gt;&lt;code&gt;membrane&lt;/code&gt; &lt;code&gt;javascript&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tseeley.com/about/&quot;&gt;Thomas Seeley&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/tseeley.com&quot;&gt;@tseeley.com on Bluesky&lt;/a&gt;, &lt;a href=&quot;https://mastodon.social/@iamseeley&quot;&gt;@iamseeley on Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/iamseeley&quot;&gt;@iamseeley on GitHub&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Scheme-ing</title>
        <published>2024-07-04T00:00:00+00:00</published>
        <updated>2024-07-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Thomas Seeley
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.tseeley.com/posts/schemeing/"/>
        <id>https://www.tseeley.com/posts/schemeing/</id>
        
        <content type="html" xml:base="https://www.tseeley.com/posts/schemeing/">&lt;p&gt;I’ve been reading &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mitp-content-server.mit.edu&#x2F;books&#x2F;content&#x2F;sectbyfn&#x2F;books_pres_0&#x2F;6515&#x2F;sicp.zip&#x2F;index.html&quot;&gt;SICP&lt;&#x2F;a&gt; on and off. To be honest, I’ve only made it a few chapters in and I think that’s as far as I’ll go.&lt;&#x2F;p&gt;
&lt;p&gt;Around the same time I read &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;books&#x2F;wolf-in-white-van&#x2F;9781250074713&quot;&gt;&lt;em&gt;Wolf in White Van&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; by John Darnielle. It’s a novel about a guy who runs a &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Interactive_fiction&quot;&gt;text-based adventure game&lt;&#x2F;a&gt; by mail. Players send in their moves, he sends back what happens. The game is called Trace Italian! The whole book is told in reverse, which gives it this strange gravity. You know where the story ends up before you know how it started.&lt;&#x2F;p&gt;
&lt;p&gt;I thought it would be fun to build a text adventure in Scheme. So I did!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-game&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-game&quot; aria-label=&quot;Anchor link for: the-game&quot;&gt;The Game&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;It’s small. Four rooms, directional movement, a game loop. No items, no puzzles, no win condition. Just the skeleton of a world you can walk around in.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;define&lt;&#x2F;span&gt;&lt;span&gt; rooms&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;list&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;living-room&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;font-style: italic;&quot;&gt;You are in the living room. There is a door to the north and a kitchen to the west.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;kitchen&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;font-style: italic;&quot;&gt;You are in the kitchen. There is a living room to the east.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;hallway&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;font-style: italic;&quot;&gt;You are in the hallway. There is a living room to the south and a bedroom to the north.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;bedroom&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;font-style: italic;&quot;&gt;You are in the bedroom. There is a hallway to the south.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Rooms are lists. Connections between rooms are lists. The game state is which room you’re in.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E2E2E5; background-color: #1E1E1E;&quot;&gt;&lt;code data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;define&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;game-loop&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;describe-current-room&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;command &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;get-command&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;cond&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;eq?&lt;&#x2F;span&gt;&lt;span&gt; command &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;north&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span&gt;move &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;north&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;          ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;eq?&lt;&#x2F;span&gt;&lt;span&gt; command &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;south&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span&gt;move &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;south&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;          ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;eq?&lt;&#x2F;span&gt;&lt;span&gt; command &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;east&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span&gt;move &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;east&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;          ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;eq?&lt;&#x2F;span&gt;&lt;span&gt; command &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;west&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span&gt;move &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;west&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;          ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;eq?&lt;&#x2F;span&gt;&lt;span&gt; command &lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;quit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;display&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;font-style: italic;&quot;&gt;Goodbye!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;                                (&lt;&#x2F;span&gt;&lt;span&gt;newline&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;                                (&lt;&#x2F;span&gt;&lt;span&gt;exit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;          (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FF9800;&quot;&gt;else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;font-weight: bold;&quot;&gt;display&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B1D631;font-style: italic;&quot;&gt;Invalid command.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;font-style: italic;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;                (&lt;&#x2F;span&gt;&lt;span&gt;newline&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span&gt;game-loop&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7E8AA2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The game loop is a recursive function that describes where you are, reads a command, acts on it, then calls itself. No &lt;code&gt;while&lt;&#x2F;code&gt; loop, no mutable iterator. Just a function that calls itself forever until you quit.&lt;&#x2F;p&gt;
&lt;p&gt;It’s a good reminder that you don’t need much to make something interactive. Four rooms and a prompt. That’s a world you can walk around in.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iamseeley&#x2F;just-scheme-things&#x2F;blob&#x2F;main&#x2F;adventure.scm&quot;&gt;full source&lt;&#x2F;a&gt; is about 60 lines. There’s also a &lt;a rel=&quot;noopener nofollow external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iamseeley&#x2F;just-scheme-things&#x2F;blob&#x2F;main&#x2F;tic-tac-toe.scm&quot;&gt;tic-tac-toe&lt;&#x2F;a&gt; in the same repo from the same weekend.&lt;&#x2F;p&gt;

&lt;hr&gt;
&lt;p&gt;&lt;code&gt;scheme&lt;/code&gt; &lt;code&gt;book&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tseeley.com/about/&quot;&gt;Thomas Seeley&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/tseeley.com&quot;&gt;@tseeley.com on Bluesky&lt;/a&gt;, &lt;a href=&quot;https://mastodon.social/@iamseeley&quot;&gt;@iamseeley on Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/iamseeley&quot;&gt;@iamseeley on GitHub&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
</feed>
