<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on Cameron Little's Website</title><link>https://camlittle.com/posts/</link><description>Recent posts on Cameron Little's Website</description><image><title>Posts on Cameron Little's Website</title><url>https://camlittle.com/profile.jpg?s=144</url><link>https://camlittle.com/posts/</link><width>144</width></image><language>en-us</language><managingEditor>cameron@camlittle.com (Cameron Little)</managingEditor><webMaster>cameron@camlittle.com (Cameron Little)</webMaster><atom:link href="https://camlittle.com/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>MYOG Quilt</title><link>https://camlittle.com/posts/2025-05-08-myog-quilt/</link><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2025-05-08-myog-quilt/</guid><enclosure url="https://camlittle.com/posts/2025-05-08-myog-quilt/thumbnail.jpeg" length="27910" type="image/jpeg"/><description>&lt;p>I recently made my own backpacking quilt. This was the largest &lt;abbr title="make your own gear">MYOG&lt;/abbr> project I&amp;rsquo;ve attempted, and I&amp;rsquo;m satisfied with how it turned out.&lt;/p>
&lt;h2 id="materials">&lt;a class="heading-link" href="#materials">&lt;/a>Materials&lt;/h2>
&lt;p>I&amp;rsquo;ve used Ripstop by the Roll in the past, and I still think they&amp;rsquo;re best general source for MYOG projects (wide selection, good pricing, although shipping isn&amp;rsquo;t great).&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://ripstopbytheroll.com/products/0-66-oz-membrane-10-taffeta-nylon-1">0.66 oz MEMBRANE 10 Taffeta Nylon&lt;/a>&lt;/strong>: One of the lightest options, fully downproof, and a nice feeling&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://ripstopbytheroll.com/products/0-5-oz-ns50-noseeum-mesh">0.5 oz Noseeum Mesh&lt;/a>&lt;/strong>: I cut 3.5&amp;quot; strips from this, as it&amp;rsquo;s lighter than the precut baffle tape&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://ripstopbytheroll.com/products/gutermann-tera-80-thread">Gutermann Tera 80 thread&lt;/a>&lt;/strong>&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://ripstopbytheroll.com/products/mil-spec-nylon-grosgrain-ribbon">Grosgrain ribbon&lt;/a>&lt;/strong>: 3/8&amp;quot; and 3/4&amp;quot; widths&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://ripstopbytheroll.com/products/low-profile-center-push-buckle">Low profile center push buckles&lt;/a>&lt;/strong>&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://ripstopbytheroll.com/products/shock-cord">1/16&amp;quot; Shock Cord&lt;/a>&lt;/strong>&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://ripstopbytheroll.com/products/anchorable-cordlock">Anchorable Cordlock&lt;/a>&lt;/strong> (glow-in-the-dark, so I can find them at night)&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.amazon.com/dp/B073T8NQT5?ref_=ppx_hzsearch_conn_dt_b_fed_asin_title_1">900 FP goose down&lt;/a>&lt;/strong> (from Amazon)&lt;/li>
&lt;/ul>
&lt;p>I considered 0.56 oz nylon, but my research concluded the weight savings weren&amp;rsquo;t worth the durability decrease.&lt;/p></description><content:encoded><![CDATA[
  
<p>I recently made my own backpacking quilt. This was the largest <abbr title="make your own gear">MYOG</abbr> project I&rsquo;ve attempted, and I&rsquo;m satisfied with how it turned out.</p>
<h2 id="materials"><a class="heading-link" href="#materials"></a>Materials</h2>
<p>I&rsquo;ve used Ripstop by the Roll in the past, and I still think they&rsquo;re best general source for MYOG projects (wide selection, good pricing, although shipping isn&rsquo;t great).</p>
<ul>
<li><strong><a href="https://ripstopbytheroll.com/products/0-66-oz-membrane-10-taffeta-nylon-1">0.66 oz MEMBRANE 10 Taffeta Nylon</a></strong>: One of the lightest options, fully downproof, and a nice feeling</li>
<li><strong><a href="https://ripstopbytheroll.com/products/0-5-oz-ns50-noseeum-mesh">0.5 oz Noseeum Mesh</a></strong>: I cut 3.5&quot; strips from this, as it&rsquo;s lighter than the precut baffle tape</li>
<li><strong><a href="https://ripstopbytheroll.com/products/gutermann-tera-80-thread">Gutermann Tera 80 thread</a></strong></li>
<li><strong><a href="https://ripstopbytheroll.com/products/mil-spec-nylon-grosgrain-ribbon">Grosgrain ribbon</a></strong>: 3/8&quot; and 3/4&quot; widths</li>
<li><strong><a href="https://ripstopbytheroll.com/products/low-profile-center-push-buckle">Low profile center push buckles</a></strong></li>
<li><strong><a href="https://ripstopbytheroll.com/products/shock-cord">1/16&quot; Shock Cord</a></strong></li>
<li><strong><a href="https://ripstopbytheroll.com/products/anchorable-cordlock">Anchorable Cordlock</a></strong> (glow-in-the-dark, so I can find them at night)</li>
<li><strong><a href="https://www.amazon.com/dp/B073T8NQT5?ref_=ppx_hzsearch_conn_dt_b_fed_asin_title_1">900 FP goose down</a></strong> (from Amazon)</li>
</ul>
<p>I considered 0.56 oz nylon, but my research concluded the weight savings weren&rsquo;t worth the durability decrease.</p>
<p>The goose down is my main risk on this project. It&rsquo;s not treated to handle water and is from an unknown source, but is way cheaper than <a href="https://ripstopbytheroll.com/products/hyperdry-850-fp-dwr-goose-down">RSBR&rsquo;s 850 DWR down</a>. Since I&rsquo;m currently in a drier climate, I&rsquo;m taking a risk on moisture management. Although I think the supplier shorted me a bit on weight (I think the 1 lb includes the bag it&rsquo;s shipped in), I&rsquo;m reasonably happy with it.</p>
<h2 id="patterning"><a class="heading-link" href="#patterning"></a>Patterning</h2>
<p>I mostly did my own patterning. <a href="https://dubberdesigns.com/downloads/down-quilt-pattern-generator/">Dubber Designs has a generator</a>, but I wasn&rsquo;t able to import it properly into Apple Numbers or Google Sheets. It did provide some good guidance on down calculations. <a href="https://cdn.shopify.com/s/files/1/0261/6507/files/How_to_Make_a_Quilt.pdf?v=1727186891">Ripstop by the Roll has a one-pager</a>, but I didn&rsquo;t do a full taper like they do.</p>
<p><a href="https://www.reddit.com/r/myog/comments/w4im76/partial_box_baffle_quilt_membrane_7_noseeum_900fp/">vanCapare</a>, <a href="https://www.reddit.com/r/myog/comments/13x3si1/first_down_backpacking_quilt/">HollowCartographer</a>, and <a href="https://www.reddit.com/r/myog/comments/zdhlip/made_my_own_ultralight_sleeping_bag_quilt/">durianbae</a> were good sources of inspiration on Reddit.</p>
<p><a href="https://docs.google.com/spreadsheets/d/12EN9bKKhYVg3KWGNHM6rn6EtfmkH-3rTnbDq5mud7SA/edit?usp=sharing">My rough down calculations are in Google Sheets.</a></p>
<p>Some of my decisions were:</p>
<ul>
<li>Long enough to fully cover my head. I generally sleep with my head fully covered and didn&rsquo;t want to construct a separate hood.</li>
<li>Partial taper around the legs. I get some weight savings this way, and it accommodates my side-sleeping.</li>
<li>Four sleeping pad straps spaced along the body.</li>
<li>Fully closed footbox. This means I don&rsquo;t have a cinch at the foot, and prevents slipping off the pad.</li>
</ul>
<p>My final measurements:</p>
<ul>
<li>50&quot; width</li>
<li>78&quot; height</li>
<li>34&quot; long taper, down to 35&quot; wide at the toes</li>
<li>Baffles are 3&quot; thick and approximately 5&quot; wide (10 vertical baffles, 6 horizontal baffles, and 2 in the footbox)</li>
<li>~10% overstuff</li>
</ul>
<p>
<img src="./notes.jpeg" alt="Handwritten notes and design" />

</p>
<p>This required two shapes, cut twice for the top and bottom. The arc in the footbox&rsquo;s length is the same length as the width of the bottom of the main body.</p>
<p>
<img src="./pattern.png" alt="Pattern" />

</p>
<h2 id="baffles"><a class="heading-link" href="#baffles"></a>Baffles</h2>
<p>Baffle design is a deep topic on its own, with <a href="https://www.reddit.com/r/Ultralight/comments/lbvrtv/battle_of_the_baffles_horizontal_vertical_chevron/">a lot of options</a>.</p>
<p>I seriously considered Karo baffles, but because I have experience sewing box baffles (from a pair of down pants I made my wife a few Christmases ago), I was confident enough to attempt them again.</p>
<p>I decided on a split vertical/horizontal design: upper body vertical and lower body horizontal. This seems to be the most popular option for DIY projects. Vertical avoids down clumping on the sides while sleeping, but it does make stitching a more complex (since you&rsquo;re sewing along the long edge in a narrow tube). Horizontal is easier to construct, but down can clump at the sides when sleeping.</p>
<p>I do think chevron or diagonal designs could have benefits, but the complexity and margin for error seem quite a bit higher.</p>
<h2 id="construction"><a class="heading-link" href="#construction"></a>Construction</h2>
<p>Construction of box baffling requires thinking inside out.</p>
<ol>
<li>I started by sewing the box baffles on the wrong side of the bottom side layer of fabric. I tried to curve along the baffle material from my seam allowance to the center at the edges of my fabric, which adds a rounded finished edge.</li>
</ol>
<p>
<img src="./construction-2.jpeg" alt="first six sewn" />

</p>
<ol start="2">
<li>I then sewed the footbox&rsquo;s arc onto the base of the main body of the quilt.</li>
</ol>
<p>
<img src="./construction-3.jpeg" alt="with footbox" />

</p>
<ol start="3">
<li>Then, I sewed along the edge of the entire quilt except the top edge, wrong side to wrong side. This includes my straps, ensuring they&rsquo;re on the inside. I now have an inside-out quilt, with box baffles sewn to one side, with raw edges exposed. When I flip this right side out, the edges are nicely finished, and my straps are exposed.</li>
</ol>
<p>
<img src="./construction-5-straps.jpeg" alt="Straps" />

</p>
<ol start="4">
<li>To fill with down, I used a length of PVC and PEX. I 3D-printed a removable cap for the PVC and a pusher to attach to the PEX. I filled and measured down into the PVC on a scale, slid the open end into my space to fill, removed the cap, and pushed the down into the cavity.</li>
</ol>
<p>
<img src="./construction-4.jpeg" alt="Down setup" />

</p>
<ol start="5">
<li>The next step is the most tedious. Working inside out from the base of the quilt, I sewed the unattached side of each box baffle to the wrong side of the top fabric. When I had 5 or so inches left to sew, I paused (with my needle down) and filled with the correct amount of down. Marking my lines was pretty tricky here, as the quilt was no longer a flat shape.</li>
</ol>
<p>
<img src="./construction-6.jpeg" alt="Sewing the baffles" />

</p>
<ol start="6">
<li>To finish the top edge I sewed a straight stitch, leaving a rough edge exposed. I then attached 3/8&quot; grosgrain loops with my cordlocks attached. I then made a strip of bias tape with a double fold and finished ends, and sewed it on to cover the rough edge. Finally, I fed my shock cord through to finish it off.</li>
</ol>
<p>
<img src="./construction-7.jpeg" alt="Finished top edge" />

</p>
<h2 id="final-product"><a class="heading-link" href="#final-product"></a>Final product</h2>
<p>The final product is around 22 oz and was more than warm enough for under 40℉.</p>
<p>
<img src="./complete-bottom.jpeg" alt="Completed bottom" />

</p>
<p>
<img src="./complete-top.jpeg" alt="Completed top" />

</p>
<h2 id="learnings"><a class="heading-link" href="#learnings"></a>Learnings</h2>
<ul>
<li>The most common error I made was letting the fabric fold under itself while sewing, meaning I was sewing into an additional layer. I needed to pick this out and redo it each time.</li>
<li>It was really difficult to mark up and keep accurate lines when I was partially through sewing. I&rsquo;d prefer to figure out a method to fully mark out sewing lines while the fabric is flat. I need to find something better than chalk for this, as it needs to hold up through the several days of sewing.</li>
<li>I&rsquo;m not sure if the head cinch is necessary, based on how I sleep. Similarly, I don&rsquo;t know if I need the strap closest to my head.</li>
<li>If I were to redo this, I&rsquo;d go with a shorter baffle with more overstuffing, to avoid down movement and save a little weight.</li>
</ul>
<p>
<img src="./in-use.jpeg" alt="In use" />

</p>


  
  ]]></content:encoded></item><item><title>Bridging SwiftUI and Core Animations</title><link>https://camlittle.com/posts/2024-11-14-swiftui-core-animation/</link><pubDate>Thu, 14 Nov 2024 00:00:00 +0000</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2024-11-14-swiftui-core-animation/</guid><enclosure url="https://camlittle.com/posts/2024-11-14-swiftui-core-animation/thumbnail.png" length="16221" type="image/png"/><description>&lt;p>A project I&amp;rsquo;ve recently started heavily uses Core Animation. The app runs on macOS and iOS and relies on a custom CALayer subclass. Apple has provided &lt;a href="https://developer.apple.com/documentation/swiftui/unifying-your-app-s-animations">bridges between SwiftUI and AppKit or UIKit animations&lt;/a> but doesn&amp;rsquo;t provide guidance on how to bridge to Core Animation.&lt;/p>
&lt;p>I&amp;rsquo;ve been working on solving this problem. Though it&amp;rsquo;s not perfect, this post describes what I&amp;rsquo;ve come up with.&lt;/p>
&lt;p>For convenience, the example code in this post uses UIKit only and is simplified to focus on the essentials. Full code (runnable on iOS and macOS) can be found in the &lt;a href="https://github.com/apexskier/swiftui-coreanimation">sample code GitHub repository&lt;/a>. I assume familiarity with SwiftUI and Core Animation basics in this article, as Apple and others provide good documentation.&lt;/p></description><content:encoded><![CDATA[
  
<p>A project I&rsquo;ve recently started heavily uses Core Animation. The app runs on macOS and iOS and relies on a custom CALayer subclass. Apple has provided <a href="https://developer.apple.com/documentation/swiftui/unifying-your-app-s-animations">bridges between SwiftUI and AppKit or UIKit animations</a> but doesn&rsquo;t provide guidance on how to bridge to Core Animation.</p>
<p>I&rsquo;ve been working on solving this problem. Though it&rsquo;s not perfect, this post describes what I&rsquo;ve come up with.</p>
<p>For convenience, the example code in this post uses UIKit only and is simplified to focus on the essentials. Full code (runnable on iOS and macOS) can be found in the <a href="https://github.com/apexskier/swiftui-coreanimation">sample code GitHub repository</a>. I assume familiarity with SwiftUI and Core Animation basics in this article, as Apple and others provide good documentation.</p>
<h2 id="architecture"><a class="heading-link" href="#architecture"></a>Architecture</h2>
<p>First, we need to connect SwiftUI to the Core Animation layer. This uses <a href="https://developer.apple.com/documentation/swiftui/uiviewrepresentable"><code>UIViewRepresentable</code></a> and <a href="https://developer.apple.com/documentation/uikit/uiview"><code>UIView</code></a> (AppKit&rsquo;s <code>NS</code> equivalents on macOS).</p>
<pre class="mermaid">flowchart TD
    SwiftUI
    ViewRepresentable[NSViewRepresentable/UIViewRepresentable]
    View[NSView/UIView]
    CALayer

    SwiftUI --> ViewRepresentable --> View --> CALayer
</pre>

<h2 id="data-flow"><a class="heading-link" href="#data-flow"></a>Data flow</h2>
<p>Our animatable property can be passed through each of these layers. This is simpler with one-way data binding, but by providing access to the SwiftUI binding through the <code>UIViewRepresentable</code>&rsquo;s coordinator, my example code is bidirectional.</p>
<p>Here&rsquo;s how our animatable <code>value</code> is passed from SwiftUI into our <code>CALayer</code>&rsquo;s draw function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">struct</span> <span class="nc">ContentView</span><span class="p">:</span> <span class="n">some</span> <span class="n">View</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">@</span><span class="n">State</span> <span class="kd">var</span> <span class="nv">value</span> <span class="p">=</span> <span class="mf">0.0</span> <span class="c1">// source of truth</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nv">body</span><span class="p">:</span> <span class="n">some</span> <span class="n">View</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">CustomSwiftUIView</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">struct</span> <span class="nc">CustomSwiftUIView</span><span class="p">:</span> <span class="n">UIViewRepresentable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">@</span><span class="n">Binding</span> <span class="kd">var</span> <span class="nv">value</span><span class="p">:</span> <span class="nb">Double</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">makeUIView</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="n">Context</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="n">CustomPlatformView</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">view</span> <span class="p">=</span> <span class="n">CustomPlatformView</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// this link allows our UIView to write value (and read if needed outside of standard SwiftUI updates)</span>
</span></span><span class="line"><span class="cl">        <span class="n">view</span><span class="p">.</span><span class="n">delegate</span> <span class="p">=</span> <span class="n">context</span><span class="p">.</span><span class="n">coordinator</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">view</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">updateUIView</span><span class="p">(</span><span class="kc">_</span> <span class="n">uiView</span><span class="p">:</span> <span class="n">CustomPlatformView</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// this &#34;pushes&#34; changes into our CALayer reactively, following standard SwiftUI updates</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">view</span><span class="p">.</span><span class="n">layer</span> <span class="k">as</span><span class="p">?</span> <span class="n">CustomLayer</span><span class="p">)?.</span><span class="n">update</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">with</span><span class="p">:</span> <span class="n">context</span><span class="p">.</span><span class="n">transaction</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">value</span><span class="p">:</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// The coordinator provides access to the value binding from CustomPlatformView</span>
</span></span><span class="line"><span class="cl">    <span class="kd">class</span> <span class="nc">Coordinator</span><span class="p">:</span> <span class="n">NSObject</span><span class="p">,</span> <span class="n">CustomDelegate</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nv">parent</span><span class="p">:</span> <span class="n">CustomSwiftUIView</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">init</span><span class="p">(</span><span class="kc">_</span> <span class="n">parent</span><span class="p">:</span> <span class="n">CustomSwiftUIView</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kc">self</span><span class="p">.</span><span class="n">parent</span> <span class="p">=</span> <span class="n">parent</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">makeCoordinator</span><span class="p">()</span> <span class="p">-&gt;</span> <span class="n">Coordinator</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Coordinator</span><span class="p">(</span><span class="kc">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">CustomPlatformView</span><span class="p">:</span> <span class="n">UIView</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// this coordinator provides read/write access to the value</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// for example, a custom gesture recognizer could update the binding</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="n">CustomSwiftUIView</span><span class="p">.</span><span class="n">Coordinator</span><span class="p">?</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">override</span> <span class="kd">class</span> <span class="nc">var</span> <span class="n">layerClass</span><span class="p">:</span> <span class="nb">AnyClass</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">CustomLayer</span><span class="p">.</span><span class="kc">self</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">CustomLayer</span><span class="p">:</span> <span class="n">CALayer</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">@NSManaged</span> <span class="kd">var</span> <span class="nv">value</span><span class="p">:</span> <span class="n">CGFloat</span> <span class="c1">// animatable property</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">override</span> <span class="kd">func</span> <span class="nf">draw</span><span class="p">(</span><span class="k">in</span> <span class="n">ctx</span><span class="p">:</span> <span class="n">CGContext</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// use `value` as needed</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">update</span><span class="p">(</span><span class="n">with</span> <span class="n">transaction</span><span class="p">:</span> <span class="n">SwiftUI</span><span class="p">.</span><span class="n">Transaction</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">Double</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// we&#39;ll fill this in later to respect animations</span>
</span></span><span class="line"><span class="cl">        <span class="kc">self</span><span class="p">.</span><span class="n">value</span> <span class="p">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>To summarize why we need each copy of <code>value</code>:</p>
<ul>
<li><code>ContentView</code>: This is our main source of truth and should be considered the canonical one. SwiftUI and most reactive UI frameworks require top-down state passing.</li>
<li><code>CustomSwiftUIView</code>: Also inherent in SwiftUI is the data propagation model: passing properties throughout the hierarchy with a single source of truth. Because this is a binding, we can read and write it, but it could be a plain <code>var</code> if read-only access is needed.</li>
<li><code>CustomLayer</code>: This is needed for Core Animation to store intermediate values representing state mid-animation. Core Animation duplicates this again within the <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/CoreAnimationBasics/CoreAnimationBasics.html#//apple_ref/doc/uid/TP40004514-CH2-SW19">&ldquo;model&rdquo; and &ldquo;presentation&rdquo; parts of the layer tree</a>. Writing to this value directly will result in state drift, you must use the <code>Coordinator</code> instead.</li>
</ul>
<p>Note that we do not need to store the <code>value</code> inside the <code>UIView</code> layer. (In theory, we could move the drawing code into the <code>UIView</code>, but that kind of defeats the point of this post.) Instead, <code>UIView</code> relies on the <code>Coordinator</code>.</p>
<h2 id="animation"><a class="heading-link" href="#animation"></a>Animation</h2>
<p>Now that we can read and write our animatable value, we want to animate it. Our goal is to drive all animation from a single place. Additionally, we&rsquo;d like to use the most modern methods. For these reasons, and because we have to, we&rsquo;ll use SwiftUI syntax.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="n">Button</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">withAnimation</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span> <span class="p">=</span> <span class="mf">0.5</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">label</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Text</span><span class="p">(</span><span class="s">&#34;Animate Value&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="custom-calayer-properties"><a class="heading-link" href="#custom-calayer-properties"></a>Custom CALayer Properties</h3>
<p>First, we need to enable Core Animation for our custom <code>value</code> property. Two things are required:</p>
<ol>
<li>Make <code>value</code> dynamic by specifying <code>@NSManaged</code></li>
<li>Ensure Core Animation redraws when <code>value</code> changes</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">CustomLayer</span><span class="p">:</span> <span class="n">CALayer</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">private</span> <span class="kd">let</span> <span class="nv">animationKeyPath</span> <span class="p">=</span> <span class="err">\</span><span class="n">CustomLayer</span><span class="p">.</span><span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">private</span> <span class="kd">var</span> <span class="nv">nsAnimationKeyPath</span><span class="p">:</span> <span class="nb">String</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">NSExpression</span><span class="p">(</span><span class="n">forKeyPath</span><span class="p">:</span> <span class="n">animationKeyPath</span><span class="p">).</span><span class="n">keyPath</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="kr">@NSManaged</span> <span class="kd">var</span> <span class="nv">value</span><span class="p">:</span> <span class="n">CGFloat</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="kr">override</span> <span class="kd">class</span> <span class="nc">func</span> <span class="n">needsDisplay</span><span class="p">(</span><span class="n">forKey</span> <span class="n">key</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">Bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">key</span> <span class="p">==</span> <span class="n">nsAnimationKeyPath</span> <span class="o">||</span> <span class="kc">super</span><span class="p">.</span><span class="n">needsDisplay</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="bridging-transactions"><a class="heading-link" href="#bridging-transactions"></a>Bridging Transactions</h3>
<p>The core component of this technique is using SwiftUI&rsquo;s <a href="https://developer.apple.com/documentation/swiftui/transaction"><code>Transaction</code></a> to determine if an animation is active when a property is changed. SwiftUI provides this in the context of a view representable&rsquo;s update, which we can pass directly into our custom <code>CALayer</code>&rsquo;s <code>update</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">struct</span> <span class="nc">CustomSwiftUIView</span><span class="p">:</span> <span class="n">UIViewRepresentable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">updateUIView</span><span class="p">(</span><span class="kc">_</span> <span class="n">uiView</span><span class="p">:</span> <span class="n">CustomPlatformView</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">uiView</span><span class="p">.</span><span class="n">layer</span> <span class="k">as</span><span class="p">?</span> <span class="n">CustomLayer</span><span class="p">)?.</span><span class="n">update</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">with</span><span class="p">:</span> <span class="n">context</span><span class="p">.</span><span class="n">transaction</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">value</span><span class="p">:</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">CustomLayer</span><span class="p">:</span> <span class="n">CALayer</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">private</span> <span class="kd">let</span> <span class="nv">animationKey</span> <span class="p">=</span> <span class="s">&#34;value-animation-key&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">func</span> <span class="nf">update</span><span class="p">(</span><span class="n">with</span> <span class="n">transaction</span><span class="p">:</span> <span class="n">SwiftUI</span><span class="p">.</span><span class="n">Transaction</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">Double</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// additional animation management is omitted</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="kd">let</span> <span class="nv">animation</span> <span class="p">=</span> <span class="n">transaction</span><span class="p">.</span><span class="n">animation</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// create Core Animation CAAnimation from transaction.animation</span>
</span></span><span class="line"><span class="cl">            <span class="kd">let</span> <span class="nv">caAnimation</span><span class="p">:</span> <span class="n">CAAnimation</span> <span class="p">=</span> <span class="p">...</span>
</span></span><span class="line"><span class="cl">            <span class="n">add</span><span class="p">(</span><span class="n">caAnimation</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="n">animationKey</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="bridging-animations"><a class="heading-link" href="#bridging-animations"></a>Bridging Animations</h3>
<p>Now, we need to transform the <code>SwiftUI.Animation</code> into a <code>CAAnimation</code>. This is a little more complicated. <code>SwiftUI.Animation</code> is fairly opaque. It doesn&rsquo;t provide access to the type of animation, its configuration, or any way to execute itself as a function.</p>
<p>Instead, we use the fairly hacky technique of parsing the animation&rsquo;s <a href="https://developer.apple.com/documentation/Swift/CustomStringConvertible"><code>description</code></a> and extracting parameters from it.</p>
<p>This is fairly straightforward for spring animations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="n">nonisolated</span><span class="p">(</span><span class="n">unsafe</span><span class="p">)</span> <span class="kd">let</span> <span class="nv">fluidSpringAnimationRegex</span> <span class="p">=</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="o">/</span><span class="n">FluidSpringAnimation</span><span class="err">\</span><span class="p">(</span><span class="n">response</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">response</span><span class="p">&gt;</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">dampingFraction</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">dampingFraction</span><span class="p">&gt;</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">blendDuration</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">blendDuration</span><span class="p">&gt;</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">)</span><span class="err">\</span><span class="p">)</span><span class="o">/</span><span class="p">#</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="kd">let</span> <span class="nv">springParameters</span> <span class="p">=</span> <span class="k">try</span> <span class="n">fluidSpringAnimationRegex</span><span class="p">.</span><span class="n">wholeMatch</span><span class="p">(</span><span class="k">in</span><span class="p">:</span> <span class="n">description</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nv">caAnimation</span> <span class="p">=</span> <span class="n">CASpringAnimation</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nv">response</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="nb">Float</span><span class="p">(</span><span class="n">springParameters</span><span class="p">.</span><span class="n">response</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nv">dampingFraction</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="nb">Float</span><span class="p">(</span><span class="n">springParameters</span><span class="p">.</span><span class="n">dampingFraction</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nv">omegaN</span> <span class="p">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="p">.</span><span class="n">pi</span> <span class="o">/</span> <span class="n">response</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">stiffness</span> <span class="p">=</span> <span class="n">omegaN</span> <span class="o">*</span> <span class="n">omegaN</span> <span class="o">*</span> <span class="n">mass</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">damping</span> <span class="p">=</span> <span class="n">dampingFraction</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">caAnimation</span><span class="p">.</span><span class="n">stiffness</span> <span class="o">*</span> <span class="n">caAnimation</span><span class="p">.</span><span class="n">mass</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">caAnimation</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nonisolated</span><span class="p">(</span><span class="n">unsafe</span><span class="p">)</span> <span class="kd">let</span> <span class="nv">springAnimationRegex</span> <span class="p">=</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="o">/</span><span class="n">SpringAnimation</span><span class="err">\</span><span class="p">(</span><span class="n">mass</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">mass</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">stiffness</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">stiffness</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">damping</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">damping</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">initialVelocity</span><span class="p">:</span> <span class="n">SwiftUI</span><span class="p">.</span><span class="n">_Velocity</span><span class="p">&lt;</span><span class="n">Swift</span><span class="p">.</span><span class="nb">Double</span><span class="p">&gt;</span><span class="err">\</span><span class="p">(</span><span class="n">valuePerSecond</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">initialVelocity</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">)</span><span class="err">\</span><span class="p">)</span><span class="err">\</span><span class="p">)</span><span class="o">/</span><span class="p">#</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="kd">let</span> <span class="nv">springParameters</span> <span class="p">=</span> <span class="k">try</span> <span class="n">springAnimationRegex</span><span class="p">.</span><span class="n">wholeMatch</span><span class="p">(</span><span class="k">in</span><span class="p">:</span> <span class="n">description</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nv">caAnimation</span> <span class="p">=</span> <span class="n">CASpringAnimation</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">mass</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="nb">Float</span><span class="p">(</span><span class="n">springParameters</span><span class="p">.</span><span class="n">mass</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">stiffness</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="nb">Float</span><span class="p">(</span><span class="n">springParameters</span><span class="p">.</span><span class="n">stiffness</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">damping</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="nb">Float</span><span class="p">(</span><span class="n">springParameters</span><span class="p">.</span><span class="n">damping</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">initialVelocity</span> <span class="p">=</span> <span class="n">CGFloat</span><span class="p">(</span><span class="nb">Float</span><span class="p">(</span><span class="n">springParameters</span><span class="p">.</span><span class="n">initialVelocity</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">caAnimation</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And we can fairly easily recreate Apple&rsquo;s default animation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="k">if</span> <span class="n">description</span> <span class="p">==</span> <span class="s">&#34;DefaultAnimation()&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// defaults changed in more recent versions</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="cp">#available</span><span class="p">(</span><span class="cp">iOS</span> <span class="mi">17</span><span class="p">,</span> <span class="cp">macOS</span> <span class="mi">14</span><span class="p">,</span> <span class="cp">tvOS</span> <span class="mi">17</span><span class="p">,</span> <span class="cp">watchOS</span> <span class="mi">10</span><span class="p">,</span> <span class="o">*</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">caAnimation</span> <span class="p">=</span> <span class="n">CASpringAnimation</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">caAnimation</span><span class="p">.</span><span class="n">apply</span><span class="p">(</span><span class="n">response</span><span class="p">:</span> <span class="mf">0.55</span><span class="p">,</span> <span class="n">dampingFraction</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">caAnimation</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nv">caAnimation</span> <span class="p">=</span> <span class="n">CABasicAnimation</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">caAnimation</span><span class="p">.</span><span class="n">timingFunction</span> <span class="p">=</span> <span class="p">.</span><span class="kd">init</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="p">.</span><span class="n">easeInEaseOut</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">caAnimation</span><span class="p">.</span><span class="n">duration</span> <span class="p">=</span> <span class="mf">0.35</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">caAnimation</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>However, traditional bezier animation curves are more difficult. SwiftUI produces descriptions that can be regex matched using:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="p">#</span><span class="o">/</span><span class="n">BezierAnimation</span><span class="err">\</span><span class="p">(</span><span class="n">duration</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">duration</span><span class="p">&gt;</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">curve</span><span class="p">:</span> <span class="err">\</span><span class="p">(</span><span class="kd">extension</span> <span class="nc">in</span> <span class="n">SwiftUI</span><span class="err">\</span><span class="p">):(?&lt;</span><span class="n">cubicSolver</span><span class="p">&gt;</span><span class="n">SwiftUI</span><span class="p">.</span><span class="n">UnitCurve</span><span class="p">.</span><span class="n">CubicSolver</span><span class="err">\</span><span class="p">(</span><span class="n">ax</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">ax</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">bx</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">bx</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">cx</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">cx</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">ay</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">ay</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">by</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">by</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">),</span> <span class="n">cy</span><span class="p">:</span> <span class="p">(?&lt;</span><span class="n">cy</span><span class="p">&gt;</span><span class="o">-?</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="err">\</span><span class="p">.</span><span class="err">\</span><span class="n">d</span><span class="o">+</span><span class="p">)</span><span class="err">\</span><span class="p">))</span><span class="err">\</span><span class="p">)</span><span class="o">/</span><span class="p">#</span>
</span></span></code></pre></div><p><del>However, I haven&rsquo;t yet been able to figure out what the cubic solver is and how to convert its three two-dimensional coordinates (<code>(ax, ay), (bx, by), (cx, cy)</code>) into a bezier curve&rsquo;s two two-dimensional control points (<a href="https://developer.apple.com/documentation/quartzcore/camediatimingfunction/1522235-init"><code>CAMediaTimingFunction.init(controlPoints:_:_:_:)</code></a>).</del></p>
<p><strong>Update 2024-12-10</strong></p>
<p>Thanks to Eric from <a href="https://createwithplay.com">Play</a>, I now know how to convert SwiftUI&rsquo;s <code>CubicSolver</code> back to the original Bezier control points.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">pt1</span> <span class="p">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">x</span><span class="p">:</span> <span class="n">cx</span> <span class="o">/</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">y</span><span class="p">:</span> <span class="n">cy</span> <span class="o">/</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">pt2</span> <span class="p">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">x</span><span class="p">:</span> <span class="p">(</span><span class="n">bx</span> <span class="o">+</span> <span class="n">cx</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span> <span class="o">+</span> <span class="n">pt1</span><span class="p">.</span><span class="n">x</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">y</span><span class="p">:</span> <span class="p">(</span><span class="n">by</span> <span class="o">+</span> <span class="n">cy</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span> <span class="o">+</span> <span class="n">pt1</span><span class="p">.</span><span class="n">y</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">caAnimation</span><span class="p">.</span><span class="n">timingFunction</span> <span class="p">=</span> <span class="p">.</span><span class="kd">init</span><span class="p">(</span><span class="n">controlPoints</span><span class="p">:</span> <span class="n">pt1</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">pt1</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">pt2</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">pt2</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
</span></span></code></pre></div><p>Previously, I resorted to explicitly matching against standard timing curves I can explicitly create using SwiftUI and Core Animation. For example, ease in&rsquo;s cubic solver is:</p>
<pre tabindex="0"><code>SwiftUI.UnitCurve.CubicSolver(ax: -0.7400000000000002, bx: 0.4800000000000002, cx: 1.26, ay: -2.0, by: 3.0, cy: 0.0)
</code></pre><p>And the Core Animation instance is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">caAnimation</span> <span class="p">=</span> <span class="n">CABasicAnimation</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">caAnimation</span><span class="p">.</span><span class="n">duration</span> <span class="p">=</span> <span class="nb">Double</span><span class="p">(</span><span class="n">bezierParameters</span><span class="p">.</span><span class="n">duration</span><span class="p">)</span><span class="o">!</span>
</span></span><span class="line"><span class="cl"><span class="k">switch</span> <span class="n">bezierParameters</span><span class="p">.</span><span class="n">cubicSolver</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="n">easeInAnimationCubicSolver</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">timingFunction</span> <span class="p">=</span> <span class="p">.</span><span class="kd">init</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="p">.</span><span class="n">easeIn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="n">caAnimation</span>
</span></span></code></pre></div><p>Once the animation is created, we configure the keypath it modifies, and its start and end values without our layer&rsquo;s <code>update</code> function. We also need to ensure we update the spring animation&rsquo;s duration, as it depends on the value&rsquo;s range. Core Animation will cut off the animation without this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kc">self</span><span class="p">.</span><span class="n">value</span> <span class="p">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="kd">let</span> <span class="nv">caAnimation</span> <span class="p">=</span> <span class="n">transaction</span><span class="p">.</span><span class="n">animation</span><span class="p">?.</span><span class="n">caAnimation</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">keyPath</span> <span class="p">=</span> <span class="kc">Self</span><span class="p">.</span><span class="n">nsAnimationKeyPath</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">fromValue</span> <span class="p">=</span> <span class="p">(</span><span class="n">presentation</span><span class="p">()</span> <span class="p">??</span> <span class="kc">self</span><span class="p">).</span><span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="n">caAnimation</span><span class="p">.</span><span class="n">toValue</span> <span class="p">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="kd">let</span> <span class="nv">caAnimation</span> <span class="p">=</span> <span class="n">caAnimation</span> <span class="k">as</span><span class="p">?</span> <span class="n">CASpringAnimation</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Core Animation will cut off the animation if the duration is not updated</span>
</span></span><span class="line"><span class="cl">        <span class="n">caAnimation</span><span class="p">.</span><span class="n">duration</span> <span class="p">=</span> <span class="n">caAnimation</span><span class="p">.</span><span class="n">settlingDuration</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">add</span><span class="p">(</span><span class="n">caAnimation</span><span class="p">,</span> <span class="n">forKey</span><span class="p">:</span> <span class="kc">Self</span><span class="p">.</span><span class="n">animationKey</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="final-result"><a class="heading-link" href="#final-result"></a>Final Result</h2>
<p>For my final demo, I want to draw something that shows <code>value</code> animating. I&rsquo;ll draw a circle that will animate through the vertical center axis of the view. I&rsquo;ll also print the value as text to visually guarantee it&rsquo;s changing.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">diameter</span> <span class="p">=</span> <span class="mf">20.0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">override</span> <span class="kd">func</span> <span class="nf">draw</span><span class="p">(</span><span class="k">in</span> <span class="n">ctx</span><span class="p">:</span> <span class="n">CGContext</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ctx</span><span class="p">.</span><span class="n">clear</span><span class="p">(</span><span class="n">bounds</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// set graphics context to support NSString drawing</span>
</span></span><span class="line"><span class="cl">    <span class="n">UIGraphicsPushContext</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;</span><span class="si">\(</span><span class="n">value</span><span class="si">)</span><span class="s">&#34;</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="k">in</span><span class="p">:</span> <span class="n">bounds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">withAttributes</span><span class="p">:</span> <span class="n">fontAttributes</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ctx</span><span class="p">.</span><span class="n">setFillColor</span><span class="p">(</span><span class="n">CGColor</span><span class="p">(</span><span class="n">red</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">green</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">blue</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">alpha</span><span class="p">:</span> <span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">ctx</span><span class="p">.</span><span class="n">fillEllipse</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="k">in</span><span class="p">:</span> <span class="n">CGRect</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">origin</span><span class="p">:</span> <span class="n">CGPoint</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">x</span><span class="p">:</span> <span class="p">(</span><span class="n">bounds</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">diameter</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">y</span><span class="p">:</span> <span class="p">(</span><span class="n">bounds</span><span class="p">.</span><span class="n">height</span> <span class="o">-</span> <span class="n">diameter</span><span class="p">)</span> <span class="o">*</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">size</span><span class="p">:</span> <span class="n">CGSize</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="n">diameter</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="n">diameter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Another feature I want to show is bidirectional data binding. When you click or tap on the custom view, it will interrupt the animation and set the parent to the current state, to keep everything in sync. We can also animate changes from the internal view using the same SwiftUI <code>withAnimation</code> function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">handleTap</span><span class="p">(</span><span class="n">gestureRecognizer</span><span class="p">:</span> <span class="n">UIGestureRecognizer</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">guard</span> <span class="kd">let</span> <span class="nv">coordinator</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">coordinator</span><span class="p">.</span><span class="n">parent</span><span class="p">.</span><span class="n">value</span> <span class="p">=</span> <span class="p">(</span><span class="n">presentation</span><span class="p">()</span> <span class="p">??</span> <span class="kc">self</span><span class="p">).</span><span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="n">removeAnimation</span><span class="p">(</span><span class="n">forKey</span><span class="p">:</span> <span class="kc">Self</span><span class="p">.</span><span class="n">animationKey</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">@objc</span> <span class="kd">func</span> <span class="nf">handleDoubleTap</span><span class="p">(</span><span class="n">gestureRecognizer</span><span class="p">:</span> <span class="n">UIGestureRecognizer</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">guard</span> <span class="kd">let</span> <span class="nv">coordinator</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">withAnimation</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">coordinator</span><span class="p">.</span><span class="n">parent</span><span class="p">.</span><span class="n">value</span> <span class="p">=</span> <span class="p">(</span><span class="n">presentation</span><span class="p">()</span> <span class="p">??</span> <span class="kc">self</span><span class="p">).</span><span class="n">value</span> <span class="o">&gt;</span> <span class="mf">0.5</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Complete code with an example app and Xcode project can be found at <a href="https://github.com/apexskier/swiftui-coreanimation">https://github.com/apexskier/swiftui-coreanimation</a>.</p>
<figure>
    <video src="https://content.camlittle.com/swiftui-caanimation%20-%20swiftui-caanimation%20-%202024-11-14%20at%2017.41.53-converted.mp4" width="420" controls muted></video>
    <figcaption>Final demo of animation</figcaption>
</figure>
<h2 id="taking-it-further"><a class="heading-link" href="#taking-it-further"></a>Taking it further</h2>
<p>I&rsquo;m pretty happy with this approach, especially the ability to drive animations from multiple layers in the stack using the same mechanism. However, it&rsquo;s not ideal for a few reasons.</p>
<p>From <a href="https://developer.apple.com/documentation/swiftui/animation/spring">the SwiftUI <code>spring</code> documentation</a>:</p>
<blockquote>
<p>When mixed with other <code>spring()</code> or <code>interactiveSpring()</code> animations on the same property, each animation will be replaced by their successor, preserving velocity from one animation to the next. Optionally blends the response values between springs over a time period.</p>
</blockquote>
<p>My approach does neither of these things. In theory, when an animated update propagates while an animation is in progress, we could look up the current animation and set the new animation&rsquo;s initial velocity appropriately. The blocker is the opaqueness of CAAnimations, which don&rsquo;t provide APIs for access to value or velocity at a given time.</p>
<p>Blending response values is even more difficult, as it would require dynamically changing <code>CAAnimation</code>s over time, which isn&rsquo;t supported.</p>
<p><del>As mentioned above, I haven&rsquo;t figured out SwiftUI&rsquo;s <code>CubicSolver</code>, which I think is a private struct implementing <code>UnitCurve</code>. It represents at least bezier curves, but I haven&rsquo;t figured out the math or how it works. If you have any ideas, please get in touch!</del></p>
<p>Ultimately, these issues are due to the closed nature of SwiftUI and other Apple UIs. At the core, animation curves are math. Standard curves are simple functions over time (<code>f(time) = value</code>). Springs are more complex since they&rsquo;re semi-stateful by taking the animated value into account, but are also deterministic. I&rsquo;d like to be able to directly call these functions. Unfortunately, <code>CABasicAnimation</code> has no support for this. At first glance at the documentation, SwiftUI&rsquo;s <code>Animation</code> appears to support this with <a href="https://developer.apple.com/documentation/swiftui/animation/animate(value:time:context:)"><code>func animate&lt;V&gt;(value: V, time: TimeInterval, context: inout AnimationContext&lt;V&gt;) -&gt; V?</code></a>. However, I have not found a way to get ahold of an <code>AnimationContext</code> outside of a custom Animation subclass, which makes this non-viable. SwiftUI&rsquo;s <code>UnitCurve</code> has <a href="https://developer.apple.com/documentation/swiftui/unitcurve/value(at:)"><code>value(at:)</code></a> and <a href="https://developer.apple.com/documentation/swiftui/unitcurve/velocity(at:)"><code>velocity(at:)</code></a> functions that are exactly what I want, but don&rsquo;t have a spring equivalent, and <code>UnitCurve</code>s aren&rsquo;t accessible from <code>Animation</code>. I could directly implement timing curve functions, but I&rsquo;d prefer something built-in to avoid building it from scratch.</p>
<p><strong>Update 2024-12-10</strong></p>
<p>In additional to figuring out <code>CubicSolver</code> as mentioned above, I&rsquo;ve extended the <a href="https://github.com/apexskier/swiftui-coreanimation">demo project on GitHub</a> to show SwiftUI and CoreAnimation side by side, so you can visually confirm the animations are the same. SwiftUI has noticeable lag, but overall this approach works.</p>
<figure>
    <video src="https://content.camlittle.com/iPhone%2016%20Pro%20-%20Simulator%20-%202024-12-10%20at%2018.08.44.mp4" width="420" controls muted></video>
    <figcaption>Final demo of animation</figcaption>
</figure>


  
  ]]></content:encoded></item><item><title>Christmas Movies 2023</title><link>https://camlittle.com/posts/2023-12-26-christmas-movies-2023/</link><pubDate>Tue, 26 Dec 2023 00:00:00 +0000</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2023-12-26-christmas-movies-2023/</guid><enclosure url="https://camlittle.com/posts/2023-12-26-christmas-movies-2023/thumbnail.png" length="26671" type="image/png"/><description>&lt;p>My wife and I (and her parents) tend to watch a lot of Christmas movies this time of year. Here are my rankings of those we got to this year:&lt;/p>
&lt;h3 id="time-datetime2023-12-131213time---the-claus-familyhttpsenwikipediaorgwikithe_claus_family">&lt;a class="heading-link" href="#time-datetime2023-12-131213time---the-claus-familyhttpsenwikipediaorgwikithe_claus_family">&lt;/a>&lt;time datetime="2023-12-13">12/13&lt;/time> - &lt;a href="https://en.wikipedia.org/wiki/The_Claus_Family">The Claus Family&lt;/a>&lt;/h3>
&lt;p>Especially considering the &lt;em>two&lt;/em> sequels, not very good. Pretty unconvincing and to-be-expected storylines, both about Santa Claus and saving the biscuit factory. The humor wasn&amp;rsquo;t great either, but this was probably made worse by watching a dub.&lt;/p></description><content:encoded><![CDATA[
  
<p>My wife and I (and her parents) tend to watch a lot of Christmas movies this time of year. Here are my rankings of those we got to this year:</p>
<h3 id="time-datetime2023-12-131213time---the-claus-familyhttpsenwikipediaorgwikithe_claus_family"><a class="heading-link" href="#time-datetime2023-12-131213time---the-claus-familyhttpsenwikipediaorgwikithe_claus_family"></a><time datetime="2023-12-13">12/13</time> - <a href="https://en.wikipedia.org/wiki/The_Claus_Family">The Claus Family</a></h3>
<p>Especially considering the <em>two</em> sequels, not very good. Pretty unconvincing and to-be-expected storylines, both about Santa Claus and saving the biscuit factory. The humor wasn&rsquo;t great either, but this was probably made worse by watching a dub.</p>
<p>★☆☆☆☆</p>
<h3 id="time-datetime2023-12-161216time---family-switchhttpsenwikipediaorgwikifamily_switch"><a class="heading-link" href="#time-datetime2023-12-161216time---family-switchhttpsenwikipediaorgwikifamily_switch"></a><time datetime="2023-12-16">12/16</time> - <a href="https://en.wikipedia.org/wiki/Family_Switch">Family Switch</a></h3>
<p>Family <em>Freaky Friday</em>, but not as good. Tenuous connection to Christmas. Lots of writing that had the appearance of jokes.</p>
<p>★☆☆☆☆</p>
<h3 id="time-datetime2023-12-161216time---love-hardhttpsenwikipediaorgwikilove_hard"><a class="heading-link" href="#time-datetime2023-12-161216time---love-hardhttpsenwikipediaorgwikilove_hard"></a><time datetime="2023-12-16">12/16</time> - <a href="https://en.wikipedia.org/wiki/Love_Hard">Love Hard</a></h3>
<p>Pretty good Christmas romantic comedy. The writing and acting were quite good in general, and the characters had more dimension than most rom-coms, especially Josh.</p>
<p>★★★★☆</p>
<h3 id="time-datetime2023-12-171217time---holiday-in-the-vineyardshttpswwwrottentomatoescommholiday_in_the_vineyards"><a class="heading-link" href="#time-datetime2023-12-171217time---holiday-in-the-vineyardshttpswwwrottentomatoescommholiday_in_the_vineyards"></a><time datetime="2023-12-17">12/17</time> - <a href="https://www.rottentomatoes.com/m/holiday_in_the_vineyards">Holiday in the Vineyards</a></h3>
<p>Pretty standard Hallmark-style Christmas romantic comedy, meaning not much comedy and a stretched connection to Christmas (who has a real estate deadline on Christmas Eve?). The son of a fancy wine company has to sneak around a small town to get inside info on buying the vineyard. Falls in love with a sexy local wine lady, and has to reveal his lies. Some gratuitous shirtless abs, and way too public sexy dancing.</p>
<p>★★☆☆☆</p>
<h3 id="time-datetime2023-12-181218time---christmas-with-youhttpsenwikipediaorgwikichristmas_with_you_film"><a class="heading-link" href="#time-datetime2023-12-181218time---christmas-with-youhttpsenwikipediaorgwikichristmas_with_you_film"></a><time datetime="2023-12-18">12/18</time> - <a href="https://en.wikipedia.org/wiki/Christmas_with_You_(film)">Christmas with You</a></h3>
<p>A slight deviation from the standard format, an aging pop star surprises a fan for the holidays, falls in love with the dad, and writes a hit Christmas song with him. Not terrible, but not one I need to watch again.</p>
<p>★★★☆☆</p>
<h3 id="time-datetime2023-12-251225time---candy-cane-lanehttpsenwikipediaorgwikicandy_cane_lane_film"><a class="heading-link" href="#time-datetime2023-12-251225time---candy-cane-lanehttpsenwikipediaorgwikicandy_cane_lane_film"></a><time datetime="2023-12-25">12/25</time> - <a href="https://en.wikipedia.org/wiki/Candy_Cane_Lane_(film)">Candy Cane Lane</a></h3>
<p>This one was a wild ride. A neighborhood&rsquo;s Christmas decoration contest escalates dramatically. This one was laugh-out-loud funny and I found the animation for the figurines quite effective.</p>
<p>★★★★★</p>


  
  ]]></content:encoded></item><item><title>Simple `struct` dependency mocking in Go</title><link>https://camlittle.com/posts/2022-09-25-go-struct-mocking/</link><pubDate>Sun, 25 Sep 2022 00:00:00 +0000</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2022-09-25-go-struct-mocking/</guid><enclosure url="https://camlittle.com/posts/2022-09-25-go-struct-mocking/thumbnail.png" length="10079" type="image/png"/><description>&lt;p>I work with many people using Go for the first time, and one of the common struggles I see is testing code that depends on external &lt;code>struct&lt;/code> types.&lt;/p>
&lt;p>Let&amp;rsquo;s start with a simple (though contrived) example. I&amp;rsquo;m writing some code that depends on the &lt;a href="https://github.com/google/go-github">go-github&lt;/a> library to interact with the GitHub API.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">before&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/google/go-github/v47/github&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">obj&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">user&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">gh&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">OrganizationsService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">obj&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">obj&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">user&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">gh&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">nil&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">Organizations&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">m&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">obj&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">CountOrganizations&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">orgs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">gh&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">List&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;couldn&amp;#39;t list organizations: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">orgs&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">m&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">obj&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">IsMemberOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">org&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">gh&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IsMember&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">org&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;couldn&amp;#39;t call IsMember: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>How do you test this? &lt;code>github.OrganizationsService&lt;/code> is a &lt;code>struct&lt;/code> type, so we&amp;rsquo;re required to have a direct instance of it to pass type checking. In many cases, this will force your tests to make network calls. Usually, you can pass &lt;code>http.Client&lt;/code> and override its behavior, but that gets messy fast.&lt;/p></description><content:encoded><![CDATA[
  
<p>I work with many people using Go for the first time, and one of the common struggles I see is testing code that depends on external <code>struct</code> types.</p>
<p>Let&rsquo;s start with a simple (though contrived) example. I&rsquo;m writing some code that depends on the <a href="https://github.com/google/go-github">go-github</a> library to interact with the GitHub API.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">before</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;context&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/google/go-github/v47/github&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">obj</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">user</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">gh</span>   <span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">OrganizationsService</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">New</span><span class="p">(</span><span class="nx">user</span> <span class="kt">string</span><span class="p">)</span> <span class="nx">obj</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">obj</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">user</span><span class="p">:</span> <span class="nx">user</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">gh</span><span class="p">:</span>   <span class="nx">github</span><span class="p">.</span><span class="nf">NewClient</span><span class="p">(</span><span class="kc">nil</span><span class="p">).</span><span class="nx">Organizations</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">m</span> <span class="o">*</span><span class="nx">obj</span><span class="p">)</span> <span class="nf">CountOrganizations</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">orgs</span><span class="p">,</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">m</span><span class="p">.</span><span class="nx">gh</span><span class="p">.</span><span class="nf">List</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">m</span><span class="p">.</span><span class="nx">user</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;couldn&#39;t list organizations: %w&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="nx">orgs</span><span class="p">),</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">m</span> <span class="o">*</span><span class="nx">obj</span><span class="p">)</span> <span class="nf">IsMemberOf</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">org</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">bool</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">result</span><span class="p">,</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">m</span><span class="p">.</span><span class="nx">gh</span><span class="p">.</span><span class="nf">IsMember</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">org</span><span class="p">,</span> <span class="nx">m</span><span class="p">.</span><span class="nx">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;couldn&#39;t call IsMember: %w&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">result</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>How do you test this? <code>github.OrganizationsService</code> is a <code>struct</code> type, so we&rsquo;re required to have a direct instance of it to pass type checking. In many cases, this will force your tests to make network calls. Usually, you can pass <code>http.Client</code> and override its behavior, but that gets messy fast.</p>
<h2 id="setup"><a class="heading-link" href="#setup"></a>Setup</h2>
<p>The key to testing this behavior is to depend on an <code>interface</code> type instead of the <code>struct</code>. Go makes this easy, which you may not know if you&rsquo;re used to other languages. In Go, an interface describes behavior that both your code <em>or dependency code</em> can implement. This feature means we can define an interface in our code that <code>github.OrganizationService</code> implements and the compiler will recognize it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">githubOrganizations</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">obj</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">user</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">gh</span>   <span class="nx">githubOrganizations</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I usually start with an empty interface and add methods as needed. Doing this helps keep my interface as clean as possible, which makes understanding code when refactoring or changing dependencies later easier.</p>
<p>Swapping <code>*github.OrganizationsService</code> for <code>githubOrganizations</code> works because all structs conform to the empty interface. However, I now see errors wherever I use <code>.gh</code>: <code>m.gh.List undefined (type githubOrganizations has no field or method List)</code>.</p>
<p>We need to add each of the undefined methods to the interface. If I&rsquo;m too lazy to look at the docs, I&rsquo;ll temporarily swap <code>githubOrganizations</code> back to <code>*github.OrganizationsService</code>, jump to the definition of the previously undefined method and port it over.</p>
<p>
<img src="https://content.camlittle.com/2022-09-24-go-mocking-interface-filling.mp4" alt="Filling example" />

</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">githubOrganizations</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nf">List</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">user</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">opts</span> <span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">ListOptions</span><span class="p">)</span> <span class="p">([]</span><span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Organization</span><span class="p">,</span> <span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Response</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nf">IsMember</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">org</span><span class="p">,</span> <span class="nx">user</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">bool</span><span class="p">,</span> <span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Response</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="writing-a-test"><a class="heading-link" href="#writing-a-test"></a>Writing a test</h2>
<p>Now let&rsquo;s use this interface to write a test verifying <code>CountOrganizations</code> works as expected.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">TestCountOrganizations</span><span class="p">(</span><span class="nx">t</span> <span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">obj</span> <span class="o">:=</span> <span class="nx">obj</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">gh</span><span class="p">:</span> <span class="nx">mockGHList</span><span class="p">{},</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">c</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">obj</span><span class="p">.</span><span class="nf">CountOrganizations</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">c</span> <span class="o">!=</span> <span class="mi">3</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">t</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;unexpected number of organizations: %d&#34;</span><span class="p">,</span> <span class="nx">c</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here&rsquo;s the real magic: <code>mockGHList</code> is a <code>struct</code> that embeds a pointer to <code>github.OrganizationsService</code>, which we originally depended on.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">mockGHList</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">OrganizationsService</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Go pointer semantics and struct embedding rules mean all methods of <code>*github.OrganizationsService</code> can be called on <code>mockGHList</code>, even when the embedded value is <code>nil</code>. This allows us to run the test without compilation issues, but because <code>github.OrganizationService</code> is <code>nil</code>, the test panics.</p>
<pre tabindex="0"><code>--- FAIL: TestCountOrganizations (0.00s)
...
github.com/google/go-github/v47/github.(*OrganizationsService).List
</code></pre><p>A <code>nil</code> pointer was found when trying to call <code>.List</code> on the <code>nil</code> <code>*github.OrganizationsService</code>. Go struct embedding rules mean that methods defined on the <code>mockGHList</code> object override those on the nested <code>*github.OrganizationsService</code> object. This rule lets us write our mock method.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">mockGHList</span><span class="p">)</span> <span class="nf">List</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="kt">string</span><span class="p">,</span> <span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">ListOptions</span><span class="p">)</span> <span class="p">([]</span><span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Organization</span><span class="p">,</span> <span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Response</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="p">[]</span><span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Organization</span><span class="p">{{},</span> <span class="p">{},</span> <span class="p">{}},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Rerun the test, and it passes!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="o">---</span> <span class="nx">PASS</span><span class="p">:</span> <span class="nf">TestCountOrganizations</span> <span class="p">(</span><span class="mf">0.00</span><span class="nx">s</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="benefits"><a class="heading-link" href="#benefits"></a>Benefits</h2>
<h3 id="single-use-minimal-mocks"><a class="heading-link" href="#single-use-minimal-mocks"></a>Single-use, minimal mocks</h3>
<p>By wrapping the original struct, we only need to implement the methods our tests directly depend on. This encourages single-use, minimal mock objects instead of manually maintaining large shared mocks.</p>
<p>Any hardcoded mock codifies its expectations of how specific scenarios behave, usually the first tests it&rsquo;s used in. Those expectations make it harder to use for new tests. As the codebase grows, it either doesn&rsquo;t thoroughly test new behavior or combinatorially increases in complexity (by introducing [boolean flags](<a href="https://martinfowler.com/bliki/FlagArgument.html">https://martinfowler.com/bliki/FlagArgument.html</a>, for example). At some point, the mock deserves its own tests to track what it&rsquo;s doing.</p>
<h3 id="fail-by-default"><a class="heading-link" href="#fail-by-default"></a>Fail-by-default</h3>
<p>This pattern fails when executed code makes unexpected method calls, which can help catch accidental changes.</p>
<h3 id="mocking-libraries"><a class="heading-link" href="#mocking-libraries"></a>Mocking libraries</h3>
<p>I&rsquo;ve also successfully used this pattern with third-party mocking libraries.</p>
<p>Here&rsquo;s an example of use with <a href="https://github.com/stretchr/testify#mock-package"><code>&quot;github.com/stretchr/testify/mock&quot;</code></a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">mockGHIsMember</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">mock</span><span class="p">.</span><span class="nx">Mock</span>
</span></span><span class="line"><span class="cl">	<span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">OrganizationsService</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">m</span> <span class="o">*</span><span class="nx">mockGHIsMember</span><span class="p">)</span> <span class="nf">IsMember</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">org</span><span class="p">,</span> <span class="nx">user</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">bool</span><span class="p">,</span> <span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Response</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">args</span> <span class="o">:=</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Called</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">org</span><span class="p">,</span> <span class="nx">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">args</span><span class="p">.</span><span class="nf">Bool</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="nx">args</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="mi">1</span><span class="p">).(</span><span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Response</span><span class="p">),</span> <span class="nx">args</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">TestIsMember</span><span class="p">(</span><span class="nx">t</span> <span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">gh</span> <span class="o">:=</span> <span class="nx">mockGHIsMember</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">obj</span> <span class="o">:=</span> <span class="nx">obj</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">user</span><span class="p">:</span> <span class="s">&#34;apexskier&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">gh</span><span class="p">:</span>   <span class="o">&amp;</span><span class="nx">gh</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">gh</span><span class="p">.</span><span class="nf">On</span><span class="p">(</span><span class="s">&#34;IsMember&#34;</span><span class="p">,</span> <span class="nx">mock</span><span class="p">.</span><span class="nf">MatchedBy</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">true</span> <span class="p">}),</span> <span class="s">&#34;org&#34;</span><span class="p">,</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">user</span><span class="p">).</span><span class="nf">Return</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span> <span class="p">(</span><span class="o">*</span><span class="nx">github</span><span class="p">.</span><span class="nx">Response</span><span class="p">)(</span><span class="kc">nil</span><span class="p">),</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">result</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">obj</span><span class="p">.</span><span class="nf">IsMemberOf</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="s">&#34;org&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">require</span><span class="p">.</span><span class="nf">NoError</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">assert</span><span class="p">.</span><span class="nf">True</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I still only need to mock the method being called.</p>
<h2 id="caveats"><a class="heading-link" href="#caveats"></a>Caveats</h2>
<ul>
<li>Defining an interface does not work when using struct fields since interfaces can only declare methods.</li>
<li>Auto-complete won&rsquo;t show other methods available on the original <code>struct</code> type. <a href="https://pkg.go.dev">https://pkg.go.dev</a> is your friend, or you can manually declare a variable of the original type and use auto-complete on that.</li>
</ul>
<hr>
<p>You can view the code from this post at <a href="https://github.com/apexskier/go-struct-mocking-demo/">https://github.com/apexskier/go-struct-mocking-demo/</a>.</p>


  
  ]]></content:encoded></item><item><title>Don't merge that TODO</title><link>https://camlittle.com/posts/2021-07-24-dont-merge-todos/</link><pubDate>Sat, 24 Jul 2021 00:00:00 +0000</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2021-07-24-dont-merge-todos/</guid><enclosure url="https://camlittle.com/posts/2021-07-24-dont-merge-todos/thumbnail.png" length="2279" type="image/png"/><description>&lt;p>One of my standard code review comments is, &amp;ldquo;Why is this a TODO&amp;rdquo;? I believe you should always avoid TODO comments in your code. They&amp;rsquo;re a familiar pattern, but they defer responsibility and make problems harder to solve.&lt;/p>
&lt;p>In most cases, the TODO should be filed in your team&amp;rsquo;s issue tracker immediately and cross-linked with the pull request. Some people have processes that track issues using TODOs, but I don&amp;rsquo;t believe source code works well for issue tracking. Source code represents the current state of your product. Reviewing historical context, though possible with version control, is not the primary interface. Issue tracking systems are built for prioritization, tracking context, linking related issues, and assigning responsibility.&lt;/p></description><content:encoded><![CDATA[
  
<p>One of my standard code review comments is, &ldquo;Why is this a TODO&rdquo;? I believe you should always avoid TODO comments in your code. They&rsquo;re a familiar pattern, but they defer responsibility and make problems harder to solve.</p>
<p>In most cases, the TODO should be filed in your team&rsquo;s issue tracker immediately and cross-linked with the pull request. Some people have processes that track issues using TODOs, but I don&rsquo;t believe source code works well for issue tracking. Source code represents the current state of your product. Reviewing historical context, though possible with version control, is not the primary interface. Issue tracking systems are built for prioritization, tracking context, linking related issues, and assigning responsibility.</p>
<p>TODOs are usually used to defer technical debt. Moving them to your issue tracker&rsquo;s backlog allows prioritization against feature work by putting them in the same playing field and can be used as a metric to understand the depth of technical debt.</p>
<p>TODOs often describe an expected future change. It&rsquo;s tough to predict the future, so the TODO might not make sense when the next person reads them. I still encourage using comments to describe the problem and the reasons for not addressing it, but not to prescribe action. In the future, you&rsquo;ll have much better context when deciding how to fix the issue, or you might be able to just delete it.</p>
<p>Even though I don&rsquo;t like seeing TODOs in a codebase, I often use them in my feature branches and scan through the diff once I&rsquo;ve opened my draft PR to decide if it&rsquo;s ready for review. They&rsquo;re lighter weight than an issue tracker and can act as a checklist for a single task. Generally, though, they&rsquo;re an indicator of poor quality and have more responsible alternatives.</p>


  
  ]]></content:encoded></item><item><title>Personalized container debugging</title><link>https://camlittle.com/posts/2021-06-24-docker-toolbox/</link><pubDate>Thu, 24 Jun 2021 08:00:00 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2021-06-24-docker-toolbox/</guid><enclosure url="https://camlittle.com/posts/2021-06-24-docker-toolbox/feature-terminal-screenshot.png" length="88396" type="image/png"/><description>&lt;p>In my work and personal life, most of the services I deploy these days run in containers, and I need to debug them frequently. Last year, I set up some automation to maintain a &amp;ldquo;toolbox&amp;rdquo; docker image that&amp;rsquo;s pre-configured with my &lt;a href="https://github.com/apexskier/dotfiles">dotfiles&lt;/a> and the tools I use day-to-day.&lt;/p>
&lt;p>Generally, I try to follow standard conventions in my personal configuration. I try to use the command line in a way that I don&amp;rsquo;t feel like I&amp;rsquo;m constantly stumbling in unfamiliar environments like remote servers, docker images such as &lt;a href="https://hub.docker.com/_/node">&lt;code>node&lt;/code>&lt;/a> or &lt;a href="https://hub.docker.com/_/golang">&lt;code>golang&lt;/code>&lt;/a>, or a fresh Raspberry Pi. This practice also makes it much easier to help my coworkers—I can work within their terminal or help instruct them without needing to translate commands. It can be painful to watch someone with muscle memory for custom &lt;a rel="noreferrer" href="https://gist.github.com/mwhite/6887990#my-aliases">git aliases&lt;/a> or non-standard shells working in a freshly provisioned EC2 instance.&lt;/p></description><content:encoded><![CDATA[
  
<p>In my work and personal life, most of the services I deploy these days run in containers, and I need to debug them frequently. Last year, I set up some automation to maintain a &ldquo;toolbox&rdquo; docker image that&rsquo;s pre-configured with my <a href="https://github.com/apexskier/dotfiles">dotfiles</a> and the tools I use day-to-day.</p>
<p>Generally, I try to follow standard conventions in my personal configuration. I try to use the command line in a way that I don&rsquo;t feel like I&rsquo;m constantly stumbling in unfamiliar environments like remote servers, docker images such as <a href="https://hub.docker.com/_/node"><code>node</code></a> or <a href="https://hub.docker.com/_/golang"><code>golang</code></a>, or a fresh Raspberry Pi. This practice also makes it much easier to help my coworkers—I can work within their terminal or help instruct them without needing to translate commands. It can be painful to watch someone with muscle memory for custom <a rel="noreferrer" href="https://gist.github.com/mwhite/6887990#my-aliases">git aliases</a> or non-standard shells working in a freshly provisioned EC2 instance.</p>
<p>Even considering these benefits, a lot of customization provides more than enough value to be worthwhile. A familiar shell makes the debugging experience much more pleasant, and installing the tools I need to investigate takes time. I&rsquo;ve also spent years polishing my bash prompt, and I love using it.</p>
<p>
<img src="./feature-terminal-screenshot.png" alt="Screenshot of my terminal running the docker toolbox" />

</p>
<p>My most common use case is debugging docker and Kubernetes networking behavior. For example, I can spin up a pod in our cluster running the toolbox image and use <code>curl</code> and <code>dig</code> to verify DNS behavior.</p>
<p>I also occasionally need to run an image that takes a long time to start or run using a wrapper utility that doesn&rsquo;t bind ports on my host. In this case, I can run my toolbox within a local docker network and connect to the image without needing to restart it or dig into the internals of how it&rsquo;s initialized.</p>
<p>Keeping my toolbox image up to date is automated directly in my <a href="https://github.com/apexskier/dotfiles/">dotfiles repository</a>, using the same scripts I use when setting up a new computer. Whenever I push to the default branch, a <a href="https://github.com/apexskier/dotfiles/actions/workflows/dockerpublish.yml">GitHub action</a> builds the image and publishes it to GitHub&rsquo;s container registry<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>The docker image builds from <code>debian:stable-slim</code>, copies my dotfiles, runs my installer and setup scripts, then runs <code>bash</code>. Putting this together was a nice way to verify the cross-compatibility of my setup. The only serious issue I ran into was ensuring my setup scripts could run non-interactively. I also took the opportunity to double-check that my dotfiles don&rsquo;t contain too much personal information (like a preconfigured git user) to let other people use my setup without accidentally impersonating me.</p>
<p>Try it yourself by running</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">docker run --rm -it ghcr.io/apexskier/dotfiles/toolbox
</span></span></code></pre></div><p>You can <a href="https://github.com/apexskier/dotfiles#docker-image">read more about it</a> and see <a href="https://github.com/apexskier/dotfiles/blob/main/Dockerfile">exactly what&rsquo;s installed</a> by visiting my dotfiles.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I also publish to Docker Hub, which I used before GitHub supported anonymous container access.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>


  
  ]]></content:encoded></item><item><title>Type Unions in Swift</title><link>https://camlittle.com/posts/type-unions-in-swift/</link><pubDate>Sun, 14 Mar 2021 00:00:00 +0000</pubDate><guid isPermaLink="true">https://camlittle.com/posts/type-unions-in-swift/</guid><enclosure url="https://camlittle.com/posts/type-unions-in-swift/thumbnail.png" length="32207" type="image/png"/><description>&lt;p>Type unions are one of my favorite patterns to enforce stricter typing in a codebase. The basic idea is to allow a single type to represent a value that is one of a set of different, distinct types.&lt;/p>
&lt;p>One of my frequent use cases is iteration over a collection of varied objects. A type union allows me to avoid complicated merging and ordering logic and use simple conditional logic within the iteration body. It’s also used commonly in APIs I interact with, where a single resource can produce different but similar things, or &lt;a href="https://camlittle.com/posts/2020-04-03-typing-tricks-to-reduce-ambiguity/#type-unions">I want to reduce ambiguity between conditional fields&lt;/a>.&lt;/p></description><content:encoded><![CDATA[
  
<p>Type unions are one of my favorite patterns to enforce stricter typing in a codebase. The basic idea is to allow a single type to represent a value that is one of a set of different, distinct types.</p>
<p>One of my frequent use cases is iteration over a collection of varied objects. A type union allows me to avoid complicated merging and ordering logic and use simple conditional logic within the iteration body. It’s also used commonly in APIs I interact with, where a single resource can produce different but similar things, or <a href="https://camlittle.com/posts/2020-04-03-typing-tricks-to-reduce-ambiguity/#type-unions">I want to reduce ambiguity between conditional fields</a>.</p>
<p>Type unions can be a stronger pattern than inheritance. In class-based inheritance, the possible set of types is unbounded, while type unions have an explicit set known at compile time. In languages like TypeScript (and Swift, as we’ll see), sub-types of a type union can be unrelated and have distinct inheritance chains.</p>
<h2 id="in-other-languages"><a class="heading-link" href="#in-other-languages"></a>In other languages</h2>
<p>Type unions are simple in weakly typed languages like JavaScript or Python<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> since a variable can contain any value. Strongly typed languages take different approaches to support them.</p>
<h3 id="typescript"><a class="heading-link" href="#typescript"></a>TypeScript</h3>
<p>TypeScript has first-class support with <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types">union types</a>. Type narrowing is done with <a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates">type predicates and custom functions</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Rabbit</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">eat</span><span class="p">(</span><span class="nx">food</span>: <span class="kt">Carrot</span><span class="p">)</span><span class="o">:</span> <span class="k">void</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kr">class</span> <span class="nx">Dog</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">pet() {</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Turtle</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;turtle&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">type</span> <span class="nx">Pet</span> <span class="o">=</span> <span class="nx">Rabbit</span> <span class="o">|</span> <span class="nx">Dog</span> <span class="o">|</span> <span class="nx">Turtle</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">whatIsIt</span><span class="p">(</span><span class="nx">pet</span>: <span class="kt">Pet</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">pet</span> <span class="o">==</span> <span class="s2">&#34;string&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s2">&#34;turtle&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">pet</span> <span class="k">instanceof</span> <span class="nx">Dog</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">pet</span><span class="p">.</span><span class="nx">pet</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s2">&#34;dog&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s2">&#34;rabbit&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>TypeScript is nice because it automatically casts the inspected variable&rsquo;s type: within the conditional blocks, <code>pet</code> assumes the narrowed type. TypeScript automatically infers common attributes of the sub-types, allowing access to the common <code>name</code> property (<code>pet.name</code>).</p>
<h3 id="kotlin"><a class="heading-link" href="#kotlin"></a>Kotlin</h3>
<p>Kotlin supports type unions with <a href="https://kotlinlang.org/docs/sealed-classes.html">sealed classes</a>. Type narrowing is done with <a href="https://kotlinlang.org/docs/sealed-classes.html#sealed-classes-and-when-expression">when expressions</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">sealed</span> <span class="k">class</span> <span class="nc">Pet</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Rabbit</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="k">val</span> <span class="py">name</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="k">val</span> <span class="py">carrot</span><span class="p">:</span> <span class="n">Double</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="p">:</span> <span class="n">Pet</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">fun</span> <span class="nf">eat</span><span class="p">(</span><span class="n">food</span><span class="p">:</span> <span class="n">Carrot</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Dog</span><span class="p">(</span><span class="k">val</span> <span class="py">name</span><span class="p">:</span> <span class="n">String</span><span class="p">)</span> <span class="p">:</span> <span class="n">Pet</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">fun</span> <span class="nf">pet</span><span class="p">()</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">object</span> <span class="nc">Turtle</span> <span class="p">:</span> <span class="n">Pet</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">val</span> <span class="py">name</span><span class="p">:</span> <span class="n">String</span> <span class="p">=</span> <span class="s2">&#34;turtle&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">whatIsIt</span><span class="p">(</span><span class="n">pet</span><span class="p">:</span> <span class="n">Pet</span><span class="p">):</span> <span class="n">String</span> <span class="p">=</span>
</span></span><span class="line"><span class="cl">	<span class="k">when</span> <span class="p">(</span><span class="n">pet</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">is</span> <span class="n">Rabbit</span> <span class="o">-&gt;</span> <span class="s2">&#34;rabbit&#34;</span>
</span></span><span class="line"><span class="cl">		<span class="k">is</span> <span class="n">Dog</span> <span class="o">-&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">pet</span><span class="p">.</span><span class="n">pet</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">			<span class="s2">&#34;dog&#34;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="k">is</span> <span class="n">Turtle</span> <span class="o">-&gt;</span> <span class="s2">&#34;turtle&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>Like TypeScript, variable types are automatically narrowed. Kotlin does not automatically infer common attributes, but they can be defined on the base sealed class, following standard inheritance patterns.</p>
<h2 id="in-swift"><a class="heading-link" href="#in-swift"></a>In Swift</h2>
<p>The core construct for a type union in Swift is an <a href="https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html#ID148">enumeration with associated values</a>. Type narrowing is done with <code>case</code> statements.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">protocol</span> <span class="nc">Rabbit</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nv">name</span><span class="p">:</span> <span class="nb">String</span> <span class="p">{</span> <span class="kr">get</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="kd">func</span> <span class="nf">eat</span><span class="p">(</span><span class="n">food</span><span class="p">:</span> <span class="n">Carrot</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">struct</span> <span class="nc">Dog</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nv">name</span><span class="p">:</span> <span class="nb">String</span>
</span></span><span class="line"><span class="cl">	<span class="kd">func</span> <span class="nf">pet</span><span class="p">()</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">typealias</span> <span class="n">Turtle</span> <span class="p">=</span> <span class="nb">String</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">enum</span> <span class="nc">Pet</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">rabbit</span><span class="p">(</span><span class="n">Rabbit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">dog</span><span class="p">(</span><span class="n">Dog</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">turtle</span><span class="p">(</span><span class="n">Turtle</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">whatIsIt</span><span class="p">(</span><span class="n">pet</span><span class="p">:</span> <span class="n">Pet</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">switch</span> <span class="n">pet</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="p">.</span><span class="n">rabbit</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;rabbit&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="p">.</span><span class="n">dog</span><span class="p">(</span><span class="kd">let</span> <span class="nv">dog</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">		<span class="n">dog</span><span class="p">.</span><span class="n">pet</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;dog&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="p">.</span><span class="n">turtle</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;turtle&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Notice that to get a variable of the narrowed type, a new variable is directly defined within the case expression (<code>let dog</code>).</p>
<p>A more concise form of type narrowing is also possible within an <code>if</code> statement<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="k">if</span> <span class="k">case</span> <span class="p">.</span><span class="n">rabbit</span><span class="p">(</span><span class="kd">let</span> <span class="nv">rabbit</span><span class="p">)</span> <span class="p">=</span> <span class="n">pet</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The main downside to Swift’s form of type unions is that the value is nested within another. It’s not possible to directly use attributes on a <code>Pet</code> variable. To work around that, you need to define accessors on the enum itself<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">enum</span> <span class="nc">Pet</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">rabbit</span><span class="p">(</span><span class="n">Rabbit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">dog</span><span class="p">(</span><span class="n">Dog</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">turtle</span><span class="p">(</span><span class="n">Turtle</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nv">name</span><span class="p">:</span> <span class="nb">String</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">switch</span> <span class="kc">self</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="p">.</span><span class="n">rabbit</span><span class="p">(</span><span class="kd">let</span> <span class="nv">rabbit</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">rabbit</span><span class="p">.</span><span class="n">name</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="p">.</span><span class="n">dog</span><span class="p">(</span><span class="kd">let</span> <span class="nv">dog</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">dog</span><span class="p">.</span><span class="n">name</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="p">.</span><span class="n">turtle</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">turtle</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">example</span><span class="p">(</span><span class="n">pet</span><span class="p">:</span> <span class="n">Pet</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">pet</span><span class="p">.</span><span class="n">name</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>If the different sub-types share a <a href="https://docs.swift.org/swift-book/LanguageGuide/Protocols.html">protocol</a>, you can define a single accessor to access the protocol.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">protocol</span> <span class="nc">Mammal</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nv">furColor</span><span class="p">:</span> <span class="nb">String</span> <span class="p">{</span> <span class="kr">get</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">enum</span> <span class="nc">Pet</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">rabbit</span><span class="p">(</span><span class="n">Rabbit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">dog</span><span class="p">(</span><span class="n">Dog</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">turtle</span><span class="p">(</span><span class="n">Turtle</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nv">mammal</span><span class="p">:</span> <span class="n">Mammal</span><span class="p">?</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">switch</span> <span class="kc">self</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="p">.</span><span class="n">rabbit</span><span class="p">(</span><span class="kd">let</span> <span class="nv">mammal</span> <span class="k">as</span> <span class="n">Mammal</span><span class="p">),</span> <span class="p">.</span><span class="n">dog</span><span class="p">(</span><span class="kd">let</span> <span class="nv">mammal</span> <span class="k">as</span> <span class="n">Mammal</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">mammal</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="p">.</span><span class="n">turtle</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">example</span><span class="p">(</span><span class="n">pet</span><span class="p">:</span> <span class="n">Pet</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">pet</span><span class="p">.</span><span class="n">mammal</span><span class="p">?.</span><span class="n">furColor</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Taking this further, if <em>all</em> sub-types share a protocol, you can extend the enum itself from that protocol and delegate to that accessor.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-swift" data-lang="swift"><span class="line"><span class="cl"><span class="kd">protocol</span> <span class="nc">Animal</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kd">func</span> <span class="nf">sleep</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">enum</span> <span class="nc">Pet</span><span class="p">:</span> <span class="n">Animal</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">rabbit</span><span class="p">(</span><span class="n">Rabbit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">dog</span><span class="p">(</span><span class="n">Dog</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">turtle</span><span class="p">(</span><span class="n">Turtle</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="kd">private</span> <span class="kd">var</span> <span class="nv">animal</span><span class="p">:</span> <span class="n">Animal</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">switch</span> <span class="kc">self</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="p">.</span><span class="n">rabbit</span><span class="p">(</span><span class="kd">let</span> <span class="nv">animal</span> <span class="k">as</span> <span class="n">Animal</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">			 <span class="p">.</span><span class="n">dog</span><span class="p">(</span><span class="kd">let</span> <span class="nv">animal</span> <span class="k">as</span> <span class="n">Animal</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">			 <span class="p">.</span><span class="n">turtle</span><span class="p">(</span><span class="kd">let</span> <span class="nv">animal</span> <span class="k">as</span> <span class="n">Animal</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">animal</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="kd">func</span> <span class="nf">sleep</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">animal</span><span class="p">.</span><span class="n">sleep</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">example</span><span class="p">(</span><span class="n">pet</span><span class="p">:</span> <span class="n">Pet</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">pet</span><span class="p">.</span><span class="n">sleep</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I find type unions significantly simplify my code and prevent mistakes by strengthening my type semantics. I hope this helps you out as well.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Unless you&rsquo;re using <a href="https://docs.python.org/3/library/typing.html#typing.Union">Python 3.5 type hinting</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://sarunw.com/posts/lesser-known-ways-of-using-swift-enums/">Here&rsquo;s another good article about working with Swift enums</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>There&rsquo;s some <a href="https://forums.swift.org/t/extract-payload-for-enum-cases-having-associated-value/27606">discussion on the Swift forums</a> about proposed syntax changes to make it easier to access the associated value within an enum, so keep your eyes out for changes in this space.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>


  
  ]]></content:encoded></item><item><title>1,000 Miles</title><link>https://camlittle.com/posts/1000-miles-2020/</link><pubDate>Thu, 12 Nov 2020 15:14:00 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/1000-miles-2020/</guid><enclosure url="https://camlittle.com/posts/1000-miles-2020/thumbnail.jpg" length="360645" type="image/jpeg"/><description>&lt;p>One of my big goals this year was to run 1,000 miles. As of yesterday, I’ve done it!&lt;/p>
&lt;p>&lt;a href="mosaic.jpg">
&lt;picture title="mosaic.jpg">
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at2x.webp"
type="image/webp"
/>
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at2x.jpg"
type="image/jpeg"
/>
&lt;source
media="min-width: 1200px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at1x.webp"
type="image/webp"
/>
&lt;source
media="min-width: 1200px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at1x.jpg"
type="image/jpeg"
/>
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at2x.webp"
type="image/webp"
/>
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at2x.jpg"
type="image/jpeg"
/>
&lt;source
media="min-width: 992px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at1x.webp"
type="image/webp"
/>
&lt;source
media="min-width: 992px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at1x.jpg"
type="image/jpeg"
/>
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at2x.webp"
type="image/webp"
/>
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at2x.jpg"
type="image/jpeg"
/>
&lt;source
media="min-width: 768px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at1x.webp"
type="image/webp"
/>
&lt;source
media="min-width: 768px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at1x.jpg"
type="image/jpeg"
/>
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at2x.webp"
type="image/webp"
/>
&lt;source
media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at2x.jpg"
type="image/jpeg"
/>
&lt;source
media="min-width: 480px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at1x.webp"
type="image/webp"
/>
&lt;source
media="min-width: 480px"
srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at1x.jpg"
type="image/jpeg"
/>
&lt;img
height="300"
style="max-height: 300px;"
src="https://camlittle.com/posts/1000-miles-2020/mosaic.bf4a1f6de511032a7b8631014f335186.jpg"
alt="mosaic.jpg"
/>
&lt;/picture>
&lt;/a>&lt;/p></description><content:encoded><![CDATA[
  
<p>One of my big goals this year was to run 1,000 miles. As of yesterday, I’ve done it!</p>
<p><a href="mosaic.jpg">





  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="mosaic.jpg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mosaic_bf4a1f6de511032a7b8631014f335186_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          height="300"
          
          style="max-height: 300px;"
          src="https://camlittle.com/posts/1000-miles-2020/mosaic.bf4a1f6de511032a7b8631014f335186.jpg"
          alt="mosaic.jpg"
      />
  </picture>



</a></p>
<p>Running has been especially important to me because of coronavirus stresses and living in a foreign country. I find it really helpful as a grounding technique—it forces me to be immersed in the physical world and be aware of physical discomforts. Having an ambitious and achievable goal also helps keep me positive and stick to a regular running schedule.</p>
<p>My favorite part of running is exploration. In a new city, running lets me see the sights and tour the area without a plan or obligation. In my current city, I end up finding all sorts of hidden treasures and trails that most people never see. I usually open up a map in my area and try to connect the green areas using <a href="https://www.strava.com/heatmap#12.04/19.88822/50.05018/hot/all">Strava’s global heatmap</a>.</p>
<p>Some highlights include seeing the old town and some local parks in Gdansk, exploring <a href="https://www.strava.com/activities/3592985128">several</a> <a href="https://www.strava.com/activities/3085728868">national</a> parks, a <a href="https://www.strava.com/activities/3045963732">snowy night run in Slovakia</a>, and <a href="https://www.strava.com/activities/3429526207">seeing castles</a> along Szlak Orlich Gniazd (the Trail of the Eagle&rsquo;s Nests). On the other hand, I&rsquo;ve had some not-so-fun runs, including <a href="https://www.strava.com/activities/3293525889">10 kilometers</a> on the same 0.2km stretch of cobble in my apartment complex during coronavirus lockdown, some smoggy morning winter runs, and <a href="https://www.strava.com/activities/3785491674">my longest and farthest run</a> to date&mdash;28 miles for my 28th birthday over 4 hours and 20 minutes. Aisha&rsquo;s cheered me along from her bike for most of the long ones in her self-designated role as &ldquo;sheepdog.&rdquo;</p>
<figure>



  
    
    
      
    
    
    
      
    
  

  
    
    
    
    
  

  
    
    
    
    
  


<ul class="post-gallery">

    
    
    
    <li style="flex: 512;">
        <a href="https://camlittle.com/posts/1000-miles-2020/kosciuszki.light.png" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="kosciuszki.light.png">
      
      
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.dark_aabd2903236f33681fee4f229bed3888_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/kosciuszki.light_66e3f1fccbe995a00900284c384771e9_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/1000-miles-2020/kosciuszki.light.66e3f1fccbe995a00900284c384771e9.png"
          alt="kosciuszki.light.png"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 512;">
        <a href="https://camlittle.com/posts/1000-miles-2020/pilsudskiego.light.png" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="pilsudskiego.light.png">
      
      
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.dark_6a02c31fd65b6f9b2e2927e3cba8fa9a_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/pilsudskiego.light_2c7c4fbd3f9557f6801d598c6e304fe4_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/1000-miles-2020/pilsudskiego.light.2c7c4fbd3f9557f6801d598c6e304fe4.png"
          alt="pilsudskiego.light.png"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 512;">
        <a href="https://camlittle.com/posts/1000-miles-2020/krakus.light.png" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="krakus.light.png">
      
      
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.dark_0e6b965b77b23b0ab7e8827a6ee80a76_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/krakus.light_0de6a5448cfe8eafd3ad527cad441250_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/1000-miles-2020/krakus.light.0de6a5448cfe8eafd3ad527cad441250.png"
          alt="krakus.light.png"
      />
  </picture>


        </a>
    </li>
    

</ul>

<figcaption>Three of the four mounds of Kraków</figcaption>
</figure>
<figure>



  
    
    
      
    
    
    
      
    
  

  
    
    
    
    
  

  
    
    
    
    
  


<ul class="post-gallery">

    
    
    
    <li style="flex: 512;">
        <a href="https://camlittle.com/posts/1000-miles-2020/lockdown.light.png" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="lockdown.light.png">
      
      
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.dark_f6d8697c256c2f19a91d4a4a8af5f3dd_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/lockdown.light_1c26d185bc0b0badb3bba6cfc5270415_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/1000-miles-2020/lockdown.light.1c26d185bc0b0badb3bba6cfc5270415.png"
          alt="lockdown.light.png"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 512;">
        <a href="https://camlittle.com/posts/1000-miles-2020/greater-krakow.light.png" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="greater-krakow.light.png">
      
      
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.dark_0f7209ac220f22916a30200d7812f12b_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/greater-krakow.light_bea4dc8a756c23c69f2ea03a7487e6cf_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/1000-miles-2020/greater-krakow.light.bea4dc8a756c23c69f2ea03a7487e6cf.png"
          alt="greater-krakow.light.png"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 512;">
        <a href="https://camlittle.com/posts/1000-miles-2020/mazury.light.png" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="mazury.light.png">
      
      
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: dark), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: dark)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: dark)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.dark_c1daf90cd5a0a1ba699e7f79726d9cc2_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
        
          
          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 1200px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 1200px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_1200x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 992px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 992px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_992x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 768px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 768px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_768x0at1x.jpg"
    type="image/jpeg"
/>


          
            







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px) and (prefers-color-scheme: light), (min-resolution: 192dpi) and (min-width: 480px) and (prefers-color-scheme: light)"
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="(min-width: 480px) and (prefers-color-scheme: light)"
    
    srcset="https://content.camlittle.com/site-media/posts/1000-miles-2020/mazury.light_a9a2bf7015597697fd7a519b73a730a2_480x0at1x.jpg"
    type="image/jpeg"
/>


          
        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/1000-miles-2020/mazury.light.a9a2bf7015597697fd7a519b73a730a2.png"
          alt="mazury.light.png"
      />
  </picture>


        </a>
    </li>
    

</ul>

<figcaption>Lockdown running in my apartment courtyard, Southern Poland, Masurian Lake District</figcaption>
</figure>
<p>Another aspect I enjoy is tracking statistics. I&rsquo;ve used <a href="https://www.strava.com/athletes/14856714">Strava</a> for the last couple of years to store my historical running information. Throughout my 1,000 miles, I&rsquo;ve spent almost six full days moving, climbed 54,775 feet (almost twice the height of Mount Everest), and burned an extra 114,267 calories (around 60 days worth of food for the average person).</p>
<figure>
<img src="graph.svg" alt="Cumulative distance over time" />
<figcaption>Cumulative distance over time</figcaption>
</figure>
<p class="full-width">
	<img alt="Heatmap of runs for the year" src="overview.dark.png" />
</p>
<p>You can see <a href="https://observablehq.com/d/bda8bfd8f9e500a9">more data here</a>.</p>
<p><small style="display: block">Maps generated with <a href="https://github.com/erik/derive">https://github.com/erik/derive</a>. Background imagery from <a href="https://carto.com/attribution/">CartoDB</a>.</small></p>


  
  ]]></content:encoded></item><item><title>Why this slack channel shouldn't be private</title><link>https://camlittle.com/posts/slack-private-channels/</link><pubDate>Fri, 06 Nov 2020 18:56:00 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/slack-private-channels/</guid><enclosure url="https://camlittle.com/posts/slack-private-channels/thumbnail.png" length="9591" type="image/png"/><description>&lt;p>Here&amp;rsquo;s why you shouldn&amp;rsquo;t use a private Slack channel:&lt;/p>
&lt;ol>
&lt;li>Messages are not &lt;a href="https://slack.com/intl/en-pl/help/articles/202528808-Search-in-Slack">searchable&lt;/a> once you leave&lt;/li>
&lt;li>Messages are not &lt;a href="https://slack.com/intl/en-pl/help/articles/203274767-Share-messages-in-Slack">sharable&lt;/a>&lt;/li>
&lt;li>People can&amp;rsquo;t easily jump in and out&lt;/li>
&lt;li>You can&amp;rsquo;t notify someone when you &lt;a href="https://slack.com/intl/en-pl/help/articles/205240127-Use-mentions-in-Slack">mention them&lt;/a>&lt;/li>
&lt;li>Workspace apps and bots aren&amp;rsquo;t present&lt;/li>
&lt;li>They cultivate an &lt;a href="https://www.forbes.com/sites/duenablomstrom1/2019/02/06/why-a-culture-of-us-vs-them-is-deadly/">&amp;ldquo;Us vs. them&amp;rdquo; culture&lt;/a>&lt;/li>
&lt;li>They foster unconscious biases by making it easy to exclude people&lt;/li>
&lt;li>You can&amp;rsquo;t change your mind and make a private channel public&lt;/li>
&lt;/ol>
&lt;p>While there are uses for private channels, they hinder communication and transparency. Please make public channels your default.&lt;/p></description><content:encoded><![CDATA[
  
<p>Here&rsquo;s why you shouldn&rsquo;t use a private Slack channel:</p>
<ol>
<li>Messages are not <a href="https://slack.com/intl/en-pl/help/articles/202528808-Search-in-Slack">searchable</a> once you leave</li>
<li>Messages are not <a href="https://slack.com/intl/en-pl/help/articles/203274767-Share-messages-in-Slack">sharable</a></li>
<li>People can&rsquo;t easily jump in and out</li>
<li>You can&rsquo;t notify someone when you <a href="https://slack.com/intl/en-pl/help/articles/205240127-Use-mentions-in-Slack">mention them</a></li>
<li>Workspace apps and bots aren&rsquo;t present</li>
<li>They cultivate an <a href="https://www.forbes.com/sites/duenablomstrom1/2019/02/06/why-a-culture-of-us-vs-them-is-deadly/">&ldquo;Us vs. them&rdquo; culture</a></li>
<li>They foster unconscious biases by making it easy to exclude people</li>
<li>You can&rsquo;t change your mind and make a private channel public</li>
</ol>
<p>While there are uses for private channels, they hinder communication and transparency. Please make public channels your default.</p>


  
  ]]></content:encoded></item><item><title>Photos</title><link>https://camlittle.com/posts/photos/</link><pubDate>Tue, 27 Oct 2020 20:34:14 +0100</pubDate><guid isPermaLink="true">https://camlittle.com/posts/photos/</guid><enclosure url="https://camlittle.com/posts/photos/thumbnail.jpg" length="81949" type="image/jpeg"/><description>&lt;p>I&amp;rsquo;m trying out a new approach to sharing my photos here on my website at &lt;a href="https://camlittle.com/photos">/photos&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;ve been considering divesting from social media for some time, for mental health, ethics, and to own my content. I feel like Instagram reached a tipping point this summer, where it shifted from a positive place to share what my friends are up to, to a platform that amplifies outrages and meme sharing.&lt;/p>
&lt;p>My approach is heavily inspired by &lt;a href="https://macwright.com/2019/02/28/photos.html">Tom MacWright&amp;rsquo;s /photos&lt;/a>, as are a few other aspects of this site&amp;rsquo;s design. I want it to be fast and simple, with low management overhead.&lt;/p></description><content:encoded><![CDATA[
  
<p>I&rsquo;m trying out a new approach to sharing my photos here on my website at <a href="https://camlittle.com/photos">/photos</a>.</p>
<p>I&rsquo;ve been considering divesting from social media for some time, for mental health, ethics, and to own my content. I feel like Instagram reached a tipping point this summer, where it shifted from a positive place to share what my friends are up to, to a platform that amplifies outrages and meme sharing.</p>
<p>My approach is heavily inspired by <a href="https://macwright.com/2019/02/28/photos.html">Tom MacWright&rsquo;s /photos</a>, as are a few other aspects of this site&rsquo;s design. I want it to be fast and simple, with low management overhead.</p>
<hr>
<p>To post a photo, I run it through <a href="https://github.com/apexskier/camlittle.com/blob/b4310c00e55425eadcf6b4bebb983ff28b589902/photo.js">a script</a> to process, resize, and upload to <a href="https://www.digitalocean.com/products/spaces/">DigitalOcean spaces</a> for long term storage. The script also creates a new post that I add some details to (location, title, description…), and all that&rsquo;s left is to publish.</p>
<p>I spent quite a bit of time refining the user experience and trying out some fun new web technologies for the first time.</p>
<p>I generate <a href="https://github.com/apexskier/camlittle.com/blob/b4310c00e55425eadcf6b4bebb983ff28b589902/layouts/photos/li.html#L6-L26">custom CSS</a> to keep the images centered and constrained within the window. I use <a href="https://css-tricks.com/practical-css-scroll-snapping/">CSS scroll snapping</a> to snap to and center on them as you scroll through. The page uses no JavaScript.</p>
<p>The fanciest feature is a custom placeholder displayed as the images load. My script extracts average colors for the top, left, right, and bottom of the image, then injects them into an inlined SVG background image for the <code>&lt;picture&gt;</code> element. The SVG is made of four radial shapes of the extracted average colors, with a blur applied on top of it to form a geometric approximation of a downsampled version of the image.</p>
<p>
	<svg viewBox="0 0 64 64" height="200" width="300" preserveAspectRatio="none" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
		<filter id="blurMe">
			<feGaussianBlur in="SourceGraphic" stdDeviation="5" />
		</filter>
		<g filter="url(#blurMe)">
			<polygon id="Path" fill="#8987a6" points="0 0 32 32 64 0"></polygon>
			<polygon id="Path" fill="#506c97" points="0 64 32 32 0 0"></polygon>
			<polygon id="Path" fill="#42587f" points="64 0 32 32 64 64"></polygon>
			<polygon id="Path" fill="#647fa2" points="64 64 32 32 0 64"></polygon>
		</g>
	</svg>
</p>
<p>Overall, it was a fun project to show off some of my favorite photos. <a href="https://camlittle.com/photos">I hope you like them too.</a></p>


  
  ]]></content:encoded></item><item><title>React and TypeScript</title><link>https://camlittle.com/posts/react-and-typescript/</link><pubDate>Sun, 11 Oct 2020 11:46:48 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/react-and-typescript/</guid><enclosure url="https://camlittle.com/posts/react-and-typescript/thumbnail.png" length="9614" type="image/png"/><description>&lt;p>Here are some of my tips and best practices for working with React using TypeScript&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. These mostly come from my experience working in a large-scale, real-world React/React Native/TypeScript codebase. You should have a working knowledge of React and some familiarity with TypeScript before reading this post, although I&amp;rsquo;ve included links to further documentation if you can&amp;rsquo;t remember the details.&lt;/p>
&lt;p>Let&amp;rsquo;s start with a basic example of creating and using a component. &lt;code>HelloProps&lt;/code> is an &lt;a href="https://www.typescriptlang.org/docs/handbook/interfaces.html">interface&lt;/a> that describes precisely what properties the component accepts and requires.&lt;/p></description><content:encoded><![CDATA[
  
<p>Here are some of my tips and best practices for working with React using TypeScript<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. These mostly come from my experience working in a large-scale, real-world React/React Native/TypeScript codebase. You should have a working knowledge of React and some familiarity with TypeScript before reading this post, although I&rsquo;ve included links to further documentation if you can&rsquo;t remember the details.</p>
<p>Let&rsquo;s start with a basic example of creating and using a component. <code>HelloProps</code> is an <a href="https://www.typescriptlang.org/docs/handbook/interfaces.html">interface</a> that describes precisely what properties the component accepts and requires.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">HelloProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">readonly</span> <span class="nx">compiler</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">readonly</span> <span class="nx">framework</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">Hello</span><span class="p">({</span> <span class="nx">compiler</span><span class="p">,</span> <span class="nx">framework</span> <span class="p">}</span><span class="o">:</span> <span class="nx">HelloProps</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span><span class="nx">Hello</span> <span class="kr">from</span> <span class="p">{</span><span class="nx">compiler</span><span class="p">}</span> <span class="nx">and</span> <span class="p">{</span><span class="nx">framework</span><span class="p">}</span><span class="o">!</span><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Hello</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;./hello&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App() {</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">Hello</span> <span class="na">compiler</span><span class="o">=</span><span class="s">&#34;TypeScript&#34;</span> <span class="na">framework</span><span class="o">=</span><span class="s">&#34;React&#34;</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>There are two main component-related types used when writing React code. I tend to get them mixed up.</p>
<ol>
<li><a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f0841a3126737ab117add60b2011d7a7c10022eb/types/react/index.d.ts#L82"><code>React.ComponentType</code></a> is what&rsquo;s <em>in</em> your JSX <small>(except for html primitives)</small></li>
<li><a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f0841a3126737ab117add60b2011d7a7c10022eb/types/react/index.d.ts#L146-L150"><code>React.ReactElement</code></a> <small>(or <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f0841a3126737ab117add60b2011d7a7c10022eb/types/react/index.d.ts#L2942"><code>JSX.Element</code></a>)</small> is what your JSX produces</li>
</ol>
<details>
<summary>Here's an explicitly annotated code sample</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">class</span> <span class="nx">Test</span> <span class="kr">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="o">&lt;</span><span class="p">{</span> <span class="nx">Comp</span>: <span class="kt">React.ComponentType</span> <span class="p">}</span><span class="o">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">render() {</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">el</span>: <span class="kt">React.ReactElement</span> <span class="o">=</span> <span class="p">&lt;</span><span class="nt">this.props.Comp</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">&lt;&gt;{</span><span class="nx">el</span><span class="p">}&lt;/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></details>
<p><code>React.ComponentType</code> is composed of <code>React.ComponentClass</code>, for class-based components, and <code>React.FunctionComponent</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> for functional components.</p>
<p>You also might encounter more &ldquo;exotic&rdquo; types of components extending from <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f0841a3126737ab117add60b2011d7a7c10022eb/types/react/index.d.ts#L355"><code>React.ExoticType</code></a>. These include forwarded ref components, context providers, and memoized components. All have different behavior restrictions at runtime, so most difficulties you&rsquo;ll encounter have a good reason behind them.</p>
<h2 id="nullable-or-optional-props"><a class="heading-link" href="#nullable-or-optional-props"></a>Nullable or optional props?</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">MyComponentProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">requiredProp</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">optionalProp?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">nullableProp</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>If a property isn&rsquo;t required, you should generally make it nullable instead of optional. Optional properties can be omitted, which means they&rsquo;re easy to forget. Nullable properties require explicitness, which is especially helpful when introducing a new property into a large codebase&mdash;the compiler will tell you if you&rsquo;ve missed anything.</p>
<p>Only use optional properties when the property has a default value or if forgetting it won&rsquo;t break the user experience.</p>
<p>Avoid using properties that are both optional and nullable. They have the disadvantages of optionals and make the codebase more complex.</p>
<p>I also recommend avoiding <code>undefined</code> for <em>any</em> variable type because it allows accidental mistakes through forgotten initialization. This also helps avoid needing <code>nullableProp={value ?? null}</code>.</p>
<h2 id="dont-export-properties-types"><a class="heading-link" href="#dont-export-properties-types"></a>Don&rsquo;t export properties types</h2>
<p>As a codebase grows, it makes sense to export property interfaces to reuse in other components and keep things <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>, especially as your codebase expands, or you start making use of <a href="https://reactjs.org/docs/higher-order-components.html">higher-order components</a> and <a href="https://reactjs.org/docs/composition-vs-inheritance.html">composition</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ButtonProps</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;../props&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">FooButton</span><span class="p">(</span><span class="nx">props</span>: <span class="kt">ButtonProps</span><span class="p">)</span> <span class="p">{}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ButtonProps</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;../props&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">BarButton</span><span class="p">(</span><span class="nx">props</span>: <span class="kt">ButtonProps</span><span class="p">)</span> <span class="p">{}</span>
</span></span></code></pre></div><p>This is a pattern I used commonly in the past. I now avoid it because it increases coupling and information leaking. If <code>FooButton</code> doesn&rsquo;t need a property from <code>ButtonProps</code>, you can&rsquo;t remove it without affecting <code>BarButton</code>. If <code>BarButton</code> needs a new property, you can&rsquo;t hide it from <code>FooButton</code> and it&rsquo;s required everywhere <code>FooButton</code> is used.</p>
<p>Sharing interfaces is especially attractive to those coming from an object-oriented world. I advise trying to avoid habitually using inheritance and conformance and instead focus on &ldquo;<a href="https://www.typescriptlang.org/docs/handbook/interfaces.html">the <em>shape</em> that values have</a>.&rdquo;</p>
<p>Instead, <strong>each component should own its own properties</strong>. This results in more explicitness and increases flexibility at the minor cost of duplicated code.</p>
<details>
<summary>
<p>In this example, the new property <code>b</code> on <code>FooButton</code> doesn&rsquo;t require adding a <code>b</code> property to <code>BarButton</code> even though we don&rsquo;t know which of the two is used.</p>
</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">FooButtonProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">a</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">b</span>: <span class="kt">number</span><span class="p">;</span> <span class="c1">// this is a new property
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kd">function</span> <span class="nx">FooButton</span><span class="p">(</span><span class="nx">props</span>: <span class="kt">FooButtonProps</span><span class="p">)</span><span class="o">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">BarButtonProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">a</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kd">function</span> <span class="nx">BarButton</span><span class="p">(</span><span class="nx">props</span>: <span class="kt">FooButtonProps</span><span class="p">)</span><span class="o">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kr">const</span> <span class="nx">conditional</span>: <span class="kt">boolean</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">Test() {</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">Button</span> <span class="o">=</span> <span class="nx">conditional</span> <span class="o">?</span> <span class="nx">FooButton</span> : <span class="kt">BarButton</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">a</span><span class="o">=</span><span class="p">{</span><span class="mi">123</span><span class="p">}</span> <span class="na">b</span><span class="o">=</span><span class="p">{</span><span class="mi">456</span><span class="p">}</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><a href="https://www.typescriptlang.org/play/?jsx=2&amp;ssl=1&amp;ssc=1&amp;pln=21&amp;pc=2#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdwAdjFlAXlnAGIQQBCAVxgwITAArEw6AN504ClAC44TQSABGbeorgaVazdrgB6E3BgALYOhspVWAO5wwUtjACedAL4MAJli4ADbIlHAEgkz4wGI8fEIiYgAUrhDSKrwCwqISUqgAlCrYeDAAdMX4AKJBWCBYLPSMLGwcuFz8oQk5kmmy8orIBupaUPS+dAHBoVwRUTAxTHAdUF3JqelxWYm5vYWYVGUVMNW19TCNkyFhuGKo8DdMfsDzYshBKhp8NchMjbPRsQAKlg7kl8nA5LoHncltlYgBeOAPJ4vJhvOAAfk2q0WKmWOJ0ikoMEEUEWAB4cSh4TIAIwAJgAzN49DSACwAVgAbCyTAA+MZAA">Playground link</a></p>
</details>
<h2 id="higher-order-components"><a class="heading-link" href="#higher-order-components"></a>Higher-order components</h2>
<p><a href="https://reactjs.org/docs/higher-order-components.html">Higher-order components</a> are an advanced React pattern for reuse of component logic. In TypeScript, higher-order components are <a href="https://www.typescriptlang.org/docs/handbook/generics.html">generic</a> to decouple them from the components being wrapped.</p>
<p>Correctly typing higher-order components is a <em>pain in the ass</em>. That being said, I&rsquo;ll try to explain my approach and the problems you&rsquo;ll encounter.</p>
<p>When writing or refactoring a higher-order component into TypeScript, you need to understand the <strong>wrapped component&rsquo;s properties</strong> and <strong>the returned component&rsquo;s properties</strong>. The wrapped, or parameter, component&rsquo;s properties can be divided into <strong>those that the higher-order component uses</strong> and <strong>those that it doesn&rsquo;t</strong>. The types that the higher-order component doesn&rsquo;t need to know about are generally represented by a generic type: let&rsquo;s call it <code>P</code>.</p>
<p>On occasion, you need to know the type of component returned. If possible, I recommend using the widest type possible (<code>React.ComponentType</code>) or relying on type interference (not specifying an explicit return type). You may need an explicitly narrower type if your component returns an exotic type, like a ref forwarder.</p>
<p>By convention, I use the names &ldquo;Provides&rdquo; and &ldquo;Requires&rdquo;. Provided properties are those that the higher-order component provides to its wrapped component, and required properties are those additionally needed by the returned component.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">withExtraFunctionality</span><span class="p">&lt;</span><span class="nt">P</span><span class="p">&gt;(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">WrappedComponent</span>: <span class="kt">React.ComponentType</span><span class="p">&lt;</span><span class="nt">P</span> <span class="err">&amp;</span> <span class="na">ExtraFunctionalityProvidesProps</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="o">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ComponentType</span><span class="p">&lt;</span><span class="nt">P</span> <span class="err">&amp;</span> <span class="na">ExtraFunctionalityRequiresProps</span><span class="p">&gt;;</span>
</span></span></code></pre></div><details>
<summary>A more complete example of a fully typed higher-order component</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kd">function</span> <span class="nx">getDisplayName</span><span class="p">(</span><span class="nx">component</span>: <span class="kt">React.ElementType</span><span class="p">)</span><span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">ExtraFunctionalityProvidesProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">a</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">b</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">ExtraFunctionalityRequiresProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">b</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">c</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">withExtraFunctionality</span><span class="p">&lt;</span><span class="nt">P</span><span class="p">&gt;(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">WrappedComponent</span>: <span class="kt">React.ComponentType</span><span class="p">&lt;</span><span class="nt">P</span> <span class="err">&amp;</span> <span class="na">ExtraFunctionalityProvidesProps</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="o">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ComponentType</span><span class="p">&lt;</span><span class="nt">P</span> <span class="err">&amp;</span> <span class="na">ExtraFunctionalityRequiresProps</span><span class="p">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nx">ExtraFunctionality</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">props</span>: <span class="kt">P</span> <span class="o">&amp;</span> <span class="nx">ExtraFunctionalityRequiresProps</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// shared, internalized logic, e.g.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kr">const</span> <span class="p">[</span><span class="nx">a</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">&lt;</span><span class="nt">WrappedComponent</span> <span class="na">a</span><span class="o">=</span><span class="p">{</span><span class="nx">a</span><span class="p">}</span> <span class="p">{</span><span class="na">...props</span><span class="p">}</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ExtraFunctionality</span><span class="p">.</span><span class="nx">displayName</span> <span class="o">=</span> <span class="sb">`withExtraFunctionality(</span><span class="si">${</span><span class="nx">getDisplayName</span><span class="p">(</span><span class="nx">WrappedComponent</span><span class="p">)</span><span class="si">}</span><span class="sb">)`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">ExtraFunctionality</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kd">function</span> <span class="nx">BaseComponent</span><span class="p">(</span><span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">d</span>: <span class="kt">string</span> <span class="p">})</span><span class="o">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// P inferred correctly as { d: string }
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">WrappedBaseComponent</span> <span class="o">=</span> <span class="nx">withExtraFunctionality</span><span class="p">(</span><span class="nx">BaseComponent</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">BaseComponent</span> <span class="na">d</span><span class="o">=</span><span class="s">&#34;d&#34;</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">WrappedBaseComponent</span> <span class="na">d</span><span class="o">=</span><span class="s">&#34;d&#34;</span> <span class="na">b</span><span class="o">=</span><span class="s">&#34;b&#34;</span> <span class="na">c</span><span class="o">=</span><span class="s">&#34;c&#34;</span> <span class="p">/&gt;;</span>
</span></span></code></pre></div><p><a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdAJlrgDbKVwECuAdvsAi84AcywwAIsFRh2ATwByyEFgAUuEpF5ZeMAFyYqMAHQBRVlhW6AKnLBYAlAdQwowXiPp13MLFAJ4WHCmAB6uyABifAJCyKzAMHIACsQAbsDMqCkQYOgA3nRwRSjOru6ehcUARqVuHvQAvgw+fgG4QaHhUfwwgrxxCXLYAI7cwJRZxLlwBcVwNXAudRVzuLXljQw8PX1wAO4JABadUJHRvbHxiQA8SQB8qpVFAOqnYPaMAMKaQjr6hngTN9wL8bHYsLc4AAyYJhU7dGL9K7JNIZLCTHKoO50JwA-DGYFaP62eyQmEnM47S6DEZjCbZXJ3GZPLjnXYUhEXJGDR5zOZgKaoAxJaGwrps6mJWnjdEM1AshzMvnFAD0KsWhw4WEYABo4C0oNyAF7auCsCAiYC4PVYYwiYws1ZCFxwADayAAunAALx4kzcVBYADKMGQvlUZDIDnoyqKjuKlBg3ENcGur2Q721hNB8GQ3ryyAaM2MJYFmKLKruMeKTTmHIl3MSxkY0lkyEUyiCvoABgcYMc4ZTEQNEqoACR5MSSVvyJQqVTpzNfH7aXQOBoObvVoqJ5PCetUxtyTZMFjsTjbRFwABCaCw2dXMFUZdyBjycEY6w8cA3BmwgOMf98HMSw-i8NU4BFdwCD8ShGDgDQoEofBWDkFB8g-L8RB-OgNF4F1Fw+W9AwfP4fX2I4D2HZFVGI+8Vz+aMGGuOjSN0D9vTIRgyDgSt6DTN4iLvNj4EYTjuPmTiqh43BONwHi+KAA">Playground link</a></p>
</details>
<p>A common error I encounter is some variety of the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">&#39;P&#39; could be instantiated with an arbitrary type which could be unrelated to…
</span></span></code></pre></div><p>The most common cause of this is the higher-order component &ldquo;stealing&rdquo; properties from the wrapped component (or TypeScript thinks this is happening). It highlights that a property name visible to the higher-order component could overlap with one in <code>P</code> required by the wrapped component.</p>
<details>
<summary>An example of stolen props in a higher-order component</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">ExtraFunctionalityProvidesProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">a</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">ExtraFunctionalityRequiresProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">b</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">c</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">withExtraFunctionality</span><span class="p">&lt;</span><span class="nt">P</span><span class="p">&gt;(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">WrappedComponent</span>: <span class="kt">React.ComponentType</span><span class="p">&lt;</span><span class="nt">P</span> <span class="err">&amp;</span> <span class="na">ExtraFunctionalityProvidesProps</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="o">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ComponentType</span><span class="p">&lt;</span><span class="nt">P</span> <span class="err">&amp;</span> <span class="na">ExtraFunctionalityRequiresProps</span><span class="p">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kd">function</span> <span class="nx">ExtraFunctionality</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">props</span>: <span class="kt">P</span> <span class="o">&amp;</span> <span class="nx">ExtraFunctionalityRequiresProps</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// shared, internalized logic, e.g.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kr">const</span> <span class="p">[</span><span class="nx">a</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="p">{</span> <span class="nx">b</span><span class="p">,</span> <span class="p">...</span><span class="nx">rest</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">props</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// do something with b
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        
</span></span><span class="line"><span class="cl">        <span class="c1">//      ↓ error
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">return</span> <span class="p">&lt;</span><span class="nt">WrappedComponent</span> <span class="na">a</span><span class="o">=</span><span class="p">{</span><span class="nx">a</span><span class="p">}</span> <span class="p">{</span><span class="na">...rest</span><span class="p">}</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kd">function</span> <span class="nx">BaseComponent</span><span class="p">(</span><span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">d</span>: <span class="kt">string</span> <span class="p">})</span><span class="o">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// P inferred correctly as { d: string }
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">WrappedBaseComponent</span> <span class="o">=</span> <span class="nx">withExtraFunctionality</span><span class="p">(</span><span class="nx">BaseComponent</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">BaseComponent</span> <span class="na">d</span><span class="o">=</span><span class="s">&#34;d&#34;</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">WrappedBaseComponent</span> <span class="na">d</span><span class="o">=</span><span class="s">&#34;d&#34;</span> <span class="na">b</span><span class="o">=</span><span class="s">&#34;b&#34;</span> <span class="na">c</span><span class="o">=</span><span class="s">&#34;c&#34;</span> <span class="p">/&gt;;</span>
</span></span></code></pre></div><p><a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdwAdjFlAXlnAKIAeMUZADEArk3zAITZABtgMAJ4AFYgDdgAEyyoVEMOgDedOCZQAuOKgHMA5vQC+DZq3ace-QaPExJ0uYuwARxFgSh1ifTgjUzgAIwsrKFt6GNwE6yY7Okc6AjEJKTgAd3kACz4BYXyfKVl5BQAeJQA+AApjUwB1QTAwLA0AYRJIJiwWC2w8GAA6IfApMZgAFQU+prgAMndKrwK-et11LXC9VGa6AEoJqhm5kcWVtaVN7c9q3zqArGDQ7V19ZpRDomSgwERQJhwPLeXyvKow2r+BTtGIxMARVAWZ5bCpvBH7L4-ML-VDAuAXIGo0wAempllKyEoGgANHBnGx9gAvfpwGQQGzAXCsrDTGzTMmpKRWOAAbWQAF04ABeTA3aYiVBYADKMGQrFaZDIFxSVLguCl8AMcVZ01tYXg9mVcHRpxNVNpcA0EEsJCwMFKtmKZTiEtMoZMHtRgGTCOBsYhQcNwUHgyENbrIXr9O4LFgoJUGZCOgy26b2x3U5puuCOHJaXAyRlcaF7OAAITQWGzoxYrRd+gsVo06SSmWrV1VU2mk3w3BkWBAi3odA9z2YBDjPPNUEo+BkChQhk9w8DjnNTGl6czGnbmq7iydJX9uPhe0+yJvneGOZgxoYDQ-d65hoSpkBoZBwBW9Bpj0fTXh2gHwMBoHgbEIGxOBuAgbg4GQUAA">Playground link</a></p>
</details>
<p>To fix this, you&rsquo;ll need to refactor your code to avoid the name conflict or explicitly type Provides and Requires props to prevent the stolen prop, which is error-prone.</p>
<p>Higher-order components and TypeScript can be a pain, but each upgrade of TypeScript seems to make it smoother (or maybe it&rsquo;s just me learning). I&rsquo;m hopeful that <a href="https://github.com/microsoft/TypeScript/issues/10727">#10727</a>, <a href="https://github.com/microsoft/TypeScript/issues/9252">#9252</a>, and <a href="https://github.com/Microsoft/TypeScript/issues/12936">#12936</a> will address some of the remaining issues.</p>
<h2 id="refs"><a class="heading-link" href="#refs"></a>Refs</h2>
<p><a href="https://reactjs.org/docs/refs-and-the-dom.html">Refs</a> are another place where typing can be tricky. The main cause of this is not understanding the fundamentals of what refs are&mdash;a pointer to the underlying runtime object driving your component. The <code>React.createRef</code> function takes a single type parameter that describes this object.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">Component() {</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">ref</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">&lt;</span><span class="nt">HTMLDivElement</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ref</span><span class="p">.</span><span class="nx">current</span><span class="o">?</span><span class="p">.</span><span class="nx">scrollTop</span><span class="p">;</span> <span class="c1">// &lt;-- properly typed
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">ref</span><span class="p">}</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Most examples show refs to DOM elements and other <a href="https://reactnative.dev/docs/components-and-apis#basic-components">&ldquo;basic&rdquo; components</a>, but it works the same way with <em>any</em> component.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">Component() {</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">ref</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">&lt;</span><span class="nt">ChildComponent</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ref</span><span class="p">.</span><span class="nx">current</span><span class="o">?</span><span class="p">.</span><span class="nx">doSomething</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">ChildComponent</span> <span class="na">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">ref</span><span class="p">}</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kr">class</span> <span class="nx">ChildComponent</span> <span class="kr">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">doSomething</span><span class="p">()</span><span class="o">:</span> <span class="k">void</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">render</span><span class="p">()</span><span class="o">:</span> <span class="nx">JSX</span><span class="p">.</span><span class="nx">Element</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>As your app gets more complex, so will your ref usage. Unfortunately, the way ref types are defined <a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdwAdjFlAXlnAEJYAWyAG7BocAN504UuABMIAZRJYYfZgHMAFAEoAXHEERgM+gF8GBAK5N8IpnADCJSEywtt4ydNwQmqeJQI4AF5MKhgAOlxKZFZsAgAeXgFhUQAyULwIx3AfVxgAPm16aTgAyIsoShYAfnC5RRBlVSY1YulKGAq7ePtVABsZbOc80qwCILEAkzgAenzTBhksXD7kSjgVtHRe4AGh3JY4LAAPViYZdGxM8P2XQ9AwPqxGlnQkoREoDxL6pRV1bR6AxGNpSKpLKCAuAAKXkAA1wgBRJ4vGCmIA">prevents abstracting into an interface easily</a>.</p>
<h2 id="proptypes"><a class="heading-link" href="#proptypes"></a>PropTypes</h2>
<p>If you&rsquo;re migrating a JavaScript React project to TypeScript, you&rsquo;re hopefully already using <a href="https://reactjs.org/docs/typechecking-with-proptypes.html">PropTypes</a> to provide some degree of runtime type checking to your project. With TypeScript, PropTypes become mostly unnecessary. TypeScript&rsquo;s type system is much more powerful and can replace any PropType except for strict shapes<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> and custom validator functions.</p>
<details>
<summary>
<p>Here&rsquo;s an example of migrating from <code>PropTypes</code> to TypeScript</p>
</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">Component</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">Component</span><span class="p">.</span><span class="nx">propTypes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">optionalUnion</span><span class="o">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">oneOfType</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">        <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">string</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">number</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">instanceOf</span><span class="p">(</span><span class="nx">Message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">]),</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nx">optionalObjectWithShape</span><span class="o">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">shape</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="nx">color</span><span class="o">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">string</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fontSize</span><span class="o">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">number</span>
</span></span><span class="line"><span class="cl">    <span class="p">}),</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">Component</span><span class="p">(</span><span class="nx">props</span>: <span class="kt">ComponentProps</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">ComponentProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">optionalUnion?</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kt">number</span> <span class="o">|</span> <span class="nx">Message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">optionalObjectWithShape</span><span class="o">?:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">color</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fontSize</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></details>
<p>PropTypes are still necessary if using the <a href="https://reactjs.org/docs/legacy-context.html">legacy context API</a>. I recommend pairing <code>contextTypes</code> and <code>childContextTypes</code> with an interface to add some type safety at build-time.</p>
<details>
<summary>Here's how I use legacy context in TypeScript</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">PropTypes</span> <span class="kr">from</span> <span class="s2">&#34;prop-types&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">messageContextTypes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color</span>: <span class="kt">PropTypes.string</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">MessageContext</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">MessageListProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">messages</span>: <span class="kt">ReadonlyArray</span><span class="o">&lt;</span><span class="p">{</span> <span class="nx">text</span>: <span class="kt">string</span> <span class="p">}</span><span class="o">&gt;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">class</span> <span class="nx">MessageList</span> <span class="kr">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">&lt;</span><span class="nt">MessageListProps</span><span class="p">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">static</span> <span class="nx">childContextTypes</span> <span class="o">=</span> <span class="nx">messageContextTypes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">getChildContext</span><span class="p">()</span><span class="o">:</span> <span class="nx">MessageContext</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span> <span class="nx">color</span><span class="o">:</span> <span class="s2">&#34;purple&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">render() {</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">messages</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">message</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">Message</span> <span class="na">text</span><span class="o">=</span><span class="p">{</span><span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">))}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">MessageProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">text</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">Message</span><span class="p">({</span> <span class="nx">text</span> <span class="p">}</span><span class="o">:</span> <span class="nx">MessageProps</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span><span class="nx">text</span><span class="p">}</span> <span class="p">&lt;</span><span class="nt">Button</span><span class="p">&gt;</span><span class="nx">Delete</span><span class="p">&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">ButtonProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">children</span>: <span class="kt">React.ReactNode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">Button</span><span class="p">({</span> <span class="nx">children</span> <span class="p">}</span><span class="o">:</span> <span class="nx">ButtonProps</span><span class="p">,</span> <span class="nx">context</span>: <span class="kt">MessageContext</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">background</span>: <span class="kt">context.color</span> <span class="p">}}&gt;{</span><span class="nx">children</span><span class="p">}&lt;/</span><span class="nt">button</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">Button</span><span class="p">.</span><span class="nx">contextTypes</span> <span class="o">=</span> <span class="nx">messageContextTypes</span><span class="p">;</span>
</span></span></code></pre></div><p><a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQoksiK6ACsWACoCeYW6IiXJhOAWhh8BtOnVwQAdqnggBqZAHMsAYUUwsADxi9+6ALxwA3nThx5AG2gAuOBwjcpqAHTKowBRp0AL6y-vpQBHhYcACyapo6eobw1ra2DtAA-C6+-hr0IQwK4ZG40XGo6loAMsDKbmDoqWmqlQmoLtjIACaK9jwAglBQyDwAPJZw+kY5MH4BcEEAfAWyuPZo6BVVWLXKcMlYCt3oXfheuuCKRzBj2wl7MA2oS1Y2acrIMMC4dgAWwHs3V0xWSJgEcAsrR2IOmxk89Heti0MG0AKBsOSAAoAJQue5aTFGN5pUlwSgwACuUAUVjsEEcUBcZDA1LA9iwZEW9FJhVJlGOWCguJJZNsFOptKxSLFaTG3WAADcljLZWTLDAAd5RO5vND2l4QMgwFisfqtDjIa9pWrbXKCdE4WZLOasF44UE4AB6FV2sU4nEhP1wMZehXK1VpHE8tIhQphIWlcrxLTPUW2OGzeb5YKyAiUhT4YCKWIprBYyZwxb4svPS3NcVYKk0kPhpYa5KesYAIUpMBgiiWABEsBz9KHe-3B6G26siiUonBJwOFGmG-9Ad0BZ0qDAvGcYAA5CDdLC5uj5wvfEvLxQVjdAgXVpd9lfPAA09NBM1LbUJSSMesZQlFsbVlMYACNXxLZQeA5Z1LAgvAAGsNGIAtuhceRvz3DIoCCZZIzFSxcHRLcjiDcCvSgqcFF9KM51vBQvGwuFwXMOBXSJeFTBoIA">Playground link</a></p>
</details>
<p>If you&rsquo;re using the current <a href="https://reactjs.org/docs/context.html#api">context API</a>, you don&rsquo;t have much to worry about. It works very well with TypeScript.</p>
<h2 id="return-type-of-render"><a class="heading-link" href="#return-type-of-render"></a>Return type of render</h2>
<p>The return type of a render function must be <code>JSX.Element | null</code> in TypeScript, but the <a href="https://reactjs.org/docs/react-component.html#render">React docs</a> allow strings, numbers, and arrays.</p>
<details>
<summary>This means that this code is not valid</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">RenderArray() {</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="o">=</span><span class="s">&#34;1&#34;</span><span class="p">&gt;</span><span class="nx">A</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;,</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="o">=</span><span class="s">&#34;2&#34;</span><span class="p">&gt;</span><span class="nx">B</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">RenderArray</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">RenderString() {</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;testing&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">RenderString</span> <span class="p">/&gt;;</span>
</span></span></code></pre></div><p><a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdBArgHb7AQuZYsAmWUAIJQoyAJ4AKAJRwA3nTiK4lGEyhcA2gqU6APL2AA3OAGssYgLxkAjGQB8g3QHoDhuwBptOxfqOnzVgBM9gBCzq52XnAAuvQAvgy62HwCwqJicE529Iys7JzcKVAAyjBQwCwA5tJyUSpqXGQwWKgwFZW0dAl0STz8JWXtmdlAA">Playground link</a></p>
</details>
<p>This is easy to resolve; just <a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdBArgHb7AQuZYsAmWUAIJQoyAJ4AKAJRwA3nTiK4lGEyhcAPAD4A2gqUGNvYADc4AayxiAvGQCMZLYI0B6Yya0AafQcVHTFla2AEyOAEKu7lo+cAC6rlr0AL4MGth8AsKiYnAuiQzMbDAcXOn8UADKMFDALADm0nIxKmqaWmQwWKjF9WQJyallAlU19bmJQA">wrap the return in a fragment</a>: <code>return &lt;&gt;{value}&lt;/&gt;</code>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>At time of writing: React v16.13.1, TypeScript v4.0.2&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Previously called React.SFC (stateless functional component)&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://github.com/Microsoft/TypeScript/issues/12936">Exact types</a> might provide this in the future&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>


  
  ]]></content:encoded></item><item><title>Nova.app</title><link>https://camlittle.com/posts/2020-09-26-nova/</link><pubDate>Sat, 26 Sep 2020 12:42:55 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2020-09-26-nova/</guid><enclosure url="https://camlittle.com/posts/2020-09-26-nova/thumbnail.png" length="86382" type="image/png"/><description>&lt;p>Over the last couple of months, I&amp;rsquo;ve been playing around with the new text editor &lt;a href="http://nova.app">Nova&lt;/a> by &lt;a href="http://panic.com">Panic&lt;/a> and writing extensions for it.&lt;/p>
&lt;p>Panic is an independent software development company based in Portland, OR that I&amp;rsquo;ve been a fan of for a long time. When I first started programming, I used their previous web-development focused editor &lt;a href="https://panic.com/coda/">Coda&lt;/a>, and I still use many of their products today. I&amp;rsquo;m a fan of Panic because they &lt;a href="https://sign.panic.com">do&lt;/a> &lt;a href="https://play.date">interesting&lt;/a> &lt;a href="https://panic.com/blog/firewatch-one-month-later/">things&lt;/a> with amazingly high quality. I see this as a major difference between much of the industry&amp;rsquo;s focus on growth and speed over quality.&lt;/p></description><content:encoded><![CDATA[
  
<p>Over the last couple of months, I&rsquo;ve been playing around with the new text editor <a href="http://nova.app">Nova</a> by <a href="http://panic.com">Panic</a> and writing extensions for it.</p>
<p>Panic is an independent software development company based in Portland, OR that I&rsquo;ve been a fan of for a long time. When I first started programming, I used their previous web-development focused editor <a href="https://panic.com/coda/">Coda</a>, and I still use many of their products today. I&rsquo;m a fan of Panic because they <a href="https://sign.panic.com">do</a> <a href="https://play.date">interesting</a> <a href="https://panic.com/blog/firewatch-one-month-later/">things</a> with amazingly high quality. I see this as a major difference between much of the industry&rsquo;s focus on growth and speed over quality.</p>
<p>Nova is their new code editor. It&rsquo;s been in beta for a while now, and the first version was released a couple of weeks ago.</p>
<h2 id="what-im-excited-about"><a class="heading-link" href="#what-im-excited-about"></a>What I&rsquo;m excited about</h2>
<p>I&rsquo;m excited about Nova&rsquo;s potential for a few reasons:</p>
<p>It&rsquo;s a native app with the potential for better stability and performance than editors like Atom and VS Code (my primary GUI editor). Although most of my career has been focused on the web, I still prefer native apps over web technology-based ones (one of the reasons I use Apple Photos over Google Photos).</p>
<p>It&rsquo;s a for-profit product with a single owner. Although Microsoft&rsquo;s been doing a pretty great job with VS Code, it&rsquo;s hard to get around the fact that open source and free products have a higher chance of atrophying over time. This can be due to bloat (like I experienced with Atom), contributors switching focus, or just random chance.</p>
<p>It&rsquo;s got some valuable features. <a href="https://library.panic.com/nova/run-tasks/">Tasks</a> aren&rsquo;t exactly novel but are well implemented and have good potential to grow. Savable Search Scopes are really powerful and new to me. I&rsquo;m impressed that remote files, projects, and editing is so stable in a first release.</p>
<p>Finally, Panic&rsquo;s focus on quality and close affinity with the Apple community could align really well with a high-quality, curated Extension Library.</p>
<h2 id="what-im-worried-about"><a class="heading-link" href="#what-im-worried-about"></a>What I&rsquo;m worried about</h2>
<p>But, there are a few things that worry me:</p>
<p>There&rsquo;s no debugger support yet. It sounds like this is on the roadmap, but I can&rsquo;t ignore that it&rsquo;s critical for many of my workflows. In the JavaScript landscape I can use Chrome&rsquo;s debugging GUI, but many other languages don&rsquo;t have great options outside of an IDE. Being able to work on the code I&rsquo;m stepping through directly is immensely valuable.</p>
<p>Nova doesn&rsquo;t support Vim keybindings, and the extension API doesn&rsquo;t support different editing modes. I don&rsquo;t see myself fully committing to a code editor without Vim emulation&mdash;the ubiquity of them while programming makes them second nature to me at this point.</p>
<p>I&rsquo;m worried about the feasibility of an extension community for a paid product. Most extensions are open-source, but the product isn&rsquo;t, so some of what draws me to open-source development is missing. I&rsquo;m hoping these fears end up unfounded&mdash;there&rsquo;s definitely precedent for good extension support with other paid products.</p>
<p>Finally, I&rsquo;m unsure of the target industry and how well a single product will keep up. Most of what I&rsquo;ve seen around Nova revolves around the JavaScript ecosystem, but I think the overall vision is general-purpose. Panic needs a deep understanding of various software development communities and the compatibilities between different communities&rsquo; workflows.</p>
<p>One counter-example to many of my worries is <a href="https://www.jetbrains.com/products.html#type=ide">JetBrains&rsquo; IDEs</a>. In many ways, IntelliJ and friends are similar to Nova: paid product, solid extension library, power-user focused code editor; but there are some significant differences: JetBrains is a much larger company and has broken their products into niche IDEs for specific types of programming (JVM, Golang, Python, etc.). They also offer a fully free version.</p>
<p>Most of the people I’ve talked to have agreed that Nova&rsquo;s ultimate success will depend on its extension ecosystem. That&rsquo;s what I&rsquo;ve been mostly playing with, so let&rsquo;s jump in.</p>
<h2 id="extension-development"><a class="heading-link" href="#extension-development"></a>Extension development</h2>
<p>I&rsquo;m not going to cover extension development in detail. For that, <a href="https://docs.nova.app">read the docs</a>. I&rsquo;m also not going to cover the differences between extension development for Nova and other editors since this is my first experience with extension development.</p>
<p>Extensions can provide functionality through two mechanisms: static files and a <a href="https://developer.apple.com/documentation/javascriptcore">JavaScriptCore</a> based runtime. Certain features are available through both mechanisms; certain features are only available through one. For example, language syntax highlighting is defined only through static XML files, but auto-completion can be provided through XML files or a synchronous JavaScript API.</p>
<p>There are definitely some annoying limitations and missing features of the JavaScript API, but overall it&rsquo;s very nice for a V1 release. Because the runtime is JavaScriptCore, extensions are isolated and have a hard time interfering with each other or the editor&rsquo;s core functionality (<em>theoretically</em>, there are still <a href="https://github.com/apexskier/nova-typescript/issues/74">some major extension-triggered crashes</a>). Having a single runtime is very refreshing&mdash;no browser incompatibilities or cross-platform differences to work around.</p>
<p>The static file interface for extensions makes sense&mdash;it provides a low barrier to entry to get basic extension functionality working&mdash;but is painful for more advanced extensions. One example is <a href="https://docs.nova.app/extensions/commands/#when-clauses">conditionally enabling and disabling commands</a>. For my <a href="https://extensions.panic.com/extensions/apexskier/apexskier.eslint/">ESLint extension</a>, I can prevent &ldquo;Suggest a Fix&rdquo; commands from applying when the editor isn&rsquo;t in focus, but I can&rsquo;t prevent it when no suggestions are available.</p>
<p>My favorite part of Nova&rsquo;s approach to extensions is support for Language Server Protocol servers. The <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol (LSP)</a> is a Microsoft-backed open standard for editor/language features like code navigation, refactoring, and auto-completion. It&rsquo;s a smart way to avoid custom implementations of common code functionality in Nova while potentially supporting a wide variety of languages. My <a href="https://extensions.panic.com/extensions/apexskier/apexskier.typescript/">TypeScript extension</a> runs an LSP for most of its functionality, and the protocol has been great to work with. Nova&rsquo;s support for LSP features is not great (the Go to Definition command doesn&rsquo;t even use the active LSP client), but there&rsquo;s a lot of potential.</p>
<h2 id="community"><a class="heading-link" href="#community"></a>Community</h2>
<p>Extensions aside, one of the biggest reasons Nova could succeed is a passionate community. People love Panic&mdash;they&rsquo;re the dream example of a software company for many, including myself&mdash;and want to see this type of product succeed.</p>
<p>Reach out to me to join the Nova community Discord, where we&rsquo;re talking about extension development, bugs, and the product&rsquo;s future.</p>
<p>If you give Nova a try, <a href="https://extensions.panic.com/extensions/apexskier/">check out my extensions</a>.</p>


  
  ]]></content:encoded></item><item><title>Css overscroll styling</title><link>https://camlittle.com/posts/2020-05-15-css-overscroll-styling/</link><pubDate>Fri, 15 May 2020 20:32:52 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2020-05-15-css-overscroll-styling/</guid><enclosure url="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/thumbnail.png" length="30513" type="image/png"/><description>&lt;p>You may have heard about the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior">&lt;code>overscroll-behavior&lt;/code>&lt;/a> css property. It allows you to disable the default behavior when someone scrolls beyond the bounds of your website.&lt;/p>
&lt;p>
&lt;video autoplay controls height="300" style="height: 300px;" alt="basic-overscroll.mp4">
&lt;source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/basic-overscroll.0bcad16dfb74b2cb17e84ea507a6af3e.mp4" />
&lt;/video>
&lt;/p>
&lt;p>But maybe you don&amp;rsquo;t want to disable that nice physics interaction and want to have some fun with it instead. The goal here is to use css to add some sort of &amp;ldquo;hidden&amp;rdquo; content in the background of the site that&amp;rsquo;s only visible when over-scrolling.&lt;/p></description><content:encoded><![CDATA[
  
<p>You may have heard about the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior"><code>overscroll-behavior</code></a> css property. It allows you to disable the default behavior when someone scrolls beyond the bounds of your website.</p>
<p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="basic-overscroll.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/basic-overscroll.0bcad16dfb74b2cb17e84ea507a6af3e.mp4" />
      
  </video>



</p>
<p>But maybe you don&rsquo;t want to disable that nice physics interaction and want to have some fun with it instead. The goal here is to use css to add some sort of &ldquo;hidden&rdquo; content in the background of the site that&rsquo;s only visible when over-scrolling.</p>
<p>By default, there are already two elements to play with: <code>&lt;html&gt;</code> and <code>&lt;body&gt;</code>. The first thing to understand is the sizing of these elements. Both element&rsquo;s intrinsic content size is that of the containing content, but the <code>&lt;html&gt;</code> element is special, since its <em>background</em> takes up the full window size.</p>
<p><a href="https://codepen.io/apexskier/pen/WNQgNQp">





  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="sizing.png">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-05-15-css-overscroll-styling/sizing_6bdf2a703e4472d8504751a2edd04319_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          height="300"
          
          style="max-height: 300px;"
          src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/sizing.6bdf2a703e4472d8504751a2edd04319.png"
          alt="sizing.png"
      />
  </picture>



</a></p>
<p>First, I want to make sure the <code>&lt;body&gt;</code> always covers the full content, so I don&rsquo;t have anything in the background of the <code>&lt;html&gt;</code> peeking out at the bottom. I also need to make sure my layout doesn&rsquo;t have any margins collapsing outside of the body, which in this demo I&rsquo;ll do by making my body <code>flex</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">min-height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">vh</span><span class="p">;</span> <span class="c">/* full height */</span>
</span></span><span class="line"><span class="cl">	<span class="k">display</span><span class="p">:</span> <span class="kc">flex</span><span class="p">;</span> <span class="c">/* optional - prevents margin collapse */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>With a background color on the <code>&lt;html&gt;</code> element it&rsquo;s visible when I overscroll. However, this is &ldquo;fake&rdquo; content on desktop browsers&mdash;the html is still scrolling so its <em>contents</em> aren&rsquo;t shown (for example, a background image).</p>
<p>There are a couple of approaches I want to try to get content in there.</p>
<ol>
<li>Don&rsquo;t scroll the html - scroll the body instead</li>
</ol>
<p>Unfortunately (at least in the desktop browsers I tested) this doesn&rsquo;t work as I want. The only element that allows scrolling outside of its content is the <code>&lt;html&gt;</code> tag, so the behavior is the same as if I set <code>overscroll-behavior: none</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">html</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">overflow</span><span class="p">:</span> <span class="kc">hidden</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">overflow</span><span class="p">:</span> <span class="kc">scroll</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">vh</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">overscroll-behavior</span><span class="p">:</span> <span class="kc">contain</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="no-overscroll.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/no-overscroll.2424cf6343debf8c859078a7c27a4f07.mp4" />
      
  </video>



</p>
<ol start="2">
<li>Add fake fixed elements that don&rsquo;t scroll</li>
</ol>
<p>I can use some trusty pseudo elements to add content, right?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">html</span><span class="p">::</span><span class="nd">before</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">z-index</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">content</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">right</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">background-color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">position</span><span class="p">:</span> <span class="kc">fixed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">body</span><span class="p">::</span><span class="nd">before</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">z-index</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">content</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">background-color</span><span class="p">:</span> <span class="kc">blue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">position</span><span class="p">:</span> <span class="kc">fixed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">z-index</span><span class="p">:</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">background-color</span><span class="p">:</span> <span class="kc">white</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Well, not really. On desktop, even <code>position: fixed</code> and <code>position: sticky</code> won&rsquo;t scroll into the overflow area, just like a <code>background-image</code> of <code>&lt;html&gt;</code>. Another issue is that html pseudo elements display <em>above</em> the background of the <code>&lt;body&gt;</code>. Generally, a pseudo element follows the standard <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context">stacking context</a>: <code>body { background-color: white }</code> <em>should</em> cover pseudo elements of <code>&lt;html&gt;</code> the same as it covers <code>&lt;html&gt;</code>.</p>
<p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="desktop-pseudo-elements.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/desktop-pseudo-elements.14b777ef056c0fc266b315ba38af44ba.mp4" />
      
  </video>



</p>
<p>However, this confirms that iOS <em>doesn&rsquo;t</em> scroll fixed content when overscrolling. The body background issue persists though.</p>
<p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="ios-pseudo-elements.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/ios-pseudo-elements.82a12917dcd31f1518b0ebd8363aeed0.mp4" />
      
  </video>



</p>
<ol start="3">
<li>Add a wrapper element</li>
</ol>
<p>Now the the DOM gets a little messy. We can introduce a wrapper element (instead of using <code>&lt;body&gt;</code>) to cover the full document with a non-opaque background color.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">#</span><span class="nn">wrapper</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">min-height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">vh</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">min-width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">vw</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">background-color</span><span class="p">:</span> <span class="kc">white</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">		<span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;wrapper&#34;</span><span class="p">&gt;</span><span class="c">&lt;!-- content --&gt;</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>And we finally have a hidden background element!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">html</span><span class="p">::</span><span class="nd">before</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">z-index</span><span class="p">:</span> <span class="mi">-1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">position</span><span class="p">:</span> <span class="kc">fixed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">vh</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">vw</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">content</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>However, the browser support is pretty limited. As discovered above, desktop browsers scroll <code>fixed</code> elements during overscroll, meaning they&rsquo;re not &ldquo;revealed&rdquo; like I want. In iOS Safari, however, it&rsquo;s a pretty nice effect.</p>
<p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="final-effect.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/final-effect.daa5f1678c01be4c89f4475f88ed3f8c.mp4" />
      
  </video>



</p>
<p>Unfortunately, there&rsquo;s some glitchy behavior on desktop Safari where the background texture shows through un-rendered content.</p>
<p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="glitchy.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/glitchy.356351185cac0e31ea44b350ebdd0375.mp4" />
      
  </video>



</p>
<h3 id="bonus"><a class="heading-link" href="#bonus"></a>Bonus!</h3>
<p>We can use a hidden element and <code>position: sticky</code> to emulate snapchat&rsquo;s pull-to-refresh ghost animation with pure CSS.</p>
<p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="snapchat-demo.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/snapchat-demo.39905578b1d684e5f046357104ad77d4.mp4" />
      
  </video>



</p>
<p>





  
  <video autoplay controls height="300" style="height: 300px;" alt="css-snapchat-demo.mp4">
      
      <source src="https://camlittle.com/posts/2020-05-15-css-overscroll-styling/css-snapchat-demo.bceeefd866d630044b219b01b2c4c81c.mp4" />
      
  </video>



</p>
<p><a href="snap-ghost.html">Try it here</a></p>


  
  ]]></content:encoded></item><item><title>Coding is the easy part</title><link>https://camlittle.com/posts/2020-05-04-coding-is-the-easy-part/</link><pubDate>Mon, 04 May 2020 19:57:07 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2020-05-04-coding-is-the-easy-part/</guid><enclosure url="https://camlittle.com/posts/2020-05-04-coding-is-the-easy-part/thumbnail.png" length="77515" type="image/png"/><description>&lt;p>Coding is the easy part about writing software.&lt;/p>
&lt;p>The IDE, the language, and the execution of the code itself all support and guide me as I write code. Especially when executing on a pre-designed plan or refactoring, I can rely on the compiler to let me know when I&amp;rsquo;ve made a mistake and I can look at the UI to see if I&amp;rsquo;ve done it right. Some tasks can even feel refreshing because they don&amp;rsquo;t require hard thinking while seeing a lot of output.&lt;/p></description><content:encoded><![CDATA[
  
<p>Coding is the easy part about writing software.</p>
<p>The IDE, the language, and the execution of the code itself all support and guide me as I write code. Especially when executing on a pre-designed plan or refactoring, I can rely on the compiler to let me know when I&rsquo;ve made a mistake and I can look at the UI to see if I&rsquo;ve done it right. Some tasks can even feel refreshing because they don&rsquo;t require hard thinking while seeing a lot of output.</p>
<p>Pure technical design is far harder. Without supporting tools, a higher percent of the cost comes from my brain. I can&rsquo;t lean on a program to tell me I&rsquo;m doing something wrong or tell me what to think about next.</p>
<p>To help, I try to prototype in parallel. Whatever code I produce I leave out of review to avoid sunk-cost bias and avoid <a href="https://www.quora.com/What-is-the-meaning-of-the-phrase-you-cant-see-the-forest-for-the-trees">&ldquo;can&rsquo;t see the forest for the trees&rdquo; effect</a>.</p>


  
  ]]></content:encoded></item><item><title>When should you add abstraction?</title><link>https://camlittle.com/posts/when-to-add-abstractions/</link><pubDate>Thu, 23 Apr 2020 17:44:00 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/when-to-add-abstractions/</guid><enclosure url="https://camlittle.com/posts/when-to-add-abstractions/thumbnail.jpeg" length="574139" type="image/jpeg"/><description>&lt;p>I&amp;rsquo;ve made the mistake of adding layers of abstraction too early several times
over my career as a software developer. It starts out like this: I&amp;rsquo;ve got a
really smart idea that will save me a bunch of time in the future. I&amp;rsquo;ll be able
to create a generic layer that I can build other features beneath without
needing to change consumer behavior.&lt;/p>
&lt;p>The issue is that I can&amp;rsquo;t see the future. It&amp;rsquo;s close to impossible to
understand the details of the future use cases I&amp;rsquo;m building for, and more often
then not I&amp;rsquo;m going to get it not quite right. Once that happens, the work I put
in building my abstraction layer is far less valuable and it often gets in the
way, making my job harder.&lt;/p></description><content:encoded><![CDATA[
  
<p>I&rsquo;ve made the mistake of adding layers of abstraction too early several times
over my career as a software developer. It starts out like this: I&rsquo;ve got a
really smart idea that will save me a bunch of time in the future. I&rsquo;ll be able
to create a generic layer that I can build other features beneath without
needing to change consumer behavior.</p>
<p>The issue is that I can&rsquo;t see the future. It&rsquo;s close to impossible to
understand the details of the future use cases I&rsquo;m building for, and more often
then not I&rsquo;m going to get it not quite right. Once that happens, the work I put
in building my abstraction layer is far less valuable and it often gets in the
way, making my job harder.</p>
<p>Working in a fast-growing company also means that a lot of the time the future
use case I&rsquo;m building for just never happens. We decide to go another
direction, or pause on that project, or it just doesn&rsquo;t get prioritized. Two
years later, the system has evolved to the point where what I built just
doesn&rsquo;t make sense.</p>
<h2 id="truth-is-relative"><a class="heading-link" href="#truth-is-relative"></a>Truth is relative</h2>
<p>Over time, my understanding of systems will always change, due to the complex
and changing environment I work in. Be it because of the tools we use, the
people we work with, or the size of the organization, decisions are never
binary or always correct. Choosing to use a statically typed language might be
more important if the scale of the project is larger, or if a coworker hasn&rsquo;t
developed the muscles to think dynamically. Building a massively scalable
microservice architecture might mean you can&rsquo;t deliver an MVP in time to get
the funding needed to start growing your start-up. Implementing a policy to
never force-upgrade an app version doesn&rsquo;t work once Brexit happens.</p>
<h2 id="build-defensively"><a class="heading-link" href="#build-defensively"></a>Build defensively</h2>
<p>What you can do is build defensively. In essence, this means building with the
understanding that it will be wrong in the future. Encapsulation helps a lot with
this, as it enables you to extract or split apart functionality with less pain.
Another simple defensive technique is to write less code and save the up-front
and maintenance cost.</p>
<h2 id="my-rule-of-thumb"><a class="heading-link" href="#my-rule-of-thumb"></a>My rule of thumb</h2>
<blockquote>
<p>Unless you have three <em>real</em> use cases to build against, don&rsquo;t spend extra time making the solution generic.</p>
</blockquote>
<p>The word &ldquo;real&rdquo; is intentional because of my comments on predicting the future.</p>
<p>The words &ldquo;extra time&rdquo; are also intentional. Generic abstractions can be useful
on their own, to hide complex logic or simplify a difficult to use API, or to
build a layer of defence; but if the main value in abstraction is for future
elegance <em>it&rsquo;s not worth it</em>.</p>
<p>Building an abstraction layer is the most problematic with only a single use
case, primarily because it usually adds more maintenance overhead which slows
iteration and maintenance. The &ldquo;abstract&rdquo; interface will often look very similar
to the internal one and refactoring is often not any easier than IDE assisted
renaming.</p>
<p>Building with two use cases is also problematic. It will be very tempting to
add <a href="https://martinfowler.com/bliki/FlagArgument.html">flag-based logic</a> into the
implementation, or worse, the external interface. When a third use case is
added, the most obvious method is to continue using flag-logic, which ends up
coupling the interface to the implementation. The value of the abstraction is
lower but maintenance cost isn&rsquo;t.</p>
<p>Three use cases, however, covers enough complexity that you&rsquo;re forced to
understand what the real value of the abstraction is and create it in a way that
makes it easier to work with.</p>


  
  ]]></content:encoded></item><item><title>Typing tricks to reduce ambiguity</title><link>https://camlittle.com/posts/2020-04-03-typing-tricks-to-reduce-ambiguity/</link><pubDate>Fri, 03 Apr 2020 08:11:00 +0200</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2020-04-03-typing-tricks-to-reduce-ambiguity/</guid><enclosure url="https://camlittle.com/posts/2020-04-03-typing-tricks-to-reduce-ambiguity/thumbnail.png" length="19589" type="image/png"/><description>&lt;!--
A few notes on interactivity here
- no js, all toggling of language examples is done with css
- most of the controls text is set via css pseudo elements. this prevents
it from showing up in certain contexts, like copying and scraping
-->
&lt;p>One of the fundamentals of programming is defining interfaces, or contracts between parts of systems. Interfaces create layers of abstraction and allow scaling code beyond the complexity a single person can hold in their head. They appear at the level of function signatures, to dependency signatures, up to service-to-service network calls.&lt;/p></description><content:encoded><![CDATA[
  
<!--
A few notes on interactivity here
- no js, all toggling of language examples is done with css
- most of the controls text is set via css pseudo elements. this prevents
  it from showing up in certain contexts, like copying and scraping
-->
<p>One of the fundamentals of programming is defining interfaces, or contracts between parts of systems. Interfaces create layers of abstraction and allow scaling code beyond the complexity a single person can hold in their head. They appear at the level of function signatures, to dependency signatures, up to service-to-service network calls.</p>
<p>There are almost always grey areas within an individual interface; areas where behavior isn&rsquo;t explicitly defined. This causes a couple problems. Interpretations of behavior can differ between people or over time, leading to subtle bugs due to drifting behavior in different modules. It also requires overhead and more error handling in each consumer, as you&rsquo;ll see below.</p>
<p>In a strongly typed language there are some not-immediately-obvious techniques to reduce this ambiguity and create a more intuitive design.</p>
<p>I&rsquo;ll provide code examples throughout this post, available in several different languages.</p>
<!-- no <p> --> <input type="radio" name="lang" id="lang-ts" value="ts" /> <label for="lang-ts">Typescript</label><br />
<!-- no <p> --> <input type="radio" name="lang" id="lang-kt" value="kt" /> <label for="lang-kt">Kotlin</label><br />
<!-- no <p> --> <input type="radio" name="lang" id="lang-go" value="go" /> <label for="lang-go">Go</label><br />
<!-- no <p> --> <input type="radio" name="lang" id="lang-openapi" value="openapi" checked /> <label for="lang-openapi">OpenAPI Specification</label><br />
<!-- no <p> --> <input type="radio" name="lang" id="lang-all" value="all" /> <label for="lang-all">All of the above</label><br />
<h2 id="the-basics-comments"><a class="heading-link" href="#the-basics-comments"></a>The basics: Comments</h2>
<p>As the author, the easiest way to avoid ambiguity is through documenting your code.</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Foo</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">type</span><span class="o">:</span> <span class="kt">string</span><span class="p">;</span> <span class="c1">// describes how foo should be represented
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Bar</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">discount?</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">discountApplied?</span>: <span class="kt">boolean</span><span class="p">;</span> <span class="c1">// indicates if `discount` is possible, or actually applied, not present if no discount
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="k">val</span> <span class="py">type</span><span class="p">:</span> <span class="n">String</span> <span class="c1">// describes how foo should be represented
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Bar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="k">val</span> <span class="py">discount</span><span class="p">:</span> <span class="n">String</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">  <span class="k">val</span> <span class="py">discountApplied</span><span class="p">:</span> <span class="n">Boolean</span><span class="p">?</span> <span class="c1">// indicates if `discount` is possible, or actually applied, not present if no discount
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Foo</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Type</span> <span class="kt">string</span> <span class="c1">// describes how foo should be represented
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Bar</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Discount</span>        <span class="o">*</span><span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">DiscountApplied</span> <span class="o">*</span><span class="kt">bool</span> <span class="c1">// indicates if `discount` is possible, or actually applied, not present if no discount
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Foo</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">describes how foo should be represented</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Bar</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">discount</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">discountApplied</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">boolean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">indicates if `discount` is possible, or actually applied, not present if no discount</span><span class="w">
</span></span></span></code></pre></div></div>
<p>This helps, but couples you to additional tooling within your editor to expose without reading the source. Comments don&rsquo;t protect against human issues, such as misinterpretation or plain laziness, and conformance can&rsquo;t automatically be verified by build tooling.</p>
<h2 id="taking-advantage-of-typing"><a class="heading-link" href="#taking-advantage-of-typing"></a>Taking advantage of typing</h2>
<h3 id="enums"><a class="heading-link" href="#enums"></a>Enums</h3>
<p>A simple pattern is to use enumeration values. Explicit enumerations can reduce the need for inline error handling and allows for self-documentation through good naming.
This sounds simple, but I see people forget about enums often. The key question to ask is:</p>
<blockquote>
<p>Do I care about how the consumer <em>uses</em> this value?</p>
</blockquote>
<p>If the answer is yes, a string type doesn&rsquo;t capture the correct nuance.</p>
<!--
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
-->
<div class="example-code full-width" data-lang="ts">
<div class="code-comparison">
<div>
<p><strong>Before</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Foo</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">type</span><span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">convertFooTypeToX</span><span class="p">(</span><span class="nx">x</span>: <span class="kt">Foo</span><span class="p">)</span><span class="o">:</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">switch</span> <span class="p">(</span><span class="nx">x</span><span class="p">.</span><span class="kr">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="s2">&#34;a&#34;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="s2">&#34;1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="s2">&#34;b&#34;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="s2">&#34;2&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="s2">&#34;c&#34;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="s2">&#34;3&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="sb">`unknown type: </span><span class="si">${</span><span class="nx">x</span><span class="p">.</span><span class="kr">type</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div>
<p><strong>After</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">enum</span> <span class="nx">FooType</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">a</span> <span class="o">=</span> <span class="s2">&#34;a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">b</span> <span class="o">=</span> <span class="s2">&#34;b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">c</span> <span class="o">=</span> <span class="s2">&#34;c&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Foo</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">type</span><span class="o">:</span> <span class="nx">FooType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">convertFooTypeToX</span><span class="p">(</span><span class="nx">x</span>: <span class="kt">Foo</span><span class="p">)</span><span class="o">:</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">switch</span> <span class="p">(</span><span class="nx">x</span><span class="p">.</span><span class="kr">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">FooType.a</span>:
</span></span><span class="line"><span class="cl">      <span class="kt">return</span> <span class="s2">&#34;1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">FooType.b</span>:
</span></span><span class="line"><span class="cl">      <span class="kt">return</span> <span class="s2">&#34;2&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">FooType.c</span>:
</span></span><span class="line"><span class="cl">      <span class="kt">return</span> <span class="s2">&#34;3&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// no default is required to satisfy type checking
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
</div>
</div>
<div class="example-code full-width" data-lang="kt">
<div class="code-comparison">
<div>
<p><strong>Before</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">type</span><span class="p">:</span> <span class="n">String</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">convertFooTypeToX</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Foo</span><span class="p">):</span> <span class="n">String</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;a&#34;</span> <span class="o">-&gt;</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;b&#34;</span> <span class="o">-&gt;</span> <span class="s2">&#34;2&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;c&#34;</span> <span class="o">-&gt;</span> <span class="s2">&#34;3&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span> <span class="o">-&gt;</span> <span class="k">throw</span> <span class="n">Exception</span><span class="p">(</span><span class="s2">&#34;unknown type: </span><span class="si">${x.type}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div>
<p><strong>After</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">enum</span> <span class="k">class</span> <span class="nc">FooType</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">a</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">b</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">c</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">type</span><span class="p">:</span> <span class="n">FooType</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">convertFooTypeToX</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Foo</span><span class="p">):</span> <span class="n">String</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nc">FooType</span><span class="p">.</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nc">FooType</span><span class="p">.</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="s2">&#34;2&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nc">FooType</span><span class="p">.</span><span class="n">c</span> <span class="o">-&gt;</span> <span class="s2">&#34;3&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// no else is required to satisfy type checking
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div></div>
</div>
</div>
<div class="example-code full-width" data-lang="go">
<div class="code-comparison">
<div>
<p><strong>Before</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Foo</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Type</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">ConvertFooTypeToX</span><span class="p">(</span><span class="nx">x</span> <span class="nx">Foo</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">switch</span> <span class="nx">x</span><span class="p">.</span><span class="nx">Type</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;a&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;b&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;2&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;c&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;3&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nb">panic</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;unknown type: %s&#34;</span><span class="p">,</span> <span class="nx">x</span><span class="p">.</span><span class="nx">Type</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><!-- [Playground](https://play.golang.org/p/LPKcE9TPvvT) -->
</div>
<div>
<p><strong>After</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">FooType</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="nx">FooTypeA</span> <span class="nx">FooType</span> <span class="p">=</span> <span class="s">&#34;a&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">FooTypeB</span>         <span class="p">=</span> <span class="s">&#34;b&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">FooTypeC</span>         <span class="p">=</span> <span class="s">&#34;c&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Foo</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Type</span> <span class="nx">FooType</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">ConvertFooTypeToX</span><span class="p">(</span><span class="nx">x</span> <span class="nx">Foo</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">switch</span> <span class="nx">x</span><span class="p">.</span><span class="nx">Type</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="nx">FooTypeA</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="nx">FooTypeB</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;2&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="nx">FooTypeC</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="s">&#34;3&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// unfortunately, without real enums in go, we still need this
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nb">panic</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;unknown type: %s&#34;</span><span class="p">,</span> <span class="nx">x</span><span class="p">.</span><span class="nx">Type</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><!-- [Playground](https://play.golang.org/p/W6Zrdrp4OQt) -->
</div>
</div>
</div>
<div class="example-code full-width" data-lang="openapi">
<div class="code-comparison">
<div>
<p><strong>Before</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Foo</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span></code></pre></div></div>
<div>
<p><strong>After</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Foo</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">enum</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">a, b, c]</span><span class="w">
</span></span></span></code></pre></div></div>
</div>
</div>
<h3 id="string-formats"><a class="heading-link" href="#string-formats"></a>String formats</h3>
<p>Enums work well, but only work for explicit sets of values&mdash;they can&rsquo;t be applied if I don&rsquo;t know the full set of values. If I do know, adding anything new breaks backwards compatibility.</p>
<p>To get around this, I can use a type alias to provide more semantic information.</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">type</span> <span class="nx">HtmlType</span> <span class="o">=</span> <span class="kt">string</span><span class="p">;</span>
</span></span></code></pre></div></div>
<div class="example-code" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">typealias</span> <span class="n">HtmlType</span> <span class="p">=</span> <span class="n">String</span>
</span></span></code></pre></div></div>
<div class="example-code" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">HtmlType</span> <span class="kt">string</span>
</span></span></code></pre></div></div>
<div class="example-code" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Foo</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">html</span><span class="w">
</span></span></span></code></pre></div></div>
<p>There&rsquo;s a loophole: The consumer could create their own string and pass it in place of <code>HtmlType</code>. Depending on the serialization library and language, it can be possible to discourage this with custom deserialization of a non-constructable type.</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">type</span> <span class="nx">HtmlType</span> <span class="o">=</span> <span class="kt">string</span> <span class="o">&amp;</span> <span class="p">{</span> <span class="nx">__t</span><span class="o">:</span> <span class="s2">&#34;html_type&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">read</span><span class="p">(</span><span class="nx">response</span>: <span class="kt">Response</span><span class="p">)</span><span class="o">:</span> <span class="nx">HtmlType</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">body</span> <span class="kr">as</span> <span class="nx">HtmlType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">HtmlTypeDeserializer</span> <span class="n">extends</span> <span class="n">JsonDeserializer</span><span class="p">&lt;</span><span class="n">HtmlType</span><span class="p">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nd">@Override</span>
</span></span><span class="line"><span class="cl">  <span class="k">fun</span> <span class="nf">deserialize</span><span class="p">(</span><span class="n">JsonParser</span> <span class="n">jp</span><span class="p">,</span> <span class="n">DeserializationContext</span> <span class="n">ctx</span><span class="p">)</span> <span class="n">throws</span> <span class="n">IOException</span><span class="p">,</span> <span class="n">JsonProcessingException</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// read contents of jp...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="n">HtmlType</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@JsonDeserialize</span><span class="p">(</span><span class="n">using</span> <span class="p">=</span> <span class="n">HtmlTypeDeserializer</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">HtmlType</span> <span class="k">private</span> <span class="k">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">val</span> <span class="py">content</span><span class="p">:</span> <span class="n">String</span><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// no example in this language
</span></span></span></code></pre></div></div>
<div class="example-code" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="c"># no example in this language</span><span class="w">
</span></span></span></code></pre></div></div>
<p>In the real world, it&rsquo;s risky to migrate to a format from an existing enumeration. There&rsquo;s often logic in the client that depends on knowledge of a specific value, and all that logic needs to be transformed into an api driven model. It&rsquo;s tempting to keep it in the client, but that makes <em>removal</em> of the value not-backwards compatible and undocumented.</p>
<h3 id="nested-nullables"><a class="heading-link" href="#nested-nullables"></a>Nested nullables</h3>
<p>A common pattern I see when designing api responses is to use a flat structure like the following.</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Workout</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swim_id</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swim_miles</span>: <span class="kt">number</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swim_time</span>: <span class="kt">Time</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">bike_id</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">bike_miles</span>: <span class="kt">number</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">bike_time</span>: <span class="kt">Time</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">run_id</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">run_miles</span>: <span class="kt">number</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">run_time</span>: <span class="kt">Time</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">kotlin.time.Duration;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Workout</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">swim</span><span class="n">_id</span><span class="p">:</span> <span class="n">String</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">swim</span><span class="n">_miles</span><span class="p">:</span> <span class="n">String</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">swim</span><span class="n">_time</span><span class="p">:</span> <span class="n">Duration</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">bike</span><span class="n">_id</span><span class="p">:</span> <span class="n">String</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">bike</span><span class="n">_miles</span><span class="p">:</span> <span class="n">Double</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">bike</span><span class="n">_time</span><span class="p">:</span> <span class="n">Duration</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">run</span><span class="n">_id</span><span class="p">:</span> <span class="n">String</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">run</span><span class="n">_miles</span><span class="p">:</span> <span class="n">Double</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">run</span><span class="n">_time</span><span class="p">:</span> <span class="n">Duration</span><span class="p">?</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Workout</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">SwimID</span>    <span class="o">*</span><span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">SwimMiles</span> <span class="o">*</span><span class="kt">float64</span>
</span></span><span class="line"><span class="cl">	<span class="nx">SwimTime</span>  <span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span>
</span></span><span class="line"><span class="cl">	<span class="nx">BikeID</span>    <span class="o">*</span><span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">BikeMiles</span> <span class="o">*</span><span class="kt">float64</span>
</span></span><span class="line"><span class="cl">	<span class="nx">BikeTime</span>  <span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span>
</span></span><span class="line"><span class="cl">	<span class="nx">RunID</span>     <span class="o">*</span><span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">RunMiles</span>  <span class="o">*</span><span class="kt">float64</span>
</span></span><span class="line"><span class="cl">	<span class="nx">RunTime</span>   <span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Workout</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">swim_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">swim_miles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">swim_time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bike_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bike_miles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bike_time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run_miles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run_time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div></div>
<p>This leads to open questions: What happens a mix of <code>id</code>, <code>miles</code>, and <code>time</code> is present? Should I always expect <code>miles</code> to be present if <code>id</code> is present? How does <code>time</code> relate to <code>miles</code>?</p>
<p>By breaking up the flat structure these can be answered.</p>
<p>Here, it&rsquo;s clear that an <code>id</code> is always paired with <code>miles</code> and <code>time</code>, and each type of activity might be missing:</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Segment</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">miles</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">time</span>: <span class="kt">Time</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Workout</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swim</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">bike</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">run</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">kotlin.time.Duration;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Segment</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">id</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">miles</span><span class="p">:</span> <span class="n">Double</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">time</span><span class="p">:</span> <span class="n">Duration</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Workout</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">swim</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">bike</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">run</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Segment</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ID</span>    <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Miles</span> <span class="kt">float64</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Time</span>  <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Workout</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Swim</span> <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Bike</span> <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Run</span>  <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Segment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">miles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Workout</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">swim</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bike</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div></div>
<p>With this alternate structure, I can see that the other fields might be missing:</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Segment</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">miles</span>: <span class="kt">number</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">time</span>: <span class="kt">Time</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Workout</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swim</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">bike</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">run</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">kotlin.time.Duration;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Segment</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">id</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">miles</span><span class="p">:</span> <span class="n">Double</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">time</span><span class="p">:</span> <span class="n">Duration</span><span class="p">?</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Workout</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">swim</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">bike</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">run</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Segment</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ID</span>    <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Miles</span> <span class="o">*</span><span class="kt">float64</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Time</span>  <span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Workout</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Swim</span> <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Bike</span> <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Run</span>  <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Segment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">miles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Workout</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">swim</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bike</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div></div>
<p>And here, I understand that the statistics might be missing, but are tied together:</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Segment</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">stats</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">miles</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">time</span>: <span class="kt">Time</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Workout</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swim</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">bike</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">run</span>: <span class="kt">Segment</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">kotlin.time.Duration;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Stats</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">miles</span><span class="p">:</span> <span class="n">Double</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">time</span><span class="p">:</span> <span class="n">Duration</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Segment</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">id</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">stats</span><span class="p">:</span> <span class="n">Stats</span><span class="p">?</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Workout</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">swim</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">bike</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">run</span><span class="p">:</span> <span class="n">Segment</span><span class="p">?</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Stats</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Miles</span> <span class="kt">float64</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Time</span>  <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Segment</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ID</span>    <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Stats</span> <span class="o">*</span><span class="nx">Stats</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Workout</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Swim</span> <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Bike</span> <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Run</span>  <span class="o">*</span><span class="nx">Segment</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Stats</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">miles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Segment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">stats</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Stats&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Workout</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">swim</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bike</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Segment&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div></div>
<p>This avoids type asserations and null pointer errors<span class="example-code" data-lang="ts">: <code>workout.swim_id!</code></span><span class="example-code" data-lang="kt">: <code>workout.swim_id!!</code></span><span class="example-code" data-lang="go">: <code>panic: runtime error: invalid memory address or nil pointer dereference</code></span><span class="example-code" data-lang="openapi">.</span></p>
<h3 id="type-unions"><a class="heading-link" href="#type-unions"></a>Type unions</h3>
<p>Another type structure that can reduce ambiguity is a union type. Union types allow representing &ldquo;exclusive or&rdquo; in the interface and avoid locking in an inheritance structure. <span class="example-code full-width" data-lang="kt">Kotlin&rsquo;s <a href="https://kotlinlang.org/docs/reference/sealed-classes.html">sealed classes</a> are a good way to implement these.</span> <span class="example-code full-width" data-lang="ts">This is a <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types">first class feature in Typescript</a>.</span></p>
<p>For example, say I want to represent a figure on my site.</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">Figure</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">source</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">attr</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">height?</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">width?</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Figure</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">source</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">attr</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">height</span><span class="p">:</span> <span class="n">Int</span><span class="p">?,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">width</span><span class="p">:</span> <span class="n">Int</span><span class="p">?</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Figure</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Source</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Attr</span>   <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Height</span> <span class="o">*</span><span class="kt">int</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Width</span>  <span class="o">*</span><span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Figure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">attr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">height</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">width</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">nullable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div></div>
<p>This works, but allows for accidentally distorting the image by specifying a height and width that doesn&rsquo;t match the aspect ratio. A type union can prevent this.</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">type</span> <span class="nx">Size</span> <span class="o">=</span> <span class="p">{}</span> <span class="o">|</span> <span class="p">{</span> <span class="nx">height</span>: <span class="kt">number</span> <span class="p">}</span> <span class="o">|</span> <span class="p">{</span> <span class="nx">width</span>: <span class="kt">number</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">UnsizedFigure</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">source</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">attr</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">type</span> <span class="nx">Figure</span> <span class="o">=</span> <span class="nx">UnsizedFigure</span> <span class="o">&amp;</span> <span class="nx">Size</span><span class="p">;</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">sealed</span> <span class="k">class</span> <span class="nc">Figure</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">open</span> <span class="k">val</span> <span class="py">source</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">open</span> <span class="k">val</span> <span class="py">attr</span><span class="p">:</span> <span class="n">String</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UnsizedFigure</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">override</span> <span class="k">val</span> <span class="py">source</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">override</span> <span class="k">val</span> <span class="py">attr</span><span class="p">:</span> <span class="n">String</span>
</span></span><span class="line"><span class="cl"><span class="p">):</span> <span class="n">Figure</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">attr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">HeightSizedFigure</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">override</span> <span class="k">val</span> <span class="py">source</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">override</span> <span class="k">val</span> <span class="py">attr</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">height</span><span class="p">:</span> <span class="n">Int</span>
</span></span><span class="line"><span class="cl"><span class="p">):</span> <span class="n">Figure</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">attr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">WidthSizedFigure</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">override</span> <span class="k">val</span> <span class="py">source</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">override</span> <span class="k">val</span> <span class="py">attr</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="k">val</span> <span class="py">width</span><span class="p">:</span> <span class="n">Int</span>
</span></span><span class="line"><span class="cl"><span class="p">):</span> <span class="n">Figure</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">attr</span><span class="p">)</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">UnsizedFigure</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Source</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Attr</span>   <span class="kt">string</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">HeightSizedFigure</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">UnsizedFigure</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Height</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">WidthSizedFigure</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">UnsizedFigure</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Width</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Figure</span> <span class="kd">interface</span><span class="p">{}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">UnsizedFigure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">attr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Size</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">oneOf</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">height</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">width</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">SizedFigure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">allOf</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/UnsizedFigure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/Size&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Figure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">oneOf</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/UnsizedFigure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/components/schemas/SizedFigure&#34;</span><span class="w">
</span></span></span></code></pre></div></div>
<p>Type unions force explicit checking of which type is used. They&rsquo;re possible in many languages, but not all.</p>
<nav class="lang-controls">
<label for="lang-ts"></label>
<label for="lang-kt"></label>
<label for="lang-go"></label>
<label for="lang-openapi"></label>
</nav>
<div class="example-code full-width" data-lang="ts">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">isHeightSized</span><span class="p">(</span><span class="nx">x</span>: <span class="kt">Size</span><span class="p">)</span><span class="o">:</span> <span class="nx">x</span> <span class="k">is</span> <span class="p">{</span> <span class="nx">height</span>: <span class="kt">number</span> <span class="p">}</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="nx">x</span> <span class="kr">as</span> <span class="p">{</span> <span class="nx">height?</span>: <span class="kt">number</span> <span class="p">}).</span><span class="nx">height</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">isWidthSized</span><span class="p">(</span><span class="nx">x</span>: <span class="kt">Size</span><span class="p">)</span><span class="o">:</span> <span class="nx">x</span> <span class="k">is</span> <span class="p">{</span> <span class="nx">width</span>: <span class="kt">number</span> <span class="p">}</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="nx">x</span> <span class="kr">as</span> <span class="p">{</span> <span class="nx">width?</span>: <span class="kt">number</span> <span class="p">}).</span><span class="nx">width</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="kt">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kt" data-lang="kt"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">eval</span><span class="p">(</span><span class="n">figure</span><span class="p">:</span> <span class="n">Figure</span><span class="p">):</span> <span class="n">Int</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="n">figure</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">is</span> <span class="n">UnsizedFigure</span> <span class="o">-&gt;</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl">    <span class="k">is</span> <span class="n">HeightSizedFigure</span> <span class="o">-&gt;</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">is</span> <span class="n">WidthSizedFigure</span> <span class="o">-&gt;</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="go">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">eval</span><span class="p">(</span><span class="nx">figure</span> <span class="nx">Figure</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">switch</span> <span class="nx">t</span> <span class="o">:=</span> <span class="nx">figure</span><span class="p">.(</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="nx">UnsizedFigure</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="nx">HeightSizedFigure</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="nx">WidthSizedFigure</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">	<span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="nb">panic</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;Unknown type: %v&#34;</span><span class="p">,</span> <span class="nx">t</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></div>
<div class="example-code full-width" data-lang="openapi">
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="c"># See other languages for reference implementations.</span><span class="w">
</span></span></span></code></pre></div></div>
<h2 id="in-the-real-world"><a class="heading-link" href="#in-the-real-world"></a>In the real world</h2>
<p>These techniques can give great build-time safety when applied within a single codebase, but it&rsquo;s harder to apply that safety across different codebases (e.g. calling an api, or using a typescript dependency in a javascript codebase). Responsibility for conforming to the contract has been absolved from the consumer and has been shifted explicitly to the producer. Since there&rsquo;s less incentive for explicit protection through validation in client code, errors caused by backwards incompatibility are harder to track down. Documenting changes through the use of techniques like semver make this easy to avoid.</p>
<h3 id="introducing-into-an-existing-system"><a class="heading-link" href="#introducing-into-an-existing-system"></a>Introducing into an existing system</h3>
<p>In a well-established environment it&rsquo;s sometimes not possible to apply these patterns in the producer; especially when dealing with legacy systems or other teams. In these cases these patterns can be introduced in the middle of the stack, either within a fronting service or a module within the consuming client. This gives an explicit place to add validation and audit that the contract is being followed.</p>
<p>&nbsp;</p>
<p>To summarize: Introducing more complexity into your interface; be it a function call, library, or api; makes it harder for consumers to make the wrong assumptions without asking them to think about logic outside of their business domain.</p>


  
  ]]></content:encoded></item><item><title>Levels of understanding</title><link>https://camlittle.com/posts/levels-of-understanding/</link><pubDate>Thu, 26 Mar 2020 20:56:07 +0100</pubDate><guid isPermaLink="true">https://camlittle.com/posts/levels-of-understanding/</guid><enclosure url="https://camlittle.com/posts/levels-of-understanding/thumbnail.jpeg" length="14136" type="image/jpeg"/><description>&lt;p>Over my time working in the real world I&amp;rsquo;ve learned a lot about what it means to actually understand something. Naturally, this includes realizing that I don&amp;rsquo;t understand everything as well as I&amp;rsquo;d like to. I&amp;rsquo;ve also learned a lot about the process of learning, which has given me insights that have helped me grow faster along the way.&lt;/p>
&lt;p>To start, what does understanding actually mean?
To &lt;a href="https://www.goodreads.com/quotes/243913-in-general-i-feel-if-you-can-t-say-it-clearly">paraphrase&lt;/a> &lt;a href="https://www.goodreads.com/quotes/19421-if-you-can-t-explain-it-to-a-six-year-old">others&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>To fully understand something you should be able to explain it to someone else.&lt;/p></description><content:encoded><![CDATA[
  
<p>Over my time working in the real world I&rsquo;ve learned a lot about what it means to actually understand something. Naturally, this includes realizing that I don&rsquo;t understand everything as well as I&rsquo;d like to. I&rsquo;ve also learned a lot about the process of learning, which has given me insights that have helped me grow faster along the way.</p>
<p>To start, what does understanding actually mean?
To <a href="https://www.goodreads.com/quotes/243913-in-general-i-feel-if-you-can-t-say-it-clearly">paraphrase</a> <a href="https://www.goodreads.com/quotes/19421-if-you-can-t-explain-it-to-a-six-year-old">others</a>:</p>
<blockquote>
<p>To fully understand something you should be able to explain it to someone else.</p>
</blockquote>
<p>Getting there takes time. In my experience there are a number of stages along the way, and I&rsquo;ve found that recognizing where I&rsquo;m at helps me avoid mistakes and accelerates the learning process understanding faster.</p>
<p>In this post, I&rsquo;m going to describe these stages as I understand them and how I use them.</p>
<div class="levels">
<ol>
<li>I don&rsquo;t understand</li>
<li>I can copy and extend</li>
<li>I know enough to be dangerous</li>
<li>I&rsquo;ve got a bad feeling about this</li>
<li>I can explain what I know</li>
</ol>
</div>
<h3 id="i-dont-understand"><a class="heading-link" href="#i-dont-understand"></a>I don&rsquo;t understand</h3>
<p>At this point I&rsquo;m aware that I don&rsquo;t understand. I&rsquo;ve moved past the Peak of &ldquo;Mount Stupid&rdquo; and I&rsquo;m in the &ldquo;Valley of Despair&rdquo; in the <a href="https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect">Dunning–Kruger</a> curve. This is a safe place to be, since the though that I might be doing things wrong is top of mind. When I&rsquo;m writing code, I pay much more attention to what I&rsquo;m doing and try to communicate my questions to other reviewers, so they also take a closer look. I also take more time to build safeguards into my code during this phase because the likelyhood of future change is higher.</p>
<h3 id="i-can-copy-and-extend"><a class="heading-link" href="#i-can-copy-and-extend"></a>I can copy and extend</h3>
<p>At this stage, I can build upon existing patterns, and I&rsquo;m starting to see the shape of those patterns. This is where I generally learn the fastest, but only if I&rsquo;m paying attention. It&rsquo;s key here to avoid <a href="https://en.wikipedia.org/wiki/Cargo_cult_programming">cargo culting</a> while recognizing the cost&mdash;it takes time and mental energy.</p>
<p>When I&rsquo;m working with someone else who&rsquo;s at this level I trust them to work independently, given they have patterns to copy and their code is well-reviewed.</p>
<h3 id="i-know-enough-to-be-dangerous"><a class="heading-link" href="#i-know-enough-to-be-dangerous"></a>I know enough to be dangerous</h3>
<p>This is the riskiest level, and the best way to get through it quickly is to take personal responsibility. At this stage I know a few patterns, but not how to apply them. A specific example is when I first learned about <a href="https://blog.golang.org/using-go-modules">go modules</a> and <a href="https://golang.org/ref/spec#Packages">packages</a>. I knew how to set up my project to use packages from external dependencies, but not how to apply the package organization structure to my own project. In order to get stuff done my project grew to have have excessively long files and code duplication. Since I knew I wasn&rsquo;t following the right pattern, I was able to separate responsibility within my code to allow it to be extracted in the future.</p>
<p>A specific technique I use here is to read each line in my change and spot check if I have a basic understanding of why I had to change it. If I don&rsquo;t, I revert it and see what happens.</p>
<p>Double check all your assumptions and pay attention. Automated tooling and testing (e.g. automated tests and linting, good gitignore files) will help a lot here and reduce the need for hand-holding.</p>
<h3 id="ive-got-a-bad-feeling-about-this"><a class="heading-link" href="#ive-got-a-bad-feeling-about-this"></a>I&rsquo;ve got a bad feeling about this</h3>
<p>At this point I&rsquo;m able to, both with my own work and when reviewing others&rsquo;, instinctively recognize bad patterns and can sometimes suggestion better replacements. However, I struggle to adequately explain <em>why</em>, or to fully internalize my logic.</p>
<p>With a healthy team, this is a good place to be in. If your team has trust issues, this is a bad place to be. Without being able to explain change requests, the original author won&rsquo;t see the value. You&rsquo;ll either need to accept what you feel isn&rsquo;t quality code or risk breeding resentment.</p>
<p>I often feel stuck in this stage for a long time&mdash;sometimes years. I think this is normal. Systems are complex, but recognizing I&rsquo;m starting to see patterns allows me to be more intentional and introspective, which lets me grow faster.</p>
<h3 id="i-can-explain-what-i-know"><a class="heading-link" href="#i-can-explain-what-i-know"></a>I can explain what I know</h3>
<p>Finally, I&rsquo;m able to explain clearly. I should be able to simplify the concepts in a way that reduces complexity&mdash;such as drawing a systems diagram, creating an analogy, or writing a blog post.</p>
<p>In order to communicate concepts, especially to someone who&rsquo;s still learning, some nuance will be lost. At any given point I&rsquo;m missing some depth of detail simply because there are limits to the amount of complexity I can hold in my head at a single point in time.</p>
<p>It&rsquo;s also important to recognize that complete understanding is unobtainable. There are always differences in opinion and changes over time, and it&rsquo;s worth reconsidering your assumptions if something doesn&rsquo;t feel right.</p>


  
  ]]></content:encoded></item><item><title>How to bake your own wedding cake</title><link>https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/</link><pubDate>Sun, 22 Mar 2020 17:27:25 +0100</pubDate><guid isPermaLink="true">https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/</guid><enclosure url="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/thumbnail.jpg" length="50344" type="image/jpeg"/><description>&lt;p>I &lt;a href="http://localhost:1313/posts/wedding-website/">got married&lt;/a> last May (2019), and one of my main duties was making our wedding cake, which was a big undertaking.&lt;/p>
&lt;p>The idea started back in August or September of 2018, as we were approaching Aisha&amp;rsquo;s birthday. I&amp;rsquo;ve done quite a bit of baking over the years and Aisha, only half joking, requested a five-tier cake for her birthday. I decided I was up for the challenge and spend two-and-a-half &lt;em>days&lt;/em> making one. We&amp;rsquo;d been talking about the possibility of me making our wedding cake, so I decided to go all out and try a few new things. I ended up making a 25-pound, 5 tier cake with four different types of frosting (Italian meringue buttercream, French meringue buttercream, marshmallow based fondant, and stabilized whipped cream) and four different fillings (homemade blackberry jam, lemon curd, lavender buttercream, and whipped chocolate ganache) in the layers. I also used a cake batter that required pre-heating milk and baking from a hot liquid batter.&lt;/p></description><content:encoded><![CDATA[
  
<p>I <a href="http://localhost:1313/posts/wedding-website/">got married</a> last May (2019), and one of my main duties was making our wedding cake, which was a big undertaking.</p>
<p>The idea started back in August or September of 2018, as we were approaching Aisha&rsquo;s birthday. I&rsquo;ve done quite a bit of baking over the years and Aisha, only half joking, requested a five-tier cake for her birthday. I decided I was up for the challenge and spend two-and-a-half <em>days</em> making one. We&rsquo;d been talking about the possibility of me making our wedding cake, so I decided to go all out and try a few new things. I ended up making a 25-pound, 5 tier cake with four different types of frosting (Italian meringue buttercream, French meringue buttercream, marshmallow based fondant, and stabilized whipped cream) and four different fillings (homemade blackberry jam, lemon curd, lavender buttercream, and whipped chocolate ganache) in the layers. I also used a cake batter that required pre-heating milk and baking from a hot liquid batter.</p>
<p>Overall it was a bit rough around the edges. I didn&rsquo;t make enough fondant or frosting, I only had green food coloring, I didn&rsquo;t have quite the right tools&mdash;but the scale made up for it.</p>
<p>





  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="aisha_reaction.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction_b05116d4b02e48e460b14f65b93c9f1d_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          height="300"
          
          style="max-height: 300px;"
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/aisha_reaction.b05116d4b02e48e460b14f65b93c9f1d.jpeg"
          alt="aisha_reaction.jpeg"
      />
  </picture>



</p>
<p>After this first tiered cake, we committed&mdash;I was going to make our wedding cake.</p>
<p>We agreed that I&rsquo;d try to make one cake each month until the wedding, which meant about six full sized cakes, most of which were eaten by Aisha&rsquo;s and my coworkers.</p>



  
    
    
      
    
    
    
      
    
  

  
    
    
    
    
      
    
  

  
    
    
    
    
  

  
    
    
    
    
  


<ul class="post-gallery">

    
    
    
    <li style="flex: 720;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="2019-01-12.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12_0b9070765898003c222fb64ddac0131b_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-01-12.0b9070765898003c222fb64ddac0131b.jpeg"
          alt="2019-01-12.jpeg"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 1280;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="2019-02-03.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03_0ca22f99d9689380d2f31b4ed894d9ec_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-03.0ca22f99d9689380d2f31b4ed894d9ec.jpeg"
          alt="2019-02-03.jpeg"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 1280;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="2019-02-16.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16_0d463317c1c6fa85dcc8a0a99ef0ec6e_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-16.0d463317c1c6fa85dcc8a0a99ef0ec6e.jpeg"
          alt="2019-02-16.jpeg"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 720;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="2019-02-24.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24_765acb0c02774d58f24f85c9cbfb7279_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/2019-02-24.765acb0c02774d58f24f85c9cbfb7279.jpeg"
          alt="2019-02-24.jpeg"
      />
  </picture>


        </a>
    </li>
    

</ul>

<p>Throughout this process I learned more about what&rsquo;s special about a <em>wedding</em> cake and I fine-tuned my plans for the real thing. Wedding cakes need to be liked by everyone, they need to be able to sit around for a long time, and they need to look good.</p>
<p>For the flavor, we decided to go simple with broad appeal. We decided on a standard white sponge flavored with vanilla. Since I was doing this myself, I decided to do several different filling types (we weren&rsquo;t paying for the additional complexity). We went with a seedless raspberry jam on the bottom, a whipped chocolate ganache on the second tier, and lemon curd plus lavender on the top two tiers (for something more unique).</p>
<p>For cake durability, the main factor is frosting. Certain types, like those with raw eggs (meringue based) or cream cheese, risk food-borne illnesses after more than half a day or so. Others, like American buttercream (made with powdered sugar), develop an unpleasant crust after exposed to air for a few hours. Aisha also doesn&rsquo;t love excessively sweet or buttery frosting. Pretty early into process I discovered <a href="https://cooking.nytimes.com/recipes/1016330-ermine-icing">ermine icing</a>&mdash;also known as boiled milk frosting&mdash;and decided that it was perfect. Ermine frosting is stable for several days, doesn&rsquo;t dry out, and has a more complex flavour than most other buttercreams. During our wedding venue&rsquo;s open house one of the vendors affirmed my choice, and also advised me to bake three days before the event.</p>
<p>For appearance, I knew I couldn&rsquo;t get too fancy. My piping and decorating skills are not high. I decided to take advantage of my precision and patience&mdash;the qualities that make a good baker. We decided on a gray (Aisha&rsquo;s <a href="https://genius.com/11503998">favorite color</a>) ombre effect without additional frills. I also invested in some nicer frosting spatulas, cake pans, and tier supports to make sure the cake would be straight and even.</p>
<p>Three weeks before the wedding I did a full trial run. I made the jam and lemon curd, baked eight sets of cakes (four tiers × four layers ÷ two pans in the oven at a time), cooled, assembled, stacked, crumb-coated, frosted, ombre&rsquo;d, and finished after about a day and a half. I didn&rsquo;t aim for perfection in the trial, but focused on refining the process. I also tested out the most expensive part of this project, an <a href="https://www.amazon.com/gp/product/B00WQUOTN6/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;psc=1">insulated cake porter</a>. Luckily, my base was the same interior dimensions at 14 inches square. This provided a lot more stability because there wasn&rsquo;t opportunity for sliding.</p>



  
    
    
      
    
    
    
      
    
  

  
    
    
    
    
  

  
    
    
    
    
  

  
    
    
    
    
  


<ul class="post-gallery">

    
    
    
    <li style="flex: 960;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="testing the cake porter.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter_c41ed648b57121ec3cb10d750ae5fc14_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/testing%20the%20cake%20porter.c41ed648b57121ec3cb10d750ae5fc14.jpeg"
          alt="testing the cake porter.jpeg"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 960;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="raspberry jam.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam_7bd675764141f33737cb3ab2c4ad18f2_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/raspberry%20jam.7bd675764141f33737cb3ab2c4ad18f2.jpeg"
          alt="raspberry jam.jpeg"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 960;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="chocolate ganache.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache_d820a45bd2c84a592da7d27a72597d17_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/chocolate%20ganache.d820a45bd2c84a592da7d27a72597d17.jpeg"
          alt="chocolate ganache.jpeg"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 960;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="lemon &amp; lavender.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender_c0022c2be92a9b33f1ba4af1851dcf40_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/lemon%20&amp;%20lavender.c0022c2be92a9b33f1ba4af1851dcf40.jpeg"
          alt="lemon &amp; lavender.jpeg"
      />
  </picture>


        </a>
    </li>
    

</ul>

<p>The week leading up to the wedding I prepared the jam and lemon curd, since they were able to keep in the fridge. The day before baking, I boiled the milk for my frosting, since cooling was very important. On Friday, three days before the Sunday wedding, I managed to get the entire cake completed in 10 hours&mdash;beating my prior time and wearing me out. When testing out loading into the cake into the transporter, I had a bit of a scare&mdash;there was about 1 centimeter of vertical space between the top of the cake and the transporter (putting the cake at about 15 inches high in total).
The day of the wedding I smoothed out a final few rough spots on the frosting, added some floral decorations, and added some cake toppers&mdash;our ceramic rabbit salt and pepper shakers converted to sprinkles shakers!</p>
<p>Overall, this was a very rewarding experience, both in tastiness and sheer amount of cake.</p>
<hr>



  
    
    
      
    
    
    
      
    
  

  
    
    
    
    
      
    
  


<ul class="post-gallery">

    
    
    
    <li style="flex: 1140;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="assembling.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling_4c06777097e8ef3d72dd14c51d3af4c4_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/assembling.4c06777097e8ef3d72dd14c51d3af4c4.jpeg"
          alt="assembling.jpeg"
      />
  </picture>


        </a>
    </li>
    

    
    
    
    <li style="flex: 1280;">
        <a href="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo.jpeg" target="_blank">
            




  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="table photo.jpeg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo_cb112619b7cecedc40c34b89fd57153a_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          
          
          style=""
          src="https://camlittle.com/posts/2020-03-22-how-to-bake-your-own-wedding-cake/table%20photo.cb112619b7cecedc40c34b89fd57153a.jpeg"
          alt="table photo.jpeg"
      />
  </picture>


        </a>
    </li>
    

</ul>

<img src="final cake.jpeg" alt="The final product" height="500" />
<hr>
<h3 id="raw-notes"><a class="heading-link" href="#raw-notes"></a>Raw notes</h3>
<p>If you&rsquo;ve made it this far, you might be interested in my raw notes, including to the recipes I followed.</p>
<details>
<summary>Wedding cake.notes</summary>
<p>6x raspberry jam<br>
Lemon curd, set more<br>
Make curd and jam ahead of time (Monday or Wednesday)<br>
Boil milk day before (Thursday)<br>
Pre cool carrier<br>
Dowels in each layer (remember the middle!)<br>
Make sure layers aren’t too high</p>
<h4 id="ingredients"><a class="heading-link" href="#ingredients"></a>Ingredients</h4>
<h5 id="cake-layers-recipe-in-magazine"><a class="heading-link" href="#cake-layers-recipe-in-magazine"></a>Cake layers (recipe in magazine)</h5>
<ul>
<li>36 eggs</li>
<li>6 cups milk</li>
<li>2 tablespoons vanilla</li>
<li>3 lbs 6 oz cake flour
<ul>
<li>4+ tablespoons cornstarch</li>
</ul>
</li>
<li>4 lbs 9.5 oz sugar</li>
<li>.5 cup baking powder</li>
<li>2 tablespoons salt</li>
<li>9 sticks butter</li>
</ul>
<h5 id="ermine-frostinghttpscookingnytimescomrecipes1016330-ermine-icing"><a class="heading-link" href="#ermine-frostinghttpscookingnytimescomrecipes1016330-ermine-icing"></a><a href="https://cooking.nytimes.com/recipes/1016330-ermine-icing">Ermine Frosting</a></h5>
<ul>
<li>6 cups milk</li>
<li>2 cups less 2 tablespoons flour</li>
<li>2 tablespoons vanilla</li>
<li>6 pinches salt</li>
<li>12 sticks butter</li>
<li>6 cups sugar</li>
</ul>
<h5 id="lemon-curdhttpswwwtwopeasandtheirpodcomlemon-curd"><a class="heading-link" href="#lemon-curdhttpswwwtwopeasandtheirpodcomlemon-curd"></a><a href="https://www.twopeasandtheirpod.com/lemon-curd/">Lemon curd</a></h5>
<ul>
<li>3 eggs</li>
<li>1 cup sugar</li>
<li>1/2 cup lemon juice</li>
<li>1/2 stick butter</li>
<li>1 tablespoon lemon zest</li>
</ul>
<h5 id="raspberry-jamhttpswwwgeniuskitchencomreciperaspberry-cake-filling-152347refamp"><a class="heading-link" href="#raspberry-jamhttpswwwgeniuskitchencomreciperaspberry-cake-filling-152347refamp"></a><a href="https://www.geniuskitchen.com/recipe/raspberry-cake-filling-152347?ref=amp">Raspberry Jam</a></h5>
<ul>
<li>9 cups frozen raspberries</li>
<li>6 tablespoons cornstarch</li>
<li>3/4 cup lemon juice (bump up to 1 cup)</li>
<li>1 1/2 cup sugar</li>
</ul>
<h5 id="chocolate-moussehttpswwwricardocuisinecomenrecipes7472-chocolate-mousse-cake"><a class="heading-link" href="#chocolate-moussehttpswwwricardocuisinecomenrecipes7472-chocolate-mousse-cake"></a><a href="https://www.ricardocuisine.com/en/recipes/7472-chocolate-mousse-cake">Chocolate Mousse</a></h5>
<ul>
<li>7 oz dark chocolate</li>
<li>&lt;1 pack gelatin</li>
<li>2 tablespoons water</li>
<li>1/2 cup sugar</li>
<li>2 cups heavy cream</li>
</ul>
<h4 id="shopping-list"><a class="heading-link" href="#shopping-list"></a>Shopping List</h4>
<ul>
<li><input disabled="" type="checkbox"> 39 eggs</li>
<li><input disabled="" type="checkbox"> 1 gallon milk</li>
<li><input disabled="" type="checkbox"> 1 pint heavy cream</li>
<li><input disabled="" type="checkbox"> 22 sticks butter (~6 packs)</li>
<li><input disabled="" type="checkbox"> 5 lbs flour</li>
<li><input disabled="" type="checkbox"> Cornstarch</li>
<li><input disabled="" type="checkbox"> 8 lbs sugar</li>
<li><input disabled="" type="checkbox"> 1/2 cup baking powder</li>
<li><input disabled="" type="checkbox"> Vanilla extract</li>
<li><input disabled="" type="checkbox"> Gelatin</li>
<li><input disabled="" type="checkbox"> Salt</li>
<li><input disabled="" type="checkbox"> 4 lemons</li>
<li><input disabled="" type="checkbox"> 2.25 pounds raspberries</li>
<li><input disabled="" type="checkbox"> 7 oz dark chocolate</li>
<li><input disabled="" type="checkbox"> 3 oz cooking lavender
<p><br /></p>
</li>
<li><input disabled="" type="checkbox"> Cardboard rounds</li>
<li><input disabled="" type="checkbox"> Dowels</li>
</ul>
</details>


  
  ]]></content:encoded></item><item><title>Wedding Website</title><link>https://camlittle.com/posts/wedding-website/</link><pubDate>Thu, 14 Jun 2018 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/wedding-website/</guid><enclosure url="https://camlittle.com/posts/wedding-website/thumbnail.jpg" length="12044" type="image/jpeg"/><description>&lt;p>I&amp;rsquo;m getting married!&lt;/p>
&lt;p>Aside from being excited, I&amp;rsquo;m taking this as an opportunity to DIY some of the
pieces of weddings that could involve code.&lt;/p>
&lt;p>The most obvious part is our wedding website. When I&amp;rsquo;m building a small-scale
personal site, I usually try to choose technologies that will help me enhance
my existing skill set. Without the constraints of a legacy system, competing
priorities, or legacy browser support, I&amp;rsquo;m able to stay on the bleeding edge.
I also wanted to keep the site &amp;ldquo;raw&amp;rdquo; by writing as much pure html and css by
hand and using minimal javascript. A number of factors contribute to this,
including site performance, setup complexity, and the overload of web
development in 2018, but I also just wanted to do it.&lt;/p></description><content:encoded><![CDATA[
  
<p>I&rsquo;m getting married!</p>
<p>Aside from being excited, I&rsquo;m taking this as an opportunity to DIY some of the
pieces of weddings that could involve code.</p>
<p>The most obvious part is our wedding website. When I&rsquo;m building a small-scale
personal site, I usually try to choose technologies that will help me enhance
my existing skill set. Without the constraints of a legacy system, competing
priorities, or legacy browser support, I&rsquo;m able to stay on the bleeding edge.
I also wanted to keep the site &ldquo;raw&rdquo; by writing as much pure html and css by
hand and using minimal javascript. A number of factors contribute to this,
including site performance, setup complexity, and the overload of web
development in 2018, but I also just wanted to do it.</p>
<p>In this case, I set up a minimal build system with <a href="https://webpack.js.org">Webpack
4</a>, <a href="http://cssnext.io">cssnext</a>, and
<a href="https://babeljs.io">babel</a>. I haven&rsquo;t had time to sit down and configure
webpack from scratch for some time, so a couple loaders were new to me. I was
pleasently surprised by
<a href="https://github.com/survivejs/webpack-merge">webpack-merge</a> for splitting
development and production build configuration. I combined
<a href="https://webpack.js.org/loaders/extract-loader/https://webpack.js.org/loaders/extract-loader/">extract-loader</a>
and <a href="https://github.com/webpack-contrib/html-loader">html-loader</a> to manage css
and html, but I&rsquo;m not completely satisfied with this part of webpack.</p>
<p>I&rsquo;m continuing to use <a href="http://nginx.org">nginx</a> to host my websites and
recently upgraded to <code>https</code> using <a href="http://letsencrypt.org">Let&rsquo;s Encrypt</a>. I
took an additional step with this site and made the upgrade to <code>http2</code>. This
wasn&rsquo;t quite the learning experience I expected&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-patch" data-lang="patch"><span class="line"><span class="cl"><span class="gd">-  listen 443 ssl;
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+  listen 443 ssl http2;
</span></span></span></code></pre></div><p>I still managed to have some fun setting up my domain names.</p>
<p>While the primary url is
<a href="https://ourlittlewedding.love">https://ourlittlewedding.love</a>, I also
purchased two additional vanity hostnames:
<a href="https://aishaandcameron.love">aishaandcameron.love</a> and
<a href="https://cameronandaisha.love">cameronandaisha.love</a>. To do it right, I wanted
each domain to work, across both http and https, with or without <code>www</code>, with
minimal redirects. To do this, I created a shared ssl certificate for all six
domains.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">  Certificate Name: aishaandcameron.love
</span></span><span class="line"><span class="cl">    Domains: aishaandcameron.love cameronandaisha.love ourlittlewedding.love www.aishaandcameron.love www.cameronandaisha.love www.ourlittlewedding.love
</span></span></code></pre></div><p>Then it was a fairly simple matter of redirecting http traffic to the primary
domain to ssl</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">ourlittlewedding.love</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://ourlittlewedding.love</span><span class="nv">$request_uri</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>and redirecting any traffic to a non primary domain directly to the primary one</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">cameronandaisha.love</span> <span class="s">www.cameronandaisha.love</span> <span class="s">aishaandcameron.love</span> <span class="s">www.aishaandcameron.love</span> <span class="s">www.ourlittlewedding.love</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span> <span class="s">http2</span><span class="p">;</span> <span class="c1"># managed by Certbot
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kn">ssl_certificate</span> <span class="s">/etc/letsencrypt/live/aishaandcameron.love/fullchain.pem</span><span class="p">;</span> <span class="c1"># managed by Certbot
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kn">ssl_certificate_key</span> <span class="s">/etc/letsencrypt/live/aishaandcameron.love/privkey.pem</span><span class="p">;</span> <span class="c1"># managed by Certbot
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kn">include</span> <span class="s">/etc/letsencrypt/options-ssl-nginx.conf</span><span class="p">;</span> <span class="c1"># managed by Certbot
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kn">ssl_dhparam</span> <span class="s">/etc/letsencrypt/ssl-dhparams.pem</span><span class="p">;</span> <span class="c1"># managed by Certbot
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://ourlittlewedding.love</span><span class="nv">$request_uri</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>There might be a more efficent way to structure the configuration, but this
works well for me.</p>


  
  ]]></content:encoded></item><item><title>Slider</title><link>https://camlittle.com/posts/slider/</link><pubDate>Sun, 18 Sep 2016 16:54:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/slider/</guid><enclosure url="https://camlittle.com/posts/slider/thumbnail.jpg" length="3643" type="image/jpeg"/><description>&lt;p>Earlier this year my friend Cole approached me about motorizing his &lt;a href="https://www.google.com/search?q=video+slider&amp;amp;source=lnms&amp;amp;tbm=isch&amp;amp;sa=X&amp;amp;ved=0ahUKEwjxmMu1xdvQAhWDLmMKHajvCK8Q_AUICigD&amp;amp;biw=1280&amp;amp;bih=699&amp;amp;dpr=2">video
slider&lt;/a>.
Even though the project took far too long to finish, I&amp;rsquo;m very happy with the
results.&lt;/p>
&lt;p>The project ended up involving a bunch of fun stuff I didn&amp;rsquo;t have much
experience with, including 3D printing, Arduino, and driving a motor. The final
project self calibrates on startup, has variable speed, and is battery powered.
The slider can oscillate back and forth or move in a single direction.&lt;/p></description><content:encoded><![CDATA[
  
<p>Earlier this year my friend Cole approached me about motorizing his <a href="https://www.google.com/search?q=video+slider&amp;source=lnms&amp;tbm=isch&amp;sa=X&amp;ved=0ahUKEwjxmMu1xdvQAhWDLmMKHajvCK8Q_AUICigD&amp;biw=1280&amp;bih=699&amp;dpr=2">video
slider</a>.
Even though the project took far too long to finish, I&rsquo;m very happy with the
results.</p>
<p>The project ended up involving a bunch of fun stuff I didn&rsquo;t have much
experience with, including 3D printing, Arduino, and driving a motor. The final
project self calibrates on startup, has variable speed, and is battery powered.
The slider can oscillate back and forth or move in a single direction.</p>
<p><a href="http://www.adafruit.com/wishlists/420479">Example Parts List</a></p>
<p><a href="https://github.com/apexskier/slider">Source Code and Model files</a></p>
<h2 id="3d-printing"><a class="heading-link" href="#3d-printing"></a>3D Printing</h2>
<p>I&rsquo;ve been using <a href="http://www.openscad.org/">OpenSCAD</a> to model my prints.</p>
<figure>
    <video src="pully-timelapse.mov" width="420" controls muted></video>
    <img src="pully-mount.jpg" width="420" alt="Pully mount" />
    <figcaption>Pully mount for the far end of the slider</figcaption>
</figure>
<figure>
    <video src="slider-mount-timelapse.mov" width="420" controls muted></video>
    <img src="slider-mount.jpg" width="420" alt="Slider timing belt mount" />
    <figcaption>Timing belt mount for the camera platform</figcaption>
</figure>
<h2 id="arduino"><a class="heading-link" href="#arduino"></a>Arduino</h2>
<p>I decided on an Arduino Pro Mini because of its small size. I had no need for
internet connectivity so the feature set of the Mini worked well.</p>
<p>The basic structure of the code is a state machine. Each program loop watches
global variables and listens to events to determine how to drive the motor.
For more information, check out the <a href="https://github.com/apexskier/slider">project on
GitHub</a>.</p>
<p>Switches on either end of the slider are used for initial calibration, as a
safeguard throughout operation, and to trigger single directional movement. A
potentiometer sets speed and a button starts and stops oscillation.</p>
<p>





  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="wiring.jpg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/slider/wiring_4adff684294464a3fe6873a8c97f2296_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          height="300"
          
          style="max-height: 300px;"
          src="https://camlittle.com/posts/slider/wiring.4adff684294464a3fe6873a8c97f2296.jpg"
          alt="wiring.jpg"
      />
  </picture>



</p>
<h2 id="final-product"><a class="heading-link" href="#final-product"></a>Final Product</h2>
<figure>
    <img src="final-enclosure.jpg" width="360" alt="Slider control box" />
</figure>
<p>Take a look at Cole&rsquo;s <a href="https://www.instagram.com/p/BLUP33gD0N9/">Instagram</a>
or <a href="https://www.youtube.com/user/cole2paul">YouTube</a> to see some shots from the
final product.</p>
<h3 id="what-i-learned"><a class="heading-link" href="#what-i-learned"></a>What I Learned</h3>
<p>I was worried about excessive vibration and noise, but it&rsquo;s not noticeable
especially when using a heavier camera.</p>
<p>This was my first big 3D printing project, and I had more trouble than I&rsquo;d
like. This was mostly due to the cheapness of my printer: slow prints, not
great accuracy, and pretty terrible software. I also had to figure out how much
spacing to account for when printing pieces that fit together. I think the rule
I found was <code>.2mm</code> for a tight fit and <code>.4mm</code> for loose.</p>
<p>The biggest issue I had was when attempting to print large surfaces, like an
enclosure for the controls. I had some major warping issues; I think a heated
bed would help. After deciding to use a pre-built box instead of a custom one,
I ended up cracking it a few times before figuring out a good drilling
technique.</p>


  
  ]]></content:encoded></item><item><title>Live Share</title><link>https://camlittle.com/posts/live-share/</link><pubDate>Wed, 28 Oct 2015 11:15:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/live-share/</guid><enclosure url="https://camlittle.com/posts/live-share/thumbnail.jpg" length="14798" type="image/jpeg"/><description>&lt;img src="thumbnail.jpg" alt="Live Share icon" style="float: right;margin-left: 12px;">
&lt;p>Live Share is an app that allows you to export, edit, and create &lt;a href="http://www.apple.com/iphone-6s/cameras/">Live
Photos&lt;/a>.&lt;/p>
&lt;p>Edit: I&amp;rsquo;ve since abandoned this, but you can &lt;a href="https://github.com/apexskier/LiveGifs">view the code on GitHub&lt;/a>.&lt;/p>
&lt;h2 id="description">&lt;a class="heading-link" href="#description">&lt;/a>Description&lt;/h2>
&lt;p>Share your Live Photos as GIFs or quicktime movies quickly and easily. Send
motion to friends using other platforms.&lt;/p>
&lt;p>Tweak your Live Photos by trimming the video or changing the picture. Fix those
shots you missed by half a second.&lt;/p></description><content:encoded><![CDATA[
  
<img src="thumbnail.jpg" alt="Live Share icon" style="float: right;margin-left: 12px;">
<p>Live Share is an app that allows you to export, edit, and create <a href="http://www.apple.com/iphone-6s/cameras/">Live
Photos</a>.</p>
<p>Edit: I&rsquo;ve since abandoned this, but you can <a href="https://github.com/apexskier/LiveGifs">view the code on GitHub</a>.</p>
<h2 id="description"><a class="heading-link" href="#description"></a>Description</h2>
<p>Share your Live Photos as GIFs or quicktime movies quickly and easily. Send
motion to friends using other platforms.</p>
<p>Tweak your Live Photos by trimming the video or changing the picture. Fix those
shots you missed by half a second.</p>
<p>Create Live Photos from videos or GIFs. Create custom slow motion live
wallpapers or use your favorite meme on your lock screen. Create and share,
even from older iPhones such as the iPhone 6 and iPhone 5C.</p>
<h2 id="support"><a class="heading-link" href="#support"></a>Support</h2>
<p>This app is under active development, and may contain a few bugs. Tweet
<a href="https://twitter.com/apexskier">@apexskier</a> with any issues.</p>
<p>You can try deleting data from the app if things are behaving badly. Just tap
the info icon in the upper right and tap the &ldquo;Wipe Generated Data&rdquo; button.</p>
<h2 id="privacy-policy"><a class="heading-link" href="#privacy-policy"></a>Privacy Policy</h2>
<p>Live Share does not store any of your photos or videos outside of your device
and claims no ownership or liability for them. Your media will not be shared
with anyone. You use the app at your own risk; by using it you imply consent to
the information on this page even if it changes. Some anonymous information is
automatically collected by the developers and Apple for analytics and to
improve the quality of the app, but it will never be shared with a third party.
You can contact the developer at anytime via social media described on this
website.</p>


  
  ]]></content:encoded></item><item><title>Tap &amp; Seek</title><link>https://camlittle.com/posts/tap-and-seek/</link><pubDate>Mon, 30 Mar 2015 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/tap-and-seek/</guid><enclosure url="https://camlittle.com/posts/tap-and-seek/thumbnail.jpg" length="9330" type="image/jpeg"/><description>&lt;p>I made an app and &lt;a href="http://apexskier.github.io/SeeThere/">wrote about it&lt;/a>.&lt;/p></description><content:encoded>
  
&lt;p>I made an app and &lt;a href="http://apexskier.github.io/SeeThere/">wrote about it&lt;/a>.&lt;/p>


  
  </content:encoded></item><item><title>Homedo</title><link>https://camlittle.com/posts/homedo/</link><pubDate>Fri, 28 Feb 2014 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/homedo/</guid><enclosure url="https://camlittle.com/posts/homedo/thumbnail.jpg" length="4103" type="image/jpeg"/><description>&lt;p>After playing with my Raspberry Pi for the better part of the year, I&amp;rsquo;d been
wanting to do something that takes advantage of the Pi&amp;rsquo;s hardware. What follows
is a description of the home automation system I&amp;rsquo;ve built so far, called
&lt;a href="https://github.com/apexskier/homedo">Homedo&lt;/a>.&lt;/p>
&lt;h2 id="inspiration">&lt;a class="heading-link" href="#inspiration">&lt;/a>Inspiration&lt;/h2>
&lt;p>I live in a small apartment, so it&amp;rsquo;s hard to do much interfacing with the house
without violating my lease by ripping holes in the walls. It was really cold
this winter, so I decided to try upgrading out thermostat. The existing one was
as basic as I can imagine: just a basic &lt;a href="http://en.wikipedia.org/wiki/Thermostat#Simple_two_wire_thermostats">temperature controlled
switch&lt;/a>
opening and closing the circuit to our living room&amp;rsquo;s baseboard heater.&lt;/p></description><content:encoded><![CDATA[
  
<p>After playing with my Raspberry Pi for the better part of the year, I&rsquo;d been
wanting to do something that takes advantage of the Pi&rsquo;s hardware. What follows
is a description of the home automation system I&rsquo;ve built so far, called
<a href="https://github.com/apexskier/homedo">Homedo</a>.</p>
<h2 id="inspiration"><a class="heading-link" href="#inspiration"></a>Inspiration</h2>
<p>I live in a small apartment, so it&rsquo;s hard to do much interfacing with the house
without violating my lease by ripping holes in the walls. It was really cold
this winter, so I decided to try upgrading out thermostat. The existing one was
as basic as I can imagine: just a basic <a href="http://en.wikipedia.org/wiki/Thermostat#Simple_two_wire_thermostats">temperature controlled
switch</a>
opening and closing the circuit to our living room&rsquo;s baseboard heater.</p>
<h2 id="hardware"><a class="heading-link" href="#hardware"></a>Hardware</h2>
<p>Before this project I hadn&rsquo;t messed with physical circuits since my elementary
school days and some simple connect the dots type kits. I did quite a bit of
breadboarding and testing in order to avoid burning down the house or burning
out my Pi (whoops). This <a href="http://www.susa.net/wordpress/2012/06/raspberry-pi-relay-using-gpio/">article about driving a
relay</a>
helped a ton, along with tutorials by Adafruit. I eventually ordered a pcb from
<a href="http://makeatronics.blogspot.com/2013/06/24v-ac-solid-state-relay-board.html">Nich
Fugul</a>
and some components from Digikey to interface with the household wiring.</p>
<p>I&rsquo;ve got a few circuits set up on the Pi currently: gpio to the pcb, gpio to a
lower current relay, and gpio to an Adafruit I2C controller for some <a href="https://github.com/apexskier/ledDriver">led
lights</a>.</p>
<h2 id="software"><a class="heading-link" href="#software"></a>Software</h2>
<p>I wanted to create something intelligent, similar to the Nest, that would make
my life easier. This involves stuff I know nothing about: machine learning.</p>
<p>To get the familiar code out of the way, I wrote the basic thermostat in
Python, using the WiringPi2 library and the Bottle web framework to put it
online. The user interface uses gevent-websockets for real time information and
updating.</p>
<h3 id="intelligence"><a class="heading-link" href="#intelligence"></a>Intelligence</h3>
<p>My machine learning algorithm went through several iterations. Initially my
algorithm simply checked the temperature every five minutes, compared it to
what it knew about, and updated a confidence measurement and a target
temperature. There&rsquo;s a number of issues with this approach that became apparent
quickly. The main one is that when I change the temperature, five minutes later
the learner changes it back.</p>
<p>My seconds approach introduced the ideas of events. The learner keeps a list of
events it&rsquo;s seen, and when it sees a user set temperature action searches for a
similar event. If it finds one, that event is given a change change, which then
is saved if it&rsquo;s seen multiple times. If not, a new event is saved with a
degree of uncertainty, which then can be saved for certain if repeated.</p>
<p>This approach was much better, but has scalability issues, especially on the
limited capacity of a Raspberry Pi. I realized that the only information I need
to know to track events is what event just happened and what event is next.
Anything the user does won&rsquo;t affect any other event. The natural solution for
this is a circular doubly linked list. With this solution I only need to keep a
pointer to the last event. I can walk the list to not only see the next
&lsquo;certain&rsquo; event, but any &lsquo;uncertain&rsquo; ones in between. I don&rsquo;t need to know how
many events there are or anything about any other event at any time.  When an
event happens, I simply schedule the next one to happen at it&rsquo;s specified time.</p>
<p>When an unexpected event happens (the user gives input), my program can check
if it&rsquo;s close to the last or next event and make an appropriate adjustment,
cancelling the next event if necessary. If not, I can insert a new uncertain
event into the chain, for saving if something similar happens on the next walk
through. I&rsquo;ve also got logic that accounts for the user changing their mind,
and making minor adjustment.</p>
<h2 id="user-interface"><a class="heading-link" href="#user-interface"></a>User interface</h2>
<p>I&rsquo;ve built a web based user interface using the <a href="http://d3js.org">D3.js</a>
javascript library and a little bit of <a href="http://getbootstrap.com">Bootstrap</a>.
The main thermostat control shows the current target temperature, the current
temperature, and the next scheduled change. There&rsquo;s also a data page, showing a
graph of the house&rsquo;s temperature over the last week, the schedule, and various
user set events not saved as certain.</p>
<p>
<img src="on.png?w=250" alt="Thermostat on" />



<img src="off.png?w=250" alt="Thermostat off" />

</p>
<h2 id="using-it-in-the-real-world"><a class="heading-link" href="#using-it-in-the-real-world"></a>Using it in the real world</h2>
<p>I&rsquo;ve had the thermostat running for a couple weeks, and the machine learning
stuff for about a week. I&rsquo;m still fixing bugs, and with the amount of variation
and concurrency involved there&rsquo;s guaranteed to be more. However, it&rsquo;s working
well, and has been a great learning experience. I&rsquo;ve got a few more ideas for
improving my learning algorithms, and I&rsquo;d like to improve my system for keeping
the heat in the house even, but I&rsquo;m very proud of where I&rsquo;m at so far.</p>
<p>Check out the project <a href="https://github.com/apexskier/homedo">on GitHub</a>.</p>
<hr>
<p>





  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="graph.png">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/graph_b8f961e0f61942404c5869c52c5285e1_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          height="300"
          
          style="max-height: 300px;"
          src="https://camlittle.com/posts/homedo/graph.b8f961e0f61942404c5869c52c5285e1.png"
          alt="graph.png"
      />
  </picture>



</p>
<hr>
<p>





  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="IMG_1279.JPG">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/IMG_1279_39f331a996fa88e9f4c26fad8e11573a_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          height="300"
          
          style="max-height: 300px;"
          src="https://camlittle.com/posts/homedo/IMG_1279.39f331a996fa88e9f4c26fad8e11573a.JPG"
          alt="IMG_1279.JPG"
      />
  </picture>



</p>
<hr>
<p>





  
  
  
  
  
  
  
  
  

  
  
  
  
  
  

  <picture title="homedohardware.jpg">
      
      
        
        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_1200x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 1200px), (min-resolution: 192dpi) and (min-width: 1200px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_1200x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_1200x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 1200px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_1200x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_992x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 992px), (min-resolution: 192dpi) and (min-width: 992px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_992x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_992x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 992px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_992x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_768x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 768px), (min-resolution: 192dpi) and (min-width: 768px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_768x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_768x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 768px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_768x0at1x.jpg"
    type="image/jpeg"
/>


        
          







<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_480x0at2x.webp"
    type="image/webp"
/>
<source
    media="(-webkit-min-device-pixel-ratio: 2) and (min-width: 480px), (min-resolution: 192dpi) and (min-width: 480px)"
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_480x0at2x.jpg"
    type="image/jpeg"
/>



<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_480x0at1x.webp"
    type="image/webp"
/>
<source
    
    media="min-width: 480px"
    
    srcset="https://content.camlittle.com/site-media/posts/homedo/homedohardware_7070d09c4b7073c55855a28e059ea28a_480x0at1x.jpg"
    type="image/jpeg"
/>


        
      
      <img
          height="300"
          
          style="max-height: 300px;"
          src="https://camlittle.com/posts/homedo/homedohardware.7070d09c4b7073c55855a28e059ea28a.jpg"
          alt="homedohardware.jpg"
      />
  </picture>



</p>


  
  ]]></content:encoded></item><item><title>Max-height and focusing</title><link>https://camlittle.com/posts/max-height-focusing/</link><pubDate>Tue, 26 Nov 2013 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/max-height-focusing/</guid><enclosure url="https://camlittle.com/posts/max-height-focusing/thumbnail.jpg" length="2059" type="image/jpeg"/><description>&lt;p>Click on the box below, and tab through the subsequent links in Chrome (or
Safari) and Firefox (or Opera).&lt;/p>
&lt;div class="set-focus" tabindex="0">Set Focus&lt;/div>
&lt;style>
.set-focus {
background: #ccc;
display: inline-block;
padding: 5px 10px;
}
.sr-only {
position: absolute;
border: 0;
}
.skip {
margin: 0 auto;
background: #000;
color: red;
height: auto;
width: 100px;
padding: 0px;
-webkit-transition: max-height 200ms, padding 200ms;
-moz-transition: max-height 200ms, padding 200ms;
transition: max-height 200ms, padding 200ms;
}
.skip:focus,
.skip:active {
padding: 0.5em 1.3em;
max-height: 40px;
z-index: 1000;
}
.skip1 {
right: 20px;
max-height: 1px;
}
.skip2 {
right: 130px;
overflow: visible;
max-height: 0px;
}
.skip3 {
right: 240px;
clip: rect(0 0 0 0);
max-height: 0px;
}
.skip4 {
right: 350px;
display: block;
max-height: 0px;
}
.skip5 {
right: 460px;
clip: rect(0 0 0 0);
max-height: 1px;
}
.skip3:focus,
.skip5:focus {
clip: auto;
}
.focus-max-height-demo {
height: 60px;
position: relative;
overflow-x: scroll;
}
&lt;/style>
&lt;div class="focus-max-height-demo">
&lt;a href="#" class="skip1 skip sr-only">skip1&lt;/a>
&lt;a href="#" class="skip2 skip sr-only">skip2&lt;/a>
&lt;a href="#" class="skip3 skip sr-only">skip3&lt;/a>
&lt;a href="#" class="skip4 skip sr-only">skip4&lt;/a>
&lt;a href="#" class="skip5 skip sr-only">skip5&lt;/a>
&lt;/div>
&lt;p>In webkit browsers, you&amp;rsquo;re able to focus links unless they have the css &lt;code>max-height: 0&lt;/code>. In
Firefox and Opera you can access all links.&lt;/p></description><content:encoded><![CDATA[
  
<p>Click on the box below, and tab through the subsequent links in Chrome (or
Safari) and Firefox (or Opera).</p>
<div class="set-focus" tabindex="0">Set Focus</div>
<style>
    .set-focus {
        background: #ccc;
        display: inline-block;
        padding: 5px 10px;
    }
    .sr-only {
        position: absolute;
        border: 0;
    }
    .skip {
        margin: 0 auto;
        background: #000;
        color: red;
        height: auto;
        width: 100px;
        padding: 0px;
        -webkit-transition: max-height 200ms, padding 200ms;
           -moz-transition: max-height 200ms, padding 200ms;
                transition: max-height 200ms, padding 200ms;
    }
    .skip:focus,
    .skip:active {
        padding: 0.5em 1.3em;
        max-height: 40px;
        z-index: 1000;
    }
    .skip1 {
        right: 20px;
        max-height: 1px;
    }
    .skip2 {
        right: 130px;
        overflow: visible;
        max-height: 0px;
    }
    .skip3 {
        right: 240px;
        clip: rect(0 0 0 0);
        max-height: 0px;
    }
    .skip4 {
        right: 350px;
        display: block;
        max-height: 0px;
    }
    .skip5 {
        right: 460px;
        clip: rect(0 0 0 0);
        max-height: 1px;
    }
    .skip3:focus,
    .skip5:focus {
        clip: auto;
    }
    .focus-max-height-demo {
        height: 60px;
        position: relative;
        overflow-x: scroll;
    }
</style>
<div class="focus-max-height-demo">
    <a href="#" class="skip1 skip sr-only">skip1</a>
    <a href="#" class="skip2 skip sr-only">skip2</a>
    <a href="#" class="skip3 skip sr-only">skip3</a>
    <a href="#" class="skip4 skip sr-only">skip4</a>
    <a href="#" class="skip5 skip sr-only">skip5</a>
</div>
<p>In webkit browsers, you&rsquo;re able to focus links unless they have the css <code>max-height: 0</code>. In
Firefox and Opera you can access all links.</p>
<p>I stumbled across this quirk when animating a <a href="http://webaim.org/techniques/skipnav/">skip to content
link</a> on the new WWU Housing site. It
seems that webkit browsers disable keyboard focus to elements that have a max-height of
0.</p>
<p>This could have significant implications for accessibility if a visually
impaired user was using a site not tested in Webkit.</p>
<p>I tested a bunch of variations of styles, but nothing overrides the max-height.
skip1 is pretty basic, skip2 changes overflow, skip3 messes with cilp,
and skip4 messes with display.</p>
<p>I&rsquo;d advise using the styles set for skip5 if you need to accomplish something
similar.</p>
<p>The relevant style and html is below.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">sr-only</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">border</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">margin</span><span class="p">:</span> <span class="mi">0</span> <span class="kc">auto</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">background</span><span class="p">:</span> <span class="mh">#000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">color</span><span class="p">:</span> <span class="kc">red</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">height</span><span class="p">:</span> <span class="kc">auto</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">padding</span><span class="p">:</span> <span class="mi">0</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kp">-webkit-</span><span class="k">transition</span><span class="p">:</span> <span class="k">max-height</span> <span class="mi">200</span><span class="kt">ms</span><span class="p">,</span> <span class="k">padding</span> <span class="mi">200</span><span class="kt">ms</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kp">-moz-</span><span class="k">transition</span><span class="p">:</span> <span class="k">max-height</span> <span class="mi">200</span><span class="kt">ms</span><span class="p">,</span> <span class="k">padding</span> <span class="mi">200</span><span class="kt">ms</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">transition</span><span class="p">:</span> <span class="k">max-height</span> <span class="mi">200</span><span class="kt">ms</span><span class="p">,</span> <span class="k">padding</span> <span class="mi">200</span><span class="kt">ms</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip</span><span class="p">:</span><span class="nd">focus</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip</span><span class="p">:</span><span class="nd">active</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">padding</span><span class="p">:</span> <span class="mf">0.5</span><span class="kt">em</span> <span class="mf">1.3</span><span class="kt">em</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">max-height</span><span class="p">:</span> <span class="mi">40</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">z-index</span><span class="p">:</span> <span class="mi">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip1</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">right</span><span class="p">:</span> <span class="mi">20</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">max-height</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip2</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">right</span><span class="p">:</span> <span class="mi">130</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">overflow</span><span class="p">:</span> <span class="kc">visible</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">max-height</span><span class="p">:</span> <span class="mi">0</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip3</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">right</span><span class="p">:</span> <span class="mi">240</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">clip</span><span class="p">:</span> <span class="nb">rect</span><span class="p">(</span><span class="mi">0</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">max-height</span><span class="p">:</span> <span class="mi">0</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip4</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">right</span><span class="p">:</span> <span class="mi">350</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">display</span><span class="p">:</span> <span class="kc">block</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">max-height</span><span class="p">:</span> <span class="mi">0</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip5</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">right</span><span class="p">:</span> <span class="mi">460</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">clip</span><span class="p">:</span> <span class="nb">rect</span><span class="p">(</span><span class="mi">0</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">max-height</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip3</span><span class="p">:</span><span class="nd">focus</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nc">skip5</span><span class="p">:</span><span class="nd">focus</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">clip</span><span class="p">:</span> <span class="kc">auto</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;skip1 skip sr-only&#34;</span><span class="p">&gt;</span>skip1<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;skip2 skip sr-only&#34;</span><span class="p">&gt;</span>skip2<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;skip3 skip sr-only&#34;</span><span class="p">&gt;</span>skip3<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;skip4 skip sr-only&#34;</span><span class="p">&gt;</span>skip4<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;skip5 skip sr-only&#34;</span><span class="p">&gt;</span>skip5<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></div>

  
  ]]></content:encoded></item><item><title>TeaHarmony</title><link>https://camlittle.com/posts/teaharmony/</link><pubDate>Mon, 28 Oct 2013 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/teaharmony/</guid><enclosure url="https://camlittle.com/posts/teaharmony/thumbnail.jpg" length="2414" type="image/jpeg"/><description>&lt;p>I&amp;rsquo;m part of the Brewing Club here at WWU, which is funny, because I pretty much
only drink water. My roommate started it the spring before last, though, and
since then it&amp;rsquo;s become quite popular.&lt;/p>
&lt;p>One of the things people ask us most is where they can find a
&lt;a href="http://en.wikipedia.org/wiki/SCOBY">scoby&lt;/a> to brew
&lt;a href="http://en.wikipedia.org/wiki/Kombucha">kombucha&lt;/a>. During each fermentation a
baby which be given to someone else forms. The original mother is usually
reused. Unfortunately, finding someone with a baby can be difficult, as they
are usually tossed if not given away, and purchasing online is expensive and
not guaranteed to work.&lt;/p></description><content:encoded><![CDATA[
  
<p>I&rsquo;m part of the Brewing Club here at WWU, which is funny, because I pretty much
only drink water. My roommate started it the spring before last, though, and
since then it&rsquo;s become quite popular.</p>
<p>One of the things people ask us most is where they can find a
<a href="http://en.wikipedia.org/wiki/SCOBY">scoby</a> to brew
<a href="http://en.wikipedia.org/wiki/Kombucha">kombucha</a>. During each fermentation a
baby which be given to someone else forms. The original mother is usually
reused. Unfortunately, finding someone with a baby can be difficult, as they
are usually tossed if not given away, and purchasing online is expensive and
not guaranteed to work.</p>
<p>We&rsquo;ve talked about setting up a website to exchange SCOBY&rsquo;s and
other brewing cultures for a while, and it&rsquo;s finally ready to launch.</p>
<p>I decided to try out <a href="http://nodejs.org">Node.js</a> for this site, as I&rsquo;ve seen
it used in number of tutorials and projects I&rsquo;ve stumbled upon and I&rsquo;m very
comfortable with javascript. After researching alternative databases to MySQL
(why not try something new?), I decided to go javascript all the way and use
<a href="http://couchdb.apache.org">CouchDB</a>. One language makes a lot of sense for a
quick, single developer project. Not having to switch my brain from one to
another makes development faster and keeps code design consistant.</p>
<p>CouchDB is very interesting for someone who&rsquo;s familiar with the relational
database model. Here what I&rsquo;m noticing.</p>
<ul>
<li>Keys and values are much different. A key corresponds to value, but both can
be a full objects. This allows matching entities with objects, arrays, or
standard values.</li>
<li>You don&rsquo;t write queries like you do in SQL. You explicitly tell the database
how to return your data. If you&rsquo;re setting up the view at the same time as
your app, neither restricts what you do; you just have to make sure they&rsquo;re
on the same page.</li>
</ul>
<p>Node is great to work with. It&rsquo;s much simpler than something like apache, and
is well documented. NPM is probably the best part, primarily because it
installs locally to the project. If I&rsquo;ve messed something up, I can just <code>rm -r node_modules &amp;&amp; npm install</code>.</p>
<p>For TeaHarmony, I&rsquo;m using the following modules.</p>
<ul>
<li><a href="http://expressjs.com/">express</a> - Web app framework<br>
Express makes mapping post and get requests to specific urls super easy. It
also let me do some nifty things to redirect to and from the login page.</li>
<li><a href="http://passportjs.org/">Passport</a> - Authentication<br>
Passport has a bunch of built in hooks for authentication through websites
like Google, Twitter, and Facebook, but I&rsquo;m just using username/password
authentication. I wrote <a href="https://github.com/apexskier/teaharmony/blob/master/app.js#L124">some
code</a> to
talk to CouchDB&rsquo;s _user table through nano. It essentially pretends to be a
form post to get a cookie.</li>
<li><a href="http://jade-lang.com/">Jade</a> - Templating<br>
Jade was the default when I used express(1) to generate a skeleton app. So
far I like it, as it&rsquo;s basically minimal html with some programming fun
thrown in.</li>
<li><a href="https://github.com/dscape/nano">nano</a> - CouchDB ⇄ Node.js<br>
Nano lets my app talk to CouchDB. This is the glue holding the site together.
The one thing I found confusing at first, was the concept of using
<a href="https://github.com/dscape/nano#dbbulkdocs-params-callback">&lsquo;bulk&rsquo;</a> to update
and delete information.</li>
<li><a href="https://github.com/andris9/Nodemailer">nodemailer</a> - Emails<br>
Nodemailer lets me send emails through node. Simple enough, but I&rsquo;d like to
be able to send from arbitrary email addresses.</li>
<li><a href="http://underscorejs.org/">Underscore</a> - JS Utilities<br>
Underscore.js is becoming a default for any project I start using javascript.
When doing any sort of complex data manipulation, the utilities it provides
are a lifesaver.</li>
</ul>
<p>To make my life easier, I went with <a href="http://getbootstrap.com/">Bootstrap</a> for
the frontend.</p>
<p>If you want to check the project out (or checkout the project <small>(git
joke)</small>) <del>visit <a href="http://wwubrew.com/">http://wwubrew.com/</a></del>. The code&rsquo;s all
on <a href="https://github.com/apexskier/teaharmony">github</a>.</p>


  
  ]]></content:encoded></item><item><title>Quinault River</title><link>https://camlittle.com/posts/quinault/</link><pubDate>Fri, 30 Aug 2013 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/quinault/</guid><enclosure url="https://camlittle.com/posts/quinault/thumbnail.jpg" length="2809" type="image/jpeg"/><description>&lt;p>Earlier this month I was fortunate enough to journey out to the Quinault river,
on the &lt;a href="https://www.google.com/maps/preview#!q=Graves+Creek+Campground&amp;amp;data=!1m4!1m3!1d12674!2d-123.5797121!3d47.5723791!2m1!1e3!4m10!1m9!4m8!1m3!1d811145!2d-123.5807115!3d47.5706261!3m2!1i1029!2i904!4f13.1&amp;amp;fid=7">southwest
side&lt;/a>
of the Olympic National Park. The river is very inaccessable, but the canyon is
pretty enough to be well worth it.&lt;/p>
&lt;p>We left from the Graves Creek Campground around 10 am and started hiking
towards Pony Creek Falls. I was able to borrow a homemade kayak backpack rig,
which made the trip much easier by distributing the weight over my hips and
shoulders.&lt;/p></description><content:encoded><![CDATA[
  
<p>Earlier this month I was fortunate enough to journey out to the Quinault river,
on the <a href="https://www.google.com/maps/preview#!q=Graves+Creek+Campground&amp;data=!1m4!1m3!1d12674!2d-123.5797121!3d47.5723791!2m1!1e3!4m10!1m9!4m8!1m3!1d811145!2d-123.5807115!3d47.5706261!3m2!1i1029!2i904!4f13.1&amp;fid=7">southwest
side</a>
of the Olympic National Park. The river is very inaccessable, but the canyon is
pretty enough to be well worth it.</p>
<p>We left from the Graves Creek Campground around 10 am and started hiking
towards Pony Creek Falls. I was able to borrow a homemade kayak backpack rig,
which made the trip much easier by distributing the weight over my hips and
shoulders.</p>
<p>
<img src="quinault-1.jpeg?w=400" alt="Obligatory trailhead shot" />

</p>
<p>At about two miles, one hour, we made it to an old picnic table and started
following an elk trail Mike Hoover found that puts you just below the first set
of class V rapids. It&rsquo;s tricky to follow, but after nearly an hour we found the
correct gully leading down to the river and started lowering boats.</p>
<p>
<img src="IMG_0020.jpg?w=400" alt="Tom finishing the last rappel" />

</p>
<p>Eventually, at about one o&rsquo;clock, we were at the river. It&rsquo;s incredible.</p>
<p>Especially after the descent, the canyon feels remote. At times sheer rock
soars up close to 300 feet on either side. The water is clear enough to make
out fish below and rocks fifteen feet below. Waterfalls pour into the river and
the rocks have been polished and shaped by the water. This makes scouting a bit
scary, as it&rsquo;s difficult to get a grip when walking, and evidence of undercuts
are everywhere.</p>
<p>The river is not terribly technically difficult, but consequences could be bad
if an accident happened. I shudder to think about hiking out. The gauge read
660 cfs on this trip, but a good foot higher would have been perfect. We were
bumping rocks the entire time as it was, and a few rapids were too low for
comfort.</p>
<p>Our one scary incident was about a third of the way through, when Stephen
pinned his boat in an undercut slot. We were able to pull him out with a rope
before he fell in, but the boat took a good hour to work out. We ended up
pushing it deeper and letting it flush under the rock.</p>
<p>We made it out close to five o&rsquo;clock. The takeout was at our campsite just
after the Graves Creek confluence, so there&rsquo;s no hike out, a huge relief.</p>
<p>If you ever have the chance to do this run, take it. It&rsquo;s gorgeous, and with
the right water would be a fantastic stretch of whitewater.</p>
<p>Checkout the <a href="http://www.americanwhitewater.org/content/Report/detail/id/885045/">trip report on American
Whitewater</a>
and <a href="https://vimeo.com/72695828">Stephen&rsquo;s video on Vimeo</a>.</p>
<iframe src="https://camlittle.com//player.vimeo.com/video/72834549" width="500" height="281"
frameborder="0" webkitallowfullscreen mozallowfullscreen
allowfullscreen></iframe> <p><a href="http://vimeo.com/72834549">Quinault</a>
from <a href="http://vimeo.com/apexskier">Cameron Little</a> on <a
href="https://vimeo.com">Vimeo</a>.</p>
<p>
<img src="IMG_0022.JPG?w=400" alt="" />

</p>


  
  ]]></content:encoded></item><item><title>Raspberry Pi: Enclosure</title><link>https://camlittle.com/posts/pi-enclosure/</link><pubDate>Thu, 11 Apr 2013 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/pi-enclosure/</guid><enclosure url="https://camlittle.com/posts/pi-enclosure/thumbnail.jpg" length="2531" type="image/jpeg"/><description>&lt;p>I wanted to make my Raspberry Pi look good, be accessible, and be protected. I decided to house it in an old metal socket set box my dad gave me a while back. Besides the fact that the box was free and looks pretty awesome, I chose it because it has plenty of room. I&amp;rsquo;ve always wanted my Pi to be portable, so I purchased an &lt;a href="http://www.amazon.com/gp/product/B005NGKR54">Anker Astro3&lt;/a> external battery pack. I chose this model for a few reasons&amp;hellip;&lt;/p></description><content:encoded><![CDATA[
  
<p>I wanted to make my Raspberry Pi look good, be accessible, and be protected. I decided to house it in an old metal socket set box my dad gave me a while back. Besides the fact that the box was free and looks pretty awesome, I chose it because it has plenty of room. I&rsquo;ve always wanted my Pi to be portable, so I purchased an <a href="http://www.amazon.com/gp/product/B005NGKR54">Anker Astro3</a> external battery pack. I chose this model for a few reasons&hellip;</p>
<ol>
<li>It&rsquo;s under $50 (with a significant discount on Amazon).</li>
<li>The charge is extremely high - 10,000 mAh. I was able to get over 13 hours uptime running the Pi without a full charge on the battery.</li>
<li>It fits perfectly in my box. Most external batteries with comparable capacities are too big, and a smaller one would have moved around more than I like.</li>
<li>It can charge and distribute power simultaneously.</li>
</ol>
<p>I&rsquo;m also planning on using the battery for river trips and long car rides.</p>
<p>I used electrical tape to make a Raspberry Pi logo on the top of the box, then dremeled out holes for the ports (a good first use of the dremel).</p>
<p>I purchased some <a href="http://sugru.com">Sugru</a> to stabilize the pi inside the box, then decided to make some removable caps for the ports I don&rsquo;t often use with the excess. I also used Sugru to prop up the battery, and I can store extra OS&rsquo;s on SD cards under it.</p>
<figure><img src="https://camlittle.com/posts/pi-enclosure/1-open.jpg">
</figure>

<figure><img src="https://camlittle.com/posts/pi-enclosure/2-inside.jpg">
</figure>

<figure><img src="https://camlittle.com/posts/pi-enclosure/3-caps.jpg">
</figure>

<figure><img src="https://camlittle.com/posts/pi-enclosure/4-ports.jpg">
</figure>



  
  ]]></content:encoded></item><item><title>Raspberry Pi: Setup and first steps</title><link>https://camlittle.com/posts/raspberry-pi/</link><pubDate>Sun, 17 Mar 2013 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/raspberry-pi/</guid><enclosure url="https://camlittle.com/posts/raspberry-pi/thumbnail.jpg" length="3835" type="image/jpeg"/><description>&lt;p>I recently purchased a &lt;a href="http://www.raspberrypi.org">Raspberry Pi&lt;/a> to play
around with. It&amp;rsquo;s my first computer solely dedicated to running a linux
operating system (with root priveleges). I decided to install
&lt;a href="http://www.rasbian.org/">Rasbian&lt;/a>, the suggested OS (based on Debian), and
I&amp;rsquo;ve purchased an Edimax USB wifi dongle, as it&amp;rsquo;s supported without any extra
configuration. Setting it up was quite easy, as there are numerous tutorials
and instructions around the web.&lt;/p>
&lt;p>The first thing I decided to do was get some of my basic linux tools working. I
installed tmux and got my .bashrc, .bash_aliases, and .tmux.conf files set to
my tastes. In order to get wireless working, I had to use the GUI wireless
configuration, but it&amp;rsquo;s completely possible to do through the terminal (or over
ssh). I also set my router to assign a specific IP address to the Raspberry
Pi&amp;rsquo;s etherent and wireless connection based off their MAC addresses.&lt;/p></description><content:encoded><![CDATA[
  
<p>I recently purchased a <a href="http://www.raspberrypi.org">Raspberry Pi</a> to play
around with. It&rsquo;s my first computer solely dedicated to running a linux
operating system (with root priveleges). I decided to install
<a href="http://www.rasbian.org/">Rasbian</a>, the suggested OS (based on Debian), and
I&rsquo;ve purchased an Edimax USB wifi dongle, as it&rsquo;s supported without any extra
configuration. Setting it up was quite easy, as there are numerous tutorials
and instructions around the web.</p>
<p>The first thing I decided to do was get some of my basic linux tools working. I
installed tmux and got my .bashrc, .bash_aliases, and .tmux.conf files set to
my tastes. In order to get wireless working, I had to use the GUI wireless
configuration, but it&rsquo;s completely possible to do through the terminal (or over
ssh). I also set my router to assign a specific IP address to the Raspberry
Pi&rsquo;s etherent and wireless connection based off their MAC addresses.</p>
<p>I then decided to set up a web server as I&rsquo;d never done it from scratch. I used
the super useful <code>sudo apt-get </code> to install Apache, PHP, and MySQL, restarted,
and was set up. My router&rsquo;s config allows for redirection of specific ports to
certain internal IP addresses, to port 80 (web server) is now pointing to the
Raspbery Pi&rsquo;s IP.</p>
<p>I use <a href="http://www.namecheap.com/?aff=46677">namecheap</a> as my domain name
registrar, and a quick internet search led me to a guy who used python to
<a href="http://networkprogramming.wordpress.com/2013/02/15/servedwithpi-com-a-tiny-webserver/">dynamically change his DNS
settings</a>
to point to the Raspberry Pi. After testing his code and concept, I found that
it clears all DNS records before setting any, and for this reason, wouldn&rsquo;t
work for my purpose, as I wanted to access the site on my Pi at
<a href="http://sitkum.camlittle.com">sitkum.camlittle.com</a>. With a little
investigating and Python coding, I came up with a script that updates the
correct subdomain without removing the other DNS records. It also checks if the
record is already set correctly, and stops if so.</p>
<p><a href="https://camlittle.com/posts/raspberry-pi/update_ip.py
">Here&rsquo;s the code.</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-.py" data-lang=".py"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">urllib</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">xml.dom.minidom</span> <span class="k">as</span> <span class="nn">minidom</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="n">exit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span> <span class="s2">&#34;starting...&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># variables</span>
</span></span><span class="line"><span class="cl"><span class="n">SLD</span> <span class="o">=</span> <span class="s2">&#34;camlittle&#34;</span> <span class="c1"># domain name</span>
</span></span><span class="line"><span class="cl"><span class="n">TLD</span> <span class="o">=</span> <span class="s2">&#34;com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">apikey</span> <span class="o">=</span> <span class="s2">&#34;&lt;api key&gt;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">username</span> <span class="o">=</span> <span class="s2">&#34;&lt;username&gt;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># get the Pi&#39;s IP address</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="s2">&#34;http://dananos.brinkster.net/ip.php&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ip</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span> <span class="s2">&#34;got ip &#34;</span> <span class="o">+</span> <span class="n">ip</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># api command</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">=</span> <span class="s2">&#34;http://api.namecheap.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;/xml.response?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;apiuser=&#34;</span> <span class="o">+</span> <span class="n">username</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;&amp;apikey=&#34;</span> <span class="o">+</span> <span class="n">apikey</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;&amp;username=&#34;</span> <span class="o">+</span> <span class="n">username</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;&amp;ClientIp=&lt;this IP must be allowed via namecheap&#39;s api settings. I used the Pi&#39;s local ip&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;&amp;SLD=&#34;</span> <span class="o">+</span> <span class="n">SLD</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;&amp;TLD=&#34;</span> <span class="o">+</span> <span class="n">TLD</span>
</span></span><span class="line"><span class="cl"><span class="n">request_head</span> <span class="o">+=</span> <span class="s2">&#34;&amp;Command=namecheap.domains.dns.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># this submits the request and returns the XML response</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">request</span><span class="p">(</span><span class="n">command</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="n">request_head</span> <span class="o">+</span> <span class="n">command</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span> <span class="s2">&#34;calling &#34;</span> <span class="o">+</span> <span class="n">url</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># save the existing DNS information in these arrays</span>
</span></span><span class="line"><span class="cl"><span class="n">hostnames</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">recordtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">addresses</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">mxprefs</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">ttls</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># get existing DNS information</span>
</span></span><span class="line"><span class="cl"><span class="n">request_command</span> <span class="o">=</span> <span class="s2">&#34;getHosts&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span> <span class="s2">&#34;getting host records for &#34;</span> <span class="o">+</span> <span class="n">SLD</span> <span class="o">+</span> <span class="s2">&#34;.&#34;</span> <span class="o">+</span> <span class="n">TLD</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">xml</span> <span class="o">=</span> <span class="n">request</span><span class="p">(</span><span class="n">request_command</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">xml_dom</span> <span class="o">=</span> <span class="n">minidom</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">xml</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">hosts</span> <span class="o">=</span> <span class="n">xml_dom</span><span class="o">.</span><span class="n">documentElement</span><span class="o">.</span><span class="n">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;host&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># loop through and save</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">host</span> <span class="ow">in</span> <span class="n">hosts</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">attr</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">host</span><span class="o">.</span><span class="n">attributes</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">attr</span> <span class="o">==</span> <span class="s2">&#34;Name&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">hostnames</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">attr</span> <span class="o">==</span> <span class="s2">&#34;Type&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">recordtypes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">attr</span> <span class="o">==</span> <span class="s2">&#34;Address&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">addresses</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">attr</span> <span class="o">==</span> <span class="s2">&#34;MXPref&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">mxprefs</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">attr</span> <span class="o">==</span> <span class="s2">&#34;TTL&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">ttls</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># set DNS information</span>
</span></span><span class="line"><span class="cl"><span class="n">request_command</span> <span class="o">=</span> <span class="s2">&#34;setHosts&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">record</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">hostnames</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">    <span class="n">num</span> <span class="o">=</span> <span class="n">record</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">request_command</span> <span class="o">+=</span> <span class="s2">&#34;&amp;HostName&#34;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">num</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">+</span> <span class="n">hostnames</span><span class="p">[</span><span class="n">record</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">request_command</span> <span class="o">+=</span> <span class="s2">&#34;&amp;RecordType&#34;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">num</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">+</span> <span class="n">recordtypes</span><span class="p">[</span><span class="n">record</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">request_command</span> <span class="o">+=</span> <span class="s2">&#34;&amp;Address&#34;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">num</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># change IP address for the Pi</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">hostnames</span><span class="p">[</span><span class="n">record</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&#34;pi&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># if it&#39;s already correct, don&#39;t do anything</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">ip</span> <span class="o">==</span> <span class="n">addresses</span><span class="p">[</span><span class="n">record</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span> <span class="s2">&#34;dns record is set correctly&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">exit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">request_command</span> <span class="o">+=</span> <span class="n">ip</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">request_command</span> <span class="o">+=</span> <span class="n">addresses</span><span class="p">[</span><span class="n">record</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">request_command</span> <span class="o">+=</span> <span class="s2">&#34;&amp;MXPref&#34;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">num</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">+</span> <span class="n">mxprefs</span><span class="p">[</span><span class="n">record</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">request_command</span> <span class="o">+=</span> <span class="s2">&#34;&amp;TTL&#34;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">num</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">+</span> <span class="n">ttls</span><span class="p">[</span><span class="n">record</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">request</span><span class="p">(</span><span class="n">request_command</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span> <span class="s2">&#34;response is...</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="n">response</span>
</span></span></code></pre></div>


<p>It&rsquo;s worked so far, but as I haven&rsquo;t moved my Pi to a network with a different
IP address, I can&rsquo;t say for sure. I&rsquo;ll be testing more fully over Spring break.
I&rsquo;ve set up a cron job to run it every hour.</p>
<p>The Raspberry Pi is perfectly capable of hosting a
<a href="http://www.dingleberrypi.com">full</a> <a href="http://www.servedwithpi.com">site</a>, but,
for me, it&rsquo;s not worthwhile. A real web host provides much better security,
uptime, power, and bandwidth. Ultimately, I want to mess with my Pi without
having to worry about crashing my website.</p>
<p>The real advantage to this dynamic dns setup is ssh. I&rsquo;m now able to ssh in
without knowing the actual IP address of the Pi. It&rsquo;s not a perfect system yet,
as port 22 will not be forwarded correctly on other routers, but learning is
why I got the Pi. It really is an amazingly fun, affordable, way to get more
comforable with Linux and hacking in general.</p>


  
  ]]></content:encoded></item><item><title>CSS Lightsabers</title><link>https://camlittle.com/posts/css-lightsaber/</link><pubDate>Wed, 01 Feb 2012 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/css-lightsaber/</guid><enclosure url="https://camlittle.com/posts/css-lightsaber/thumbnail.jpg" length="1129" type="image/jpeg"/><description>&lt;p>I made some lightsabers with CSS and Javascript. You can &lt;a href="https://camlittle.com/lightsaber">play with them&lt;/a>. Space will open and close them and arrow keys will switch between them while deactivated.
They work on modern browsers, but motions are broken in Firefox. Sounds were lost when I recovered them from my old server, &lt;del>though if I get around to it I might try to reenable them&lt;/del>.&lt;/p></description><content:encoded><![CDATA[
  
<p>I made some lightsabers with CSS and Javascript. You can <a href="https://camlittle.com/lightsaber">play with them</a>. Space will open and close them and arrow keys will switch between them while deactivated.
They work on modern browsers, but motions are broken in Firefox. Sounds were lost when I recovered them from my old server, <del>though if I get around to it I might try to reenable them</del>.</p>


  
  ]]></content:encoded></item><item><title>GLGE Tutorial 2</title><link>https://camlittle.com/posts/glge-tut2/</link><pubDate>Wed, 14 Dec 2011 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/glge-tut2/</guid><enclosure url="https://camlittle.com/posts/glge-tut2/thumbnail.jpg" length="2315" type="image/jpeg"/><description>&lt;p>content:
&lt;a href="#elements-needed">elements needed&lt;/a> | &lt;a href="#meshes">meshes&lt;/a> | &lt;a href="#animation">animation&lt;/a> | &lt;a href="#the-rest-including-groups">the rest&lt;/a> | &lt;a href="#javascript">javascript&lt;/a> | &lt;a href="#here-it-is---update-glge-isnt-supported-any-more">demo/download&lt;/a>&lt;/p>
&lt;h2 id="3d-objects-with-movement">&lt;a class="heading-link" href="#3d-objects-with-movement">&lt;/a>3D objects with movement&lt;/h2>
&lt;p>If you haven&amp;rsquo;t already, go and read the &lt;a href="https://camlittle.com/posts/glge-tut1/">first tutorial&lt;/a> in
this series. This tutorial will expand on the XML file from the first tutorial.
The javascript and html are exactly the same.&lt;/p>
&lt;p>The XML file is the only one that will change. This example will include a
rotating cube and a sphere circling around it.&lt;/p>
&lt;h3 id="elements-needed">&lt;a class="heading-link" href="#elements-needed">&lt;/a>Elements needed&lt;/h3>
&lt;p>If you&amp;rsquo;ll remember, there are several things needed in our XML file. For this
one we will need&amp;hellip;&lt;/p></description><content:encoded><![CDATA[
  
<p>content:
<a href="#elements-needed">elements needed</a> | <a href="#meshes">meshes</a> | <a href="#animation">animation</a> | <a href="#the-rest-including-groups">the rest</a> | <a href="#javascript">javascript</a> | <a href="#here-it-is---update-glge-isnt-supported-any-more">demo/download</a></p>
<h2 id="3d-objects-with-movement"><a class="heading-link" href="#3d-objects-with-movement"></a>3D objects with movement</h2>
<p>If you haven&rsquo;t already, go and read the <a href="https://camlittle.com/posts/glge-tut1/">first tutorial</a> in
this series. This tutorial will expand on the XML file from the first tutorial.
The javascript and html are exactly the same.</p>
<p>The XML file is the only one that will change. This example will include a
rotating cube and a sphere circling around it.</p>
<h3 id="elements-needed"><a class="heading-link" href="#elements-needed"></a>Elements needed</h3>
<p>If you&rsquo;ll remember, there are several things needed in our XML file. For this
one we will need&hellip;</p>
<ul>
<li>Two <code>&lt;mesh&gt;</code>s composed of&hellip;
<ul>
<li><code>&lt;positions&gt;</code>,</li>
<li><code>&lt;normals&gt;</code>,</li>
<li><code>&lt;uv1&gt;</code> and</li>
<li><code>&lt;faces&gt;</code>,</li>
</ul>
</li>
<li>Two <code>&lt;material&gt;</code>s,</li>
<li>A <code>&lt;camera&gt;</code> and</li>
<li>A <code>&lt;scene&gt;</code> composed of&hellip;
<ul>
<li>Two <code>&lt;object&gt;</code>s</li>
<li>Two <code>&lt;light&gt;</code>s.</li>
</ul>
</li>
</ul>
<h3 id="meshes"><a class="heading-link" href="#meshes"></a>Meshes</h3>
<p>We need to define two meshes for two separate objects in the scene: a cube and
a sphere. I&rsquo;ll go into a little more detail on making them in this tutorial
compared to the last one.</p>
<h4 id="cube"><a class="heading-link" href="#cube"></a>Cube</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;mesh</span> <span class="na">id=</span><span class="s">&#34;cube&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/mesh&gt;</span>
</span></span></code></pre></div><p>The cube is composed of six sides. Each side is made of two flat triangles
(like the one from the last tutorial). This will make a total of 36 corners or
36 coordinates in the <code>&lt;positions&gt;</code> element of the <code>&lt;mesh&gt;</code>, 36 coordinates in
the <code>&lt;normals&gt;</code> element of the <code>&lt;mesh&gt;</code> and 36 numbers in the <code>&lt;faces&gt;</code> element
of the <code>&lt;mesh&gt;</code>.</p>
<p>I&rsquo;ll start off with the <code>&lt;positions&gt;</code> element. Each of the 36 corners will need
an x, y, z coordinate. Each face of the cube will have be made of two triangles
composed of 6 coordinates.</p>
<p>For the top face, a flat square, we will need&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">  1, 1, 1,   // right 1 unit, forward 1 unit, up 1 unit
</span></span><span class="line"><span class="cl"> -1, 1, 1,   // left  1 unit, forward 1 unit, up 1 unit
</span></span><span class="line"><span class="cl">  1,-1, 1,   // right 1 unit, back    1 unit, up 1 unit
</span></span><span class="line"><span class="cl"> -1, 1, 1,   // left  1 unit, forward 1 unit, up 1 unit
</span></span><span class="line"><span class="cl"> -1,-1, 1,   // left  1 unit, back    1 unit, up 1 unit
</span></span><span class="line"><span class="cl">  1,-1, 1... // right 1 unit, back    1 unit, up 1 unit
</span></span></code></pre></div><p>Now we have the <code>&lt;normals&gt;</code> for the top face. Each line is an x, y, z
coordinate and defines which direction the light is reflected towards. A good
rule of thumb (at least for cubes and flat faces) is that the normal for each
corner of one face is +1 at the axis that all corners on the face are equal at.
For the top face, each corner of the face is +1 at the z axis, so each normal
is 0, 0, 1. The light is reflected up (to the z axis) for each corner and the
space in between corners.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">0, 0, 1,
</span></span><span class="line"><span class="cl">0, 0, 1,
</span></span><span class="line"><span class="cl">0, 0, 1,
</span></span><span class="line"><span class="cl">0, 0, 1,
</span></span><span class="line"><span class="cl">0, 0, 1,
</span></span><span class="line"><span class="cl">0, 0, 1...
</span></span></code></pre></div><p>Since our example will not have images associated with objects and because all
of the corners are defined separately, we don&rsquo;t need a <code>&lt;uv1&gt;</code> section or a
<code>&lt;faces&gt;</code> section. See another tutorial to learn about defining multiple
corners with the same position element.</p>
<p>Lets build the rest of the <code>&lt;positions&gt;</code> and <code>&lt;normals&gt;</code> elements.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;positions&gt;</span>
</span></span><span class="line"><span class="cl">    /* back */
</span></span><span class="line"><span class="cl">     1,  1, 1,
</span></span><span class="line"><span class="cl">     1,  1, -1,
</span></span><span class="line"><span class="cl">    -1,  1, -1,
</span></span><span class="line"><span class="cl">     1,  1, 1,
</span></span><span class="line"><span class="cl">    -1,  1, -1,
</span></span><span class="line"><span class="cl">    -1,  1, 1,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* left */
</span></span><span class="line"><span class="cl">    -1, -1, -1,
</span></span><span class="line"><span class="cl">    -1, -1, 1,
</span></span><span class="line"><span class="cl">    -1,  1, 1,
</span></span><span class="line"><span class="cl">    -1, -1, -1,
</span></span><span class="line"><span class="cl">    -1,  1, 1,
</span></span><span class="line"><span class="cl">    -1,  1, -1,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* front */
</span></span><span class="line"><span class="cl">     1, -1, -1,
</span></span><span class="line"><span class="cl">     1, -1, 1,
</span></span><span class="line"><span class="cl">    -1, -1, -1,
</span></span><span class="line"><span class="cl">     1, -1, 1,
</span></span><span class="line"><span class="cl">    -1, -1, 1,
</span></span><span class="line"><span class="cl">    -1, -1, -1,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* right */
</span></span><span class="line"><span class="cl">     1,  1, -1,
</span></span><span class="line"><span class="cl">     1,  1, 1,
</span></span><span class="line"><span class="cl">     1, -1, -1,
</span></span><span class="line"><span class="cl">     1,  1, 1,
</span></span><span class="line"><span class="cl">     1, -1, 1,
</span></span><span class="line"><span class="cl">     1, -1, -1,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* top */
</span></span><span class="line"><span class="cl">     1,  1, 1,
</span></span><span class="line"><span class="cl">    -1,  1, 1,
</span></span><span class="line"><span class="cl">     1, -1, 1,
</span></span><span class="line"><span class="cl">    -1,  1, 1,
</span></span><span class="line"><span class="cl">    -1, -1, 1,
</span></span><span class="line"><span class="cl">     1, -1, 1,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* bottom */
</span></span><span class="line"><span class="cl">     1,  1, -1,
</span></span><span class="line"><span class="cl">     1, -1, -1,
</span></span><span class="line"><span class="cl">    -1, -1, -1,
</span></span><span class="line"><span class="cl">     1,  1, -1,
</span></span><span class="line"><span class="cl">    -1, -1, -1,
</span></span><span class="line"><span class="cl">    -1,  1, -1
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/positions&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;normals&gt;</span>
</span></span><span class="line"><span class="cl">    /* back */
</span></span><span class="line"><span class="cl">    0, 1, 0,
</span></span><span class="line"><span class="cl">    0, 1, 0,
</span></span><span class="line"><span class="cl">    0, 1, 0,
</span></span><span class="line"><span class="cl">    0, 1, 0,
</span></span><span class="line"><span class="cl">    0, 1, 0,
</span></span><span class="line"><span class="cl">    0, 1, 0,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* left */
</span></span><span class="line"><span class="cl">    -1, 0, 0,
</span></span><span class="line"><span class="cl">    -1, 0, 0,
</span></span><span class="line"><span class="cl">    -1, 0, 0,
</span></span><span class="line"><span class="cl">    -1, 0, 0,
</span></span><span class="line"><span class="cl">    -1, 0, 0,
</span></span><span class="line"><span class="cl">    -1, 0, 0,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* front */
</span></span><span class="line"><span class="cl">    0, -1, 0,
</span></span><span class="line"><span class="cl">    0, -1, 0,
</span></span><span class="line"><span class="cl">    0, -1, 0,
</span></span><span class="line"><span class="cl">    0, -1, 0,
</span></span><span class="line"><span class="cl">    0, -1, 0,
</span></span><span class="line"><span class="cl">    0, -1, 0,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* right */
</span></span><span class="line"><span class="cl">    1, 0, 0,
</span></span><span class="line"><span class="cl">    1, 0, 0,
</span></span><span class="line"><span class="cl">    1, 0, 0,
</span></span><span class="line"><span class="cl">    1, 0, 0,
</span></span><span class="line"><span class="cl">    1, 0, 0,
</span></span><span class="line"><span class="cl">    1, 0, 0,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* top */
</span></span><span class="line"><span class="cl">    0, 0, 1,
</span></span><span class="line"><span class="cl">    0, 0, 1,
</span></span><span class="line"><span class="cl">    0, 0, 1,
</span></span><span class="line"><span class="cl">    0, 0, 1,
</span></span><span class="line"><span class="cl">    0, 0, 1,
</span></span><span class="line"><span class="cl">    0, 0, 1,
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    /* bottom */
</span></span><span class="line"><span class="cl">    0, 0,-1,
</span></span><span class="line"><span class="cl">    0, 0,-1,
</span></span><span class="line"><span class="cl">    0, 0,-1,
</span></span><span class="line"><span class="cl">    0, 0,-1,
</span></span><span class="line"><span class="cl">    0, 0,-1,
</span></span><span class="line"><span class="cl">    0, 0,-1
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/normals&gt;</span>
</span></span></code></pre></div><p>*don&rsquo;t include comments in your XML file, they&rsquo;re just here to help clear things up.</p>
<p>There&rsquo;s the mesh for the cube!</p>
<p>Now we can define the material for the cube. This is pretty easy and no
different than the last tutorial, an <code>id</code>, the <code>specular</code> value, and a <code>color</code>
(this time, blue).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;material</span> <span class="na">id=</span><span class="s">&#34;cubeMat&#34;</span> <span class="na">specular=</span><span class="s">&#34;1&#34;</span> <span class="na">color=</span><span class="s">&#34;#0088bb&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><h4 id="sphere"><a class="heading-link" href="#sphere"></a>Sphere</h4>
<p>Now we will make the sphere. GLGE doesn&rsquo;t have a very good way of dealing with
spheres, so it has to be defined in the same way as the cube. The sphere in
this example uses 1260 corners. I won&rsquo;t walk you through that, because I
haven&rsquo;t made one manually myself, I&rsquo;ve just copied the code from other
examples. You can <a href="https://camlittle.com/posts/glge-tut2/demo_xml.xml
"
target="_blank">click here</a> to see the code I&rsquo;m using if you really want to.</p>
<p>For the sphere material, I&rsquo;m going to make it red, and a bit less shiny.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;material</span> <span class="na">id=</span><span class="s">&#34;sphereMat&#34;</span> <span class="na">specular=</span><span class="s">&#34;0.5&#34;</span> <span class="na">color=</span><span class="s">&#34;#cc0000&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><h3 id="animation"><a class="heading-link" href="#animation"></a>Animation</h3>
<p>There are two ways to animate objects in GLGE. Animation can be defined in the javascript or the XML file. Usually animations are defined in the XML file and manipulated in the javascript file. For example, there could be an animation for a character walking and an animation for a character jumping (both in the XML file) and they could be switched via javascript. In this example, I&rsquo;m only using the XML file.</p>
<h4 id="cube-spin"><a class="heading-link" href="#cube-spin"></a>Cube spin</h4>
<p>I want the cube to rotate on the y and z axises.</p>
<p>First, I define this animation in an <code>&lt;animation_vector&gt;</code>. An <code>&lt;animation_vector&gt;</code> contains and id and the number of frames. These frames contribute to the speed of the animation (more on this later) and determine how we set keyframes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;animation_vector</span> <span class="na">id=</span><span class="s">&#34;spin&#34;</span> <span class="na">frames=</span><span class="s">&#34;240&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/animation_vector&gt;</span>
</span></span></code></pre></div><p>Inside the <code>&lt;animation_vector&gt;</code> we can set the attribute changes that will occur. To do this we add an <code>&lt;animation_curve&gt;</code> for each changing attribute.</p>
<p>So, we first add an <code>&lt;animation_curve&gt;</code> that changes the rotation around the y axis.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotY&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/animation_curve&gt;</span>
</span></span></code></pre></div><p>Inside this, we add the keyframes by adding <code>&lt;linear_point&gt;</code>s. Each has a <code>x</code> value that sets what frame it happens on, and a <code>y</code> value that sets the attribute value on that frame. So, in this example, at 0 frames in the animation (remember, the animation lasts 240 frames) the rotation on the y value is 0 radians, then it increases to 6.282 radians (about 360 degrees) on the 240th frame.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;240&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><p>I also want it to rotate around the z axis, so we&rsquo;ll add another <code>&lt;animation_curve&gt;</code> inside the <code>&lt;animation_vector&gt;</code>. It&rsquo;s almost exaclty the same, only the <code>&lt;animation_curve&gt;</code>&rsquo;s channel is set to the z axis.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotZ&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;240&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/animation_curve&gt;</span>
</span></span></code></pre></div><p>So here&rsquo;s the final code for the sphere&rsquo;s animation.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;animation_vector</span> <span class="na">id=</span><span class="s">&#34;spin&#34;</span> <span class="na">frames=</span><span class="s">&#34;240&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotY&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;240&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/animation_curve&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotZ&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;240&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/animation_curve&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/animation_vector&gt;</span>
</span></span></code></pre></div><h4 id="sphere-circling"><a class="heading-link" href="#sphere-circling"></a>Sphere circling</h4>
<p>I want the sphere to circle around the cube (or circling around the z axis. I could set an x and y location attribute for each frame, but there&rsquo;s a much more efficient way to do this.</p>
<p>What we really want to do is move the sphere out 3 units, then rotate the whole system it&rsquo;s on. Imagine holding a broom by the handle and swinging it around in a circle. The sphere is the broom&rsquo;s end, and it is rotating around you, the z axis.</p>
<p>To do this, we need to introduce a new element into the XML file, a <code>&lt;group&gt;</code>. Then we can apply the animation to the group. I&rsquo;ll go over the animation quickly, since it is very simple, then move onto the <code>&lt;object&gt;</code>s and <code>&lt;group&gt;</code>s. The only difference in this animation is that it has a total of 480 frames, rather than 240. This will make it take two times longer than the cube&rsquo;s animation.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;animation_vector</span> <span class="na">id=</span><span class="s">&#34;circle&#34;</span> <span class="na">frames=</span><span class="s">&#34;480&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotZ&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;480&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/animation_curve&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/animation_vector&gt;</span>
</span></span></code></pre></div><h3 id="the-rest-including-groups"><a class="heading-link" href="#the-rest-including-groups"></a>The rest (including groups)</h3>
<p>Next we need to add a camera (see the <a href="https://camlittle.com/posts/glge-tut1/">previous tutorial</a> for more info).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;camera</span> <span class="na">id=</span><span class="s">&#34;mainCamera&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><p>and make a scene, with a light in it (again, see the <a href="https://camlittle.com/posts/glge-tut1/">previous tutorial</a> for more info).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;scene</span> <span class="na">id=</span><span class="s">&#34;mainScene&#34;</span> <span class="na">camera=</span><span class="s">&#34;#mainCamera&#34;</span> <span class="na">ambient_color=</span><span class="s">&#34;#fff&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;light</span> <span class="na">id=</span><span class="s">&#34;mainlight&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;10&#34;</span> <span class="na">loc_y=</span><span class="s">&#34;15&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;10&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;-1.3&#34;</span> <span class="na">color=</span><span class="s">&#34;#fff&#34;</span> <span class="na">type=</span><span class="s">&#34;L_POINT&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/scene&gt;</span>
</span></span></code></pre></div><p>Now we&rsquo;ll add the cube. It has an <code>id</code>, references the mesh set up earlier, the material set up earlier, has some location and rotation attributes, and some new attributes. The first new attribute is the frame rate. This sets how many frames per second is applied to the animation. So, a frame rate of 60 means that an animation with a length of 240 will take (240 / 60 = 4) 4 seconds to complete and an animation with a length 480 will take 8 seconds to complete.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;object</span> <span class="na">id=</span><span class="s">&#34;cube&#34;</span> <span class="na">mesh=</span><span class="s">&#34;#cube&#34;</span> <span class="na">material=</span><span class="s">&#34;#cubeMat&#34;</span> <span class="na">frame_rate=</span><span class="s">&#34;60&#34;</span> <span class="na">animation=</span><span class="s">&#34;#spin&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;-10&#34;</span> <span class="na">rot_y=</span><span class="s">&#34;1&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;1&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><h4 id="groups"><a class="heading-link" href="#groups"></a>Groups</h4>
<p>Well, one group.</p>
<p>I want to do two things separately to the sphere, so I need to surround it with a group (sort of like adding a container element for extra css).</p>
<p>The group is moved down 10 units (same level as the cube) with a location attribute, and has the <code>#circle</code> animation applied to it. Inside the group is the sphere object moved out 3. It also has, the mesh defined above, material defined above, and scale attributes that make it 80% of the original size.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;group</span> <span class="na">id=</span><span class="s">&#34;sphereGroup&#34;</span> <span class="na">animation=</span><span class="s">&#34;#circle&#34;</span> <span class="na">frame_rate=</span><span class="s">&#34;60&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;-10&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;object</span> <span class="na">id=</span><span class="s">&#34;sphere&#34;</span> <span class="na">mesh=</span><span class="s">&#34;#sphere&#34;</span> <span class="na">material=</span><span class="s">&#34;#sphereMat&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;3&#34;</span> <span class="na">scale_x=</span><span class="s">&#34;0.8&#34;</span> <span class="na">scale_y=</span><span class="s">&#34;0.8&#34;</span> <span class="na">scale_z=</span><span class="s">&#34;0.8&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/group&gt;</span>
</span></span></code></pre></div><p>Woohoo! We&rsquo;re done with the XML file for this tutorial. Here&rsquo;s the whole thing.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="cp">&lt;?xml version=&#34;1.0&#34;?&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;glge&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;mesh</span> <span class="na">id=</span><span class="s">&#34;cube&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        /* cube mesh code */
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/mesh&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;mesh</span> <span class="na">id=</span><span class="s">&#34;sphere&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        /* sphere mesh code */
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/mesh&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;material</span> <span class="na">id=</span><span class="s">&#34;cubeMat&#34;</span> <span class="na">specular=</span><span class="s">&#34;1&#34;</span> <span class="na">color=</span><span class="s">&#34;#0088bb&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;material</span> <span class="na">id=</span><span class="s">&#34;sphereMat&#34;</span> <span class="na">specular=</span><span class="s">&#34;0.5&#34;</span> <span class="na">color=</span><span class="s">&#34;#cc0000&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;animation_vector</span> <span class="na">id=</span><span class="s">&#34;spin&#34;</span> <span class="na">frames=</span><span class="s">&#34;240&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotY&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;240&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/animation_curve&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotZ&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;240&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/animation_curve&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/animation_vector&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;animation_vector</span> <span class="na">id=</span><span class="s">&#34;circle&#34;</span> <span class="na">frames=</span><span class="s">&#34;480&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;animation_curve</span> <span class="na">channel=</span><span class="s">&#34;RotZ&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;0&#34;</span> <span class="na">y=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;linear_point</span> <span class="na">x=</span><span class="s">&#34;480&#34;</span> <span class="na">y=</span><span class="s">&#34;6.282&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/animation_curve&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/animation_vector&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;camera</span> <span class="na">id=</span><span class="s">&#34;mainCamera&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;scene</span> <span class="na">id=</span><span class="s">&#34;mainScene&#34;</span> <span class="na">camera=</span><span class="s">&#34;#mainCamera&#34;</span> <span class="na">ambient_color=</span><span class="s">&#34;#fff&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;group</span> <span class="na">id=</span><span class="s">&#34;sphereGroup&#34;</span> <span class="na">animation=</span><span class="s">&#34;#circle&#34;</span> <span class="na">frame_rate=</span><span class="s">&#34;60&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;-10&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;object</span> <span class="na">id=</span><span class="s">&#34;sphere&#34;</span> <span class="na">mesh=</span><span class="s">&#34;#sphere&#34;</span> <span class="na">material=</span><span class="s">&#34;#sphereMat&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;3&#34;</span> <span class="na">scale_x=</span><span class="s">&#34;0.8&#34;</span> <span class="na">scale_y=</span><span class="s">&#34;0.8&#34;</span> <span class="na">scale_z=</span><span class="s">&#34;0.8&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/group&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;object</span> <span class="na">id=</span><span class="s">&#34;cube&#34;</span> <span class="na">mesh=</span><span class="s">&#34;#cube&#34;</span> <span class="na">material=</span><span class="s">&#34;#cubeMat&#34;</span> <span class="na">frame_rate=</span><span class="s">&#34;60&#34;</span> <span class="na">animation=</span><span class="s">&#34;#spin&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;-10&#34;</span> <span class="na">rot_y=</span><span class="s">&#34;1&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;1&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;light</span> <span class="na">id=</span><span class="s">&#34;mainlight&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;10&#34;</span> <span class="na">loc_y=</span><span class="s">&#34;15&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;10&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;-1.3&#34;</span> <span class="na">color=</span><span class="s">&#34;#fff&#34;</span> <span class="na">type=</span><span class="s">&#34;L_POINT&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/scene&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/glge&gt;</span>
</span></span></code></pre></div><h3 id="javascript"><a class="heading-link" href="#javascript"></a>Javascript</h3>
<p>Last thing to do to make the animation work is to modify our javascript file a bit. If you use the javascript from the previous tutorial, it will render one frame of the animation and no more than that. Let&rsquo;s look at the javascript for a static scene first.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">canvasElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;canvas&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">doc</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Document</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="s2">&#34;tutorial2.xml&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">onLoad</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">renderer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Renderer</span><span class="p">(</span><span class="nx">canvasElement</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">scene</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Scene</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">scene</span> <span class="o">=</span> <span class="nx">doc</span><span class="p">.</span><span class="nx">getElement</span><span class="p">(</span><span class="s2">&#34;mainScene&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">renderer</span><span class="p">.</span><span class="nx">setScene</span><span class="p">(</span><span class="nx">scene</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">renderer</span><span class="p">.</span><span class="nx">render</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The line we need to look at is here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">renderer</span><span class="p">.</span><span class="nx">render</span><span class="p">();</span>
</span></span></code></pre></div><p>This renders the scene once.</p>
<p>We need to set it to render over and over again with a <code>setInterval()</code> function. GLGE has logic that slows the animation down, so you can set it to render every 1 millisecond.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">setInterval</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
</span></span><span class="line"><span class="cl">    <span class="nx">renderer</span><span class="p">.</span><span class="nx">render</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="mi">1</span><span class="p">);</span>
</span></span></code></pre></div><p>Here is the complete javascript code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">canvasElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;canvas&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">doc</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Document</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="s2">&#34;tutorial2.xml&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">onLoad</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">renderer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Renderer</span><span class="p">(</span><span class="nx">canvasElement</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">scene</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Scene</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">scene</span> <span class="o">=</span> <span class="nx">doc</span><span class="p">.</span><span class="nx">getElement</span><span class="p">(</span><span class="s2">&#34;mainScene&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">renderer</span><span class="p">.</span><span class="nx">setScene</span><span class="p">(</span><span class="nx">scene</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">setInterval</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
</span></span><span class="line"><span class="cl">        <span class="nx">renderer</span><span class="p">.</span><span class="nx">render</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="here-it-is---update-glge-isnt-supported-any-more"><a class="heading-link" href="#here-it-is---update-glge-isnt-supported-any-more"></a><del>Here it is</del>&mdash;update, glge isn&rsquo;t supported any more</h3>
<p><canvas id="flatTriangle" width="400" height="300"></canvas></p>
<!--
<script src="http://example.com/glge-compiled-min.js"></script>

<script type="text/javascript">
var canvasElement = document.getElementById('flatTriangle');
var doc = new GLGE.Document();

doc.load("/posts/glge-tut2/demo_xml.xml
");

doc.onLoad = function() {
	var renderer = new GLGE.Renderer(canvasElement);
	var scene = new GLGE.Scene();
	scene = doc.getElement("mainScene");
	renderer.setScene(scene);

	setInterval(function(){
		renderer.render();
	}, 1);
}
</script>
-->


  
  ]]></content:encoded></item><item><title>GLGE Tutorial 1</title><link>https://camlittle.com/posts/glge-tut1/</link><pubDate>Thu, 01 Dec 2011 00:00:00 -0700</pubDate><guid isPermaLink="true">https://camlittle.com/posts/glge-tut1/</guid><enclosure url="https://camlittle.com/posts/glge-tut1/thumbnail.jpg" length="652" type="image/jpeg"/><description>&lt;p>content:
&lt;a href="#the-html-file">the HTML file&lt;/a> | &lt;a href="#the-xml-file">the XML file&lt;/a> | &lt;a href="#the-javascript">the javascript&lt;/a> | &lt;a href="#here-it-is----update-glge-isnt-supported-any-more">demo/download&lt;/a>&lt;/p>
&lt;h2 id="a-basic-scene">&lt;a class="heading-link" href="#a-basic-scene">&lt;/a>A basic scene&lt;/h2>
&lt;p>Most GLGE scenes need three things: an HTML file, some javascript, and an XML file. You&amp;rsquo;ll also need to &lt;a href="http://www.glge.org/downloads">download GLGE&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;m going to walk through setting up a simple scene with a flat triangle and a lighting source.&lt;/p>
&lt;p>Lets start with the HTML file.&lt;/p>
&lt;h3 id="the-html-file">&lt;a class="heading-link" href="#the-html-file">&lt;/a>The HTML file&lt;/h3>
&lt;p>The HTML file does what you&amp;rsquo;d expect by setting up the basic structure of the web page. If you&amp;rsquo;re reading this tutorial you should have a solid grasp of HTML. Inside the &lt;code>body&lt;/code> there is a &lt;code>canvas&lt;/code> element with a width, height, and id, a &lt;code>script&lt;/code> referencing GLGE and a place for our javascript.&lt;/p></description><content:encoded><![CDATA[
  
<p>content:
<a href="#the-html-file">the HTML file</a> | <a href="#the-xml-file">the XML file</a> | <a href="#the-javascript">the javascript</a> | <a href="#here-it-is----update-glge-isnt-supported-any-more">demo/download</a></p>
<h2 id="a-basic-scene"><a class="heading-link" href="#a-basic-scene"></a>A basic scene</h2>
<p>Most GLGE scenes need three things: an HTML file, some javascript, and an XML file. You&rsquo;ll also need to <a href="http://www.glge.org/downloads">download GLGE</a>.</p>
<p>I&rsquo;m going to walk through setting up a simple scene with a flat triangle and a lighting source.</p>
<p>Lets start with the HTML file.</p>
<h3 id="the-html-file"><a class="heading-link" href="#the-html-file"></a>The HTML file</h3>
<p>The HTML file does what you&rsquo;d expect by setting up the basic structure of the web page. If you&rsquo;re reading this tutorial you should have a solid grasp of HTML. Inside the <code>body</code> there is a <code>canvas</code> element with a width, height, and id, a <code>script</code> referencing GLGE and a place for our javascript.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>GLGE Demo<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">canvas</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;canvas&#34;</span> <span class="na">width</span><span class="o">=</span><span class="s">&#34;400&#34;</span> <span class="na">height</span><span class="o">=</span><span class="s">&#34;300&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">canvas</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;/assets/js/lib/glge-compiled-min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/javascript&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="c">&lt;!--</span> <span class="nx">javascript</span> <span class="nx">goes</span> <span class="nx">here</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>And that&rsquo;s it.</p>
<h3 id="the-xml-file"><a class="heading-link" href="#the-xml-file"></a>The XML file</h3>
<p>The XML file defines the scene, lighting, cameras, and any objects. The very outside structure is this&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="cp">&lt;?xml version=&#34;1.0&#34;?&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;glge&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/glge&gt;</span>
</span></span></code></pre></div><p>Above is a bare GLGE document, which needs, at minimum, the following&hellip;</p>
<ul>
<li>At least one <code>&lt;mesh&gt;</code> composed of&hellip;
<ul>
<li><code>&lt;positions&gt;</code>,</li>
<li><code>&lt;normals&gt;</code>,</li>
<li><code>&lt;uv1&gt;</code> and</li>
<li><code>&lt;faces&gt;</code>,</li>
</ul>
</li>
<li>At least one <code>&lt;material&gt;</code>,</li>
<li>A <code>&lt;camera&gt;</code> and</li>
<li>A <code>&lt;scene&gt;</code> composed of&hellip;
<ul>
<li>At least one <code>&lt;object&gt;</code></li>
<li>Optional <code>&lt;light&gt;</code>s.</li>
</ul>
</li>
</ul>
<h4 id="mesh"><a class="heading-link" href="#mesh"></a>Mesh</h4>
<p>A <code>&lt;mesh&gt;</code> is what defines an object&rsquo;s shape. Meshes can be generated by 3rd party software, or can be created by hand, as shown in this example. The simplest object defined by a mesh is a flat triangle, the building block for any object. The <code>&lt;mesh&gt;</code> behind a flat triangle is very simple.</p>
<p>It has four components, <code>&lt;positions&gt;</code>, <code>&lt;normals&gt;</code>, <code>&lt;uv1&gt;</code>, and <code>&lt;faces&gt;</code>. <code>&lt;positions&gt;</code> define corners in 3D space. In the syntax I prefer to use, each line defines x, y and z coordinates of one corner. The <code>&lt;normals&gt;</code> define the perpendicular direction from the surface, which plays into how lighting is computed. In the syntax I use, each line defines x, y and z coordinates of one normal of a corner. The <code>&lt;uv1&gt;</code> defines how an (image) texture is applied to the object. The triangle we&rsquo;re creating is a solid color, so a <code>&lt;uv1&gt;</code> is not strictly necessary. In my syntax, each line is an x and y coordinate on the flat triangle. The last part, the <code>&lt;faces&gt;</code>, define how each point is used. The renderer draws the 0th point first (position -1, 1, 0; normal 0, 0, 1; and uv1 0, 0) then moves to the 1st and so on.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;mesh</span> <span class="na">id=</span><span class="s">&#34;triangle&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;positions&gt;</span>
</span></span><span class="line"><span class="cl">        -1,  1, 0,
</span></span><span class="line"><span class="cl">        -1, -1, 0,
</span></span><span class="line"><span class="cl">         1, -1, 0
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/positions&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;normals&gt;</span>
</span></span><span class="line"><span class="cl">        0, 0, 1,
</span></span><span class="line"><span class="cl">        0, 0, 1,
</span></span><span class="line"><span class="cl">        0, 0, 1
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/normals&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;uv1&gt;</span>
</span></span><span class="line"><span class="cl">        0, 0,
</span></span><span class="line"><span class="cl">        1, 0,
</span></span><span class="line"><span class="cl">        1, 1
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/uv1&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;faces&gt;</span>
</span></span><span class="line"><span class="cl">        0, 1, 2
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/faces&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/mesh&gt;</span>
</span></span></code></pre></div><h4 id="material"><a class="heading-link" href="#material"></a>Material</h4>
<p>If the XML document only contained a mesh, we still wouldn&rsquo;t see anything. It&rsquo;s just the definition of an object, not the object itself. The next thing needed is a <code>&lt;material&gt;</code>. A <code>&lt;material&gt;</code> defines an image or color to apply to the object and how lighting affects it.</p>
<p>The simple <code>&lt;material&gt;</code> defined in my example has three parts. An <code>id</code> to identify the element, a specular shine value (think reflection), and a color. The specular value is a value from 0 to 1. The color can be a hexadecimal or rgb value.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;material</span> <span class="na">id=</span><span class="s">&#34;triangleMat&#34;</span> <span class="na">specular=</span><span class="s">&#34;1&#34;</span> <span class="na">color=</span><span class="s">&#34;#aaa&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><h4 id="camera"><a class="heading-link" href="#camera"></a>Camera</h4>
<p>Now we have the shape of an object and what it will look like defined. Next we need to create a <code>&lt;camera&gt;</code> to see it from.</p>
<p>The <code>&lt;camera&gt;</code> is composed of an <code>id</code> and the values for it&rsquo;s location and angle. The location is defined by the attributes <code>loc_x</code>, <code>loc_y</code>, and <code>loc_z</code>. The rotation is defined by the attributes <code>rot_x</code>, <code>rot_y</code>, and <code>rot_z</code> in units of radians. In my example, I don&rsquo;t need location or rotation attributes because they are at the origin, 0, the default. A default camera is at point 0, 0, 0 and is looking straight down.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;camera</span> <span class="na">id=</span><span class="s">&#34;mainCamera&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;0&#34;</span> <span class="na">loc_y=</span><span class="s">&#34;0&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;0&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;0&#34;</span> <span class="na">rot_y=</span><span class="s">&#34;0&#34;</span> <span class="na">rot_z=</span><span class="s">&#34;0&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">/* same as */
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;camera</span> <span class="na">id=</span><span class="s">&#34;mainCamera&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><h4 id="scene"><a class="heading-link" href="#scene"></a>Scene</h4>
<p>Now we have an object and our frame of viewing reference. Next we can start setting up a scene, comprised of an <code>id</code>, a reference to the <code>&lt;camera&gt;</code> we set up and ambient lighting, defined by a color.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;scene</span> <span class="na">id=</span><span class="s">&#34;mainScene&#34;</span> <span class="na">camera=</span><span class="s">&#34;#mainCamera&#34;</span> <span class="na">ambient_color=</span><span class="s">&#34;#fff&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/scene&gt;</span>
</span></span></code></pre></div><p>Inside our scene we can add <code>&lt;object&gt;</code>s and <code>&lt;light&gt;</code>s. My simple example has one <code>&lt;object&gt;</code> and one <code>&lt;light&gt;</code>, but the <code>&lt;light&gt;</code>&rsquo;s is not necessary to see the object.</p>
<h4 id="object"><a class="heading-link" href="#object"></a>Object</h4>
<p>The <code>&lt;object&gt;</code> has an <code>id</code>, a reference to the <code>&lt;mesh&gt;</code> set up earlier, location attributes (the same syntax as for the camera), rotation attributes, and scale attributes (<code>scale_x</code>, <code>scale_y</code> and <code>scale_z</code>). Again, the location and rotation attributes default to 0. The scale attributes default to 1. In my example, I&rsquo;ve moved my triangle down (z axis) by 4 units so it&rsquo;s not at the same location as the <code>&lt;camera&gt;</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;object</span> <span class="na">id=</span><span class="s">&#34;triangle&#34;</span> <span class="na">mesh=</span><span class="s">&#34;#triangle&#34;</span> <span class="na">material=</span><span class="s">&#34;#triangleMat&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;0&#34;</span> <span class="na">loc_y=</span><span class="s">&#34;0&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;-4&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;0&#34;</span> <span class="na">rot_y=</span><span class="s">&#34;0&#34;</span> <span class="na">rot_z=</span><span class="s">&#34;0&#34;</span> <span class="na">scale_x=</span><span class="s">&#34;1&#34;</span> <span class="na">scale_y=</span><span class="s">&#34;1&#34;</span> <span class="na">scale_z=</span><span class="s">&#34;1&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">/* same as */
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;object</span> <span class="na">id=</span><span class="s">&#34;triangle&#34;</span> <span class="na">mesh=</span><span class="s">&#34;#triangle&#34;</span> <span class="na">material=</span><span class="s">&#34;#triangleMat&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;-4&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><h4 id="light"><a class="heading-link" href="#light"></a>Light</h4>
<p>Now we have a functioning scene that will work if we were to render it! But&hellip; I want to make it look a bit more three dimentional (we are using WebGL). So I&rsquo;ll add a <code>&lt;light&gt;</code>. The light has an <code>id</code>, location, rotation, a <code>color</code>, and a <code>type</code>. Location and rotation are the same as <code>&lt;object&gt;</code>s and <code>&lt;camera&gt;</code>s. The <code>type</code> that I&rsquo;m using is <code>L_POINT</code> for point lighting. A point light has a location and faces a certain direction.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;light</span> <span class="na">id=</span><span class="s">&#34;mainlight&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;10&#34;</span> <span class="na">loc_y=</span><span class="s">&#34;15&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;10&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;-1.3&#34;</span> <span class="na">color=</span><span class="s">&#34;#fff&#34;</span> <span class="na">type=</span><span class="s">&#34;L_POINT&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></div><h4 id="done"><a class="heading-link" href="#done"></a>Done!</h4>
<p>So, that&rsquo;s it for this basic GLGE XML document. Here&rsquo;s it is in full.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="cp">&lt;?xml version=&#34;1.0&#34;?&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;glge&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;mesh</span> <span class="na">id=</span><span class="s">&#34;triangle&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;positions&gt;</span>
</span></span><span class="line"><span class="cl">            -1,  1, 0,
</span></span><span class="line"><span class="cl">            -1, -1, 0,
</span></span><span class="line"><span class="cl">             1, -1, 0
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/positions&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;normals&gt;</span>
</span></span><span class="line"><span class="cl">            0, 0, 1,
</span></span><span class="line"><span class="cl">            0, 0, 1,
</span></span><span class="line"><span class="cl">            0, 0, 1
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/normals&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;uv1&gt;</span>
</span></span><span class="line"><span class="cl">            0, 0,
</span></span><span class="line"><span class="cl">            1, 0,
</span></span><span class="line"><span class="cl">            1, 1
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/uv1&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;faces&gt;</span>
</span></span><span class="line"><span class="cl">            0, 1, 2
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/faces&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/mesh&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;material</span> <span class="na">id=</span><span class="s">&#34;triangleMat&#34;</span> <span class="na">specular=</span><span class="s">&#34;1&#34;</span> <span class="na">color=</span><span class="s">&#34;#aaa&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;camera</span> <span class="na">id=</span><span class="s">&#34;mainCamera&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;scene</span> <span class="na">id=</span><span class="s">&#34;mainScene&#34;</span> <span class="na">camera=</span><span class="s">&#34;#mainCamera&#34;</span> <span class="na">ambient_color=</span><span class="s">&#34;#fff&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;object</span> <span class="na">id=</span><span class="s">&#34;triangle&#34;</span> <span class="na">mesh=</span><span class="s">&#34;#triangle&#34;</span> <span class="na">material=</span><span class="s">&#34;#triangleMat&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;-4&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;light</span> <span class="na">id=</span><span class="s">&#34;mainlight&#34;</span> <span class="na">loc_x=</span><span class="s">&#34;10&#34;</span> <span class="na">loc_y=</span><span class="s">&#34;15&#34;</span> <span class="na">loc_z=</span><span class="s">&#34;10&#34;</span> <span class="na">rot_x=</span><span class="s">&#34;-1.3&#34;</span> <span class="na">color=</span><span class="s">&#34;#fff&#34;</span> <span class="na">type=</span><span class="s">&#34;L_POINT&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/scene&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/glge&gt;</span>
</span></span></code></pre></div><h3 id="the-javascript"><a class="heading-link" href="#the-javascript"></a>The javascript</h3>
<p>The javascript is pretty important. It sets up a GLGE environment, fetches all the data in the XML file, and renders it as a 3D scene. For this example, it&rsquo;s pretty simple because there&rsquo;s no action, movement or controls.</p>
<p>First, we need to get our canvas element.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">canvasElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;canvas&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>Then we create the GLGE environment.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">doc</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Document</span><span class="p">();</span>
</span></span></code></pre></div><p>Next we will load the XML file into the GLGE document.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="s2">&#34;path/to/demo_xml.xml&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>Now we set up a function to run once the XML file is loaded.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">onLoad</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Inside this function we will create a renderer and scene.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">renderer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Renderer</span><span class="p">(</span><span class="nx">canvasElement</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">scene</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Scene</span><span class="p">();</span>
</span></span></code></pre></div><p>Then we bind the scene in the javascript with the scene from the XML file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">scene</span> <span class="o">=</span> <span class="nx">doc</span><span class="p">.</span><span class="nx">getElement</span><span class="p">(</span><span class="s2">&#34;mainScene&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>Then we bind the renderer to the scene.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">renderer</span><span class="p">.</span><span class="nx">setScene</span><span class="p">(</span><span class="nx">scene</span><span class="p">);</span>
</span></span></code></pre></div><p>And finally, we render everything.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">renderer</span><span class="p">.</span><span class="nx">render</span><span class="p">();</span>
</span></span></code></pre></div><h4 id="done-1"><a class="heading-link" href="#done-1"></a>Done!</h4>
<p>Here&rsquo;s all the javascript.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">canvasElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;canvas&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">doc</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Document</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="s2">&#34;triangle.xml&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">doc</span><span class="p">.</span><span class="nx">onLoad</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">renderer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Renderer</span><span class="p">(</span><span class="nx">canvasElement</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">scene</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GLGE</span><span class="p">.</span><span class="nx">Scene</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">scene</span> <span class="o">=</span> <span class="nx">doc</span><span class="p">.</span><span class="nx">getElement</span><span class="p">(</span><span class="s2">&#34;mainScene&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">renderer</span><span class="p">.</span><span class="nx">setScene</span><span class="p">(</span><span class="nx">scene</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">renderer</span><span class="p">.</span><span class="nx">render</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="here-it-is----update-glge-isnt-supported-any-more"><a class="heading-link" href="#here-it-is----update-glge-isnt-supported-any-more"></a><del>Here it is</del> &ndash; update, glge isn&rsquo;t supported any more</h3>
<p>This is the scene described above.</p>
<p><canvas id="flatTriangle" width="400" height="300"></canvas></p>
<!--
<script src="http://example.com/glge-compiled-min.js"></script>

<script type="text/javascript">
var canvasElement = document.getElementById('flatTriangle');
var doc = new GLGE.Document();

doc.load("/posts/glge-tut1/demo_xml.xml
");

doc.onLoad = function() {
    var renderer = new GLGE.Renderer(canvasElement);
    var scene = new GLGE.Scene();
    scene = doc.getElement("mainScene");
    renderer.setScene(scene);
    renderer.render();
}
</script>

-->


  
  ]]></content:encoded></item></channel></rss>