<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Luan's Feed</title>
        <link>https://luan.xyz</link>
        <description>Posts and articles published at https://luan.xyz</description>
        <lastBuildDate>Fri, 15 May 2026 23:32:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en-us</language>
        <atom:link href="https://luan.xyz/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Card Pipeline Improvements]]></title>
            <link>https://luan.xyz//blog/2026-04-04-card-pipeline-improvements</link>
            <guid isPermaLink="false">/blog/2026-04-04-card-pipeline-improvements</guid>
            <pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Innovations on the card processing pipeline and card-press]]></description>
            <content:encoded><![CDATA[<p>I recently had the opportunity to evolve the <a href="https://luan.xyz/blog/2026-02-03-the-card-press-saga">card processing pipeline</a> thanks to the great ideas and
contributions from our office community. So I wanted to share an update of the refinements we made.</p>
<h2 id="lamination">Lamination</h2>
<p>Despite my lack of awareness of us having one, the laminating machine quickly became invaluable to a better final result as well as an easier experience with
the Cricut. After some experimentation, we discovered the best process to be:</p>
<ul>
<li>Use the 0.3 mm lamination pouches for perfect card-feel (the 0.5 mm ones were too thick for the Cricut to handle swiftly); this precludes the use of a
backing card, and makes the final sleeved result near perfect.</li>
<li>Laminating before cutting is a must due to how the pouches work; it does makes the cutting itself slightly slower (requiring a custom profile; I ended up with
max pressure and 4x passes but you can experiment with your own machine), but removing the cards from the Cricut sticker mat is infinitely easier and does not
cause irreversible bends on the cards.</li>
<li>If there are slight bends or creases on the corners due to cricut and lamination interplay, a quick second pass through the laminator (past cut, no pouch)
can easily smooth anything visible.</li>
</ul>
<h2 id="card-press">Card Press</h2>
<p>Added a few features to smooth the experience, including cut lines in case you are not using the Cricut and want to use a guillotine or similar tool.</p>
<p>Everything as usual is available at <a href="https://luan.xyz/projects/card-press/">card-press</a> and open-sourced on <a href="https://github.com/luanpotter/card-press">GitHub</a>.</p>
<h2 id="foil-paper">Foil Paper</h2>
<p>By far the most exciting development was the discovery of foil (holographic) paper. It is a cardstock with a metallic finish. We print foil cards on top, and
the shine exudes through the ink (there are also adhesive / vinyl versions for multi-layering, but I didn't go that far). You can find it on the required
US Letter size and good thickness on Staples or equivalent.</p>
<p>The only problem is that our fancy printer (which accepts the HP Click software) cannot handle it - the ink just does not adhere, regardless of the profile.
Our normal printer actually works perfectly, but it has a terrible issue. While still from HP, it is an older model that does not support HP Click.
Using the normal print dialogs on our company macOS computers was a complete pain.</p>
<p>After a lot of investigation, I concluded that the AirPrint driver simply does not respect the options on the printer dialog. I tried multiple software's
print dialog and the macOS native one, but it <em>always</em> adds an annoying margin making the cards every-so-slightly too small. In the end I realized that I
need to link the printer from scratch using a different driver (HP's own), but then, it wouldn't respect some settings such as the selected tray and print
quality. I had to resort changing those settings' defaults on the printer itself. And it would still not respect tray selection, so I had to make sure it
first failed to print on Tray 1 (that wouldn't take the foil) and then switch the failed job on the printer UI to Tray 2. I had no idea the state of printer
drivers in 2026 could be so abysmal, and definitely have some interest in taking a deeper look at the software to see if there is any way to improve this
experience (but that is far from the scope of this post). I haven't tried bringing in my Linux machine to the Office to see if it would fare any better
(would be incredibly ironic if so), so I can't comment on that.</p>
<p>All that aside, once the cards were printed on the correct size and quality on the foil paper, they look absolutely stunning. I think they could look even
better through a layered foiling approach, but that is beyond what I am willing to do for my <a href="https://luan.xyz/projects/dim-rift/">Dimensional Rift</a> playtesting
prototypes.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Overall, I found it much more enjoyable to pop the laminated cards right out of the Cricut mat, and much nicer to sleeve them by themselves, ready to playtest
with no backing cards. The foils look amazing for the fanciest cards, and I still hold that <a href="https://luan.xyz/projects/card-press/">card-press</a> is the best
tool for small-batch card printing, regardless of the cutting method.</p>
<p>I have updated the original <a href="https://luan.xyz/articles/print-and-play-with-cricut">Print and Play with Cricut tutorial</a> to include the new lamination and foil processes, so
check it out if you want to try any of these improvements!</p>
<p>If you know more about printer drivers and AirPrint on macOS, please let know, I am interested in understanding if I am doing something terribly wrong or if
this is the state of affairs with these.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Print & Play with Cricut Tutorial]]></title>
            <link>https://luan.xyz//articles/print-and-play-with-cricut</link>
            <guid isPermaLink="false">/articles/print-and-play-with-cricut</guid>
            <pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to automate your custom TCG/board-game card manufacturing with a Cricut]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>For more historical context and details about the <a href="https://luan.xyz/projects/card-press">card-press</a> tool, see <a href="https://luan.xyz/blog/2026-02-03-the-card-press-saga">The Card Press Saga</a>.
The tutorial was updated on 2026-04-04 to include lamination and foil processes - see more details on <a href="https://luan.xyz/blog/2026-04-04-card-pipeline-improvements">Card Pipeline Improvements</a>.</p>
</blockquote>
<p>This is a step-by-step tutorial on how to use a printer (that can handle cardstock or photographic paper) paired with a <a href="https://cricut.com">Cricut</a> (we have the <strong>Cricut Maker 4</strong> but should be adaptable to other models) to automatically print then cut MTG-sized cards.</p>
<figure style="display:flex;align-items:center;flex-direction:column"> <img src="https://luan.xyz/_astro/cricut-tutorial-stack.BogaOThG.jpg" alt="A complete deck for the Arkhtide TCG open-beta - fully automated." style="max-width:50%;height:auto"> <figcaption>A complete deck for the Arkhtide TCG open-beta - fully automated.</figcaption> </figure>
<h3 id="preparation">Preparation</h3>
<p>The <strong>Cricut Design Space</strong> software is utterly unusable for large quantities of cards, as it requires manually uploading and dragging images one-by-one in the most painful way imaginable. So instead, I made <a href="https://luan.xyz/projects/card-press">card-press</a>, a fully client-side webapp for generating card PDFs with (I hope) a very easy-to-use (and to automate) interface. It is <a href="https://github.com/luanpotter/card-press">open-source</a> so feel free to submit any changes or enhancements.</p>
<p>Essentially, we are going to be <em>tricking</em> the Cricut by loading an &quot;empty&quot; template as the cut PDF with just black rectangles, and printing the PDF &quot;on the side&quot; (using card-press) and then selecting the option &quot;I already printed&quot; on the Cricut.</p>
<p>So you will need to:</p>
<ul>
<li><em><code>[one time]</code></em> Make sure you have the <strong>Cricut Design Space</strong> and the printer software (in our case, <a href="https://support.hp.com/us-en/drivers/hp-click-printing-software/15550865">HP Click</a>) installed and configured.</li>
<li><em><code>[one time]</code></em> Load <a href="https://design.cricut.com/landing/project-detail/6972a7fa5a32d4c55fe26907">my custom template</a> into the Cricut Design Studio software; it matches exactly with the PDF you will generate on card-press. You can also <a href="https://luan.xyz/files/cricut/cricut-mtg-template.zip">download it here</a> if you don&#39;t want to use their &quot;cloud&quot;, but manually importing is non-trivial (exercise to the reader).</li>
<li>Load your images onto <a href="https://luan.xyz/projects/card-press">card-press</a> and generate a PDF using the pre-defined <code>Letter / Cricut - MTG 2x3</code> template (default). There are many input methods (&quot;import&quot;, file picker, drag and drop, copy and paste). If you want card backs, enable that option and generate an additional PDF for the backs.</li>
</ul>
<h3 id="materials">Materials</h3>
<p>There are 3 factors to consider when selecting your paper:</p>
<ul>
<li><strong>Size</strong>: unfortunately it must be <code>Letter</code> as that is what the Cricut understands.</li>
<li><strong>Finish</strong>: we have <code>satin</code>, <code>glossy</code> and <code>double-glossy</code> (for card backs) finishes; there is also matte. This is entirely up to you. Printing on photo/glossy paper takes slightly longer and requires a drying step, but I think it is worth it.</li>
<li><strong>Thickness</strong>: While the <code>300 gsm</code> cardstock would have a superior card-feel, I found it to sometimes cause jams (in our printer at least). So I&#39;ve started to use the <code>260 gsm</code> instead. You can always add real cards as backings inside the sleeves for enhanced flickability.</li>
</ul>
<p>Having made your choice, load a sufficient amount of sheets (according to your PDF) into the printer.</p>
<p>You can also use holographic / iridescent cardstock to make foil cards! The process is exactly the same, you just print on top of the foil paper (<em>not</em> the adhesive / vinyl ones), but make sure your printer is configured to accept it.</p>
<h3 id="execution">Execution</h3>
<ol>
<li>
<p>Kick off the print job for the entire PDF. Make sure to configure the printer for the type of paper you chose (notably, the type of finish - lest a very inky mess ensues). If you are manually attending and have free cycles in between other steps, you can cancel the in-print drying, <em>very carefully</em> dry the sheets to the side, and let the following pages get a head-start. Surprisingly, when using glossy photo paper, it is the printing that is the bottleneck rather than the cutting.</p>
</li>
<li>
<p><em><code>[optional]</code></em> If you are doing card backs, wait for all pages to finish, recollect them, flip them, and print the backs.</p>
</li>
<li>
<p><em><code>[optional]</code></em> If you are doing lamination, you can laminate the sheets once they are dry. Make sure to use the <code>0.3 mm</code> pouches for both ideal card feel and best Cricut smoothness. Laminating before cutting makes removing the cards from the Cricut mat later a breeze, and precludes the need of using backing cards on the sleeves later! It is also a pretty quick addition to the pipeline when compared to the timing of other steps.</p>
</li>
<li>
<p>Load the first printed (and dried) page of the PDF into the Cricut mat (I use the Blue / Light Grip but depending on wear and tear it might be slightly too strong or too weak). Try to align it to the top left to the best of your abilities, as the Cricut will not haphazardly scan the entire mat for the cut marks.</p>
<figure style="display:flex;align-items:center;flex-direction:column"> <img src="https://luan.xyz/_astro/cricut-tutorial-sheet.8oBgugzD.jpg" alt="Loading the printed page onto the mat." style="max-width:50%;height:auto"> <figcaption>Loading the printed page onto the mat.</figcaption> </figure>
</li>
<li>
<p>Start to &quot;Make It&quot; (the template) on the Cricut Design Space (you do not need to load any images or change anything). Click &quot;Continue&quot; and then &quot;I&#39;ve Already Printed&quot;. I use the &quot;Heavy Cardstock&quot; material setting, which I have favourited (if you are laminating, you will need a custom profile - I use max pressure and 4x passes). Load the mat and start; let the machine do the work while you focus on what really matters (overseeing it with an air of superiority).</p>
<figure style="display:flex;align-items:center;flex-direction:column"> <img src="https://luan.xyz/_astro/cricut-tutorial-heavy-cardstock.CCOdH7iN.png" alt="The Base Material setting I use. Despite the label it can cut 300 gsm without issues." style="max-width:50%;height:auto"> <figcaption>The Base Material setting I use. Despite the label it can cut 300 gsm without issues.</figcaption> </figure>
</li>
<li>
<p>Unload and peel the cards (depending on the adhesive strength, you might need to be very gentle not to bend them, unless they are laminated).</p>
<figure style="display:flex;align-items:center;flex-direction:column"> <img src="https://luan.xyz/_astro/cricut-tutorial-cut.jgdu3Jj8.jpg" alt="Cards freshly peeled from the mat." style="max-width:50%;height:auto"> <figcaption>Cards freshly peeled from the mat.</figcaption> </figure>
</li>
<li>
<p><em><code>[optional]</code></em> If called by the design of your cards, you can use the <strong>Kadomaru Pro</strong> to round the corners (its smallest setting is the closest I found to the standard MTG border-radius).</p>
</li>
</ol>
<p>And rinse and repeat! The Cricut software will require just one more click to re-do the same cut for the next page. You can very effectively alternate between collecting pages from the printer before they fall, attaching the next dry sheet onto the mat, and coding your next million-dollar project on the side.</p>
<figure style="display:flex;align-items:center;flex-direction:column"> <img src="https://luan.xyz/_astro/cricut-tutorial-sleeved.bqOoatIE.jpg" alt="The final product, sleeved and ready for playtesting." style="max-width:50%;height:auto"> <figcaption>The final product, sleeved and ready for playtesting.</figcaption> </figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[New drop-in dropped]]></title>
            <link>https://luan.xyz//blog/2026-02-08-new-drop-in-dropped</link>
            <guid isPermaLink="false">/blog/2026-02-08-new-drop-in-dropped</guid>
            <pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[New version of drop-in-css adds h2, footer, blockquote and .muted]]></description>
            <content:encoded><![CDATA[<p>While working on <a href="https://luan.xyz">this very blog</a>, I slowly started to collect some missing features in <a href="https://luan.xyz/projects/drop-in">drop-in</a>; so decided to hoist some code to the upstream.</p>
<p>As usual, you can install the <code>drop-in-css</code> package from NPM or import it directly from:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="html"><code><span class="line"><span style="color:#E1E4E8">&lt;</span><span style="color:#85E89D">link</span><span style="color:#B392F0"> rel</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">&quot;stylesheet&quot;</span><span style="color:#B392F0"> href</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">&quot;https://luan.xyz/projects/drop-in/drop-in.css&quot;</span><span style="color:#E1E4E8"> /&gt;</span></span></code></pre>
<p>Find the latest version on <a href="https://github.com/luanpotter/drop-in">GitHub</a>. Simply &quot;drop it in&quot; and it just works!</p>
<h2 id="new-features">New Features</h2>
<blockquote><p>You&#39;ve seen these features already on this website; but now they are an official part of <em>drop-in</em>.</p></blockquote>
<ul>
<li>Better style for <code>&lt;h2&gt;</code> elements (seen above)</li>
<li>New <code>.muted</code> class for <span class="muted">subtle text</span> (any element)</li>
<li>New style for <code>&lt;footer&gt;</code> elements (seen below)</li>
<li>New style for <code>&lt;blockquote&gt;</code> elements (seen above)</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Astro and RSS]]></title>
            <link>https://luan.xyz//blog/2026-02-07-astro-and-rss</link>
            <guid isPermaLink="false">/blog/2026-02-07-astro-and-rss</guid>
            <pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Setting up an RSS (and Atom) feed for the blog]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>If you want to follow this and future posts through your preferred reader, you can now subscribe to my <a href="https://luan.xyz/rss.xml">RSS</a> or <a href="https://luan.xyz/atom.xml">Atom</a> feeds.</p>
</blockquote>
<p>I spent some time digging into how to easily setup an RSS feed for these articles using my new <a href="https://astro.build">astro</a> setup; that led me into a few rabbit holes, such as learning the differences between <a href="https://stackoverflow.com/questions/3489578/which-is-most-used-rss-or-atom-and-which-one-is-better-to-build-from">RSS vs Atom</a>, setting up <a href="https://www.freshrss.org/">FreshRSS</a> on my own TrueNAS (and hopefully starting a more curated reading diet), and a few others.</p>
<p>But the biggest one was figuring out how to process a version of the actual content of the article suitable for the RSS feed. While not obligatory, it seems that the common consensus is that RSS feeds should include the article content. The problem is the content are markdown, potentially MDX files that are yet to be processed by Astro. The "official" RSS plugin also does not seem to support Atom out-of-the-box (and I wanted to offer both).</p>
<p>But nothing that some research and understanding could not fix; after studying <a href="https://github.com/delucis/astro-blog-full-text-rss/blob/latest/src/pages/rss.xml.ts">this snippet</a> and reading <a href="https://gsong.dev/articles/astro-feed-unified">this enlightening article</a> by George Song, I managed to combine them into my own, hopefully simpler <a href="https://github.com/luanpotter/luanpotter.github.io/blob/master/src/utils/feed.ts">solution</a>.</p>
<p>The key takeaways I combined from both sources were:</p>
<ul>
<li>using the <code>experimental_AstroContainer</code> API to render content "in memory" - this will work with markdown and MDX without need special handling;</li>
<li>using the <a href="https://www.npmjs.com/package/feed">feed</a> package to generate both RSS and Atom feeds with a unified API.</li>
</ul>
<p>There is still some one-off boilerplate, but hopefully it gets simpler as astro evolves its Container API. Let me know if you think it would be substantial enough to extract into a generic package, or if there are better solutions I failed to find.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hexagons]]></title>
            <link>https://luan.xyz//blog/2026-02-06-hexagons</link>
            <guid isPermaLink="false">/blog/2026-02-06-hexagons</guid>
            <pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[An add-on with drawing utilities for hex maps for Foundry VTT]]></description>
            <content:encoded><![CDATA[<p>For a while now I&#39;ve been tinkering with a custom TTRPG system based on 2d12 rolls and associated setting called <a href="https://d12.nexus">Shattered Wilds</a>. It is not intended to be a groundbreaking innovative system, but rather a different take on classic D&amp;D and other fantasy-coated systems - while implementing some of my own ideas.</p>
<p>Amongst the many things I wanted to change or do differently, using hexagonal maps for encounters always stood out to me as a superior alternative for the more tactical and nuanced combat I sought (<a href="https://www.youtube.com/watch?v=thOifuHs6eY">they are the bestagons</a>, after all).</p>
<p>However, after initial in-person sessions, I soon realized there would be some challenges when transitioning to the virtual realm. Firstly I started to look for satisfying hex-grid-aligned encounter-level map assets; but (due to not finding any and also to changing my mind about their relevance) eventually I opted for using bare grids with minimalist abstractions, akin to the ones I&#39;d been jotting over my dry-erase hexagonal mat. The challenge then shifted: I realized the drawing tools on <a href="https://foundryvtt.com/">Foundry VTT</a> (the virtual environment I use - and highly recommend) were not very well suited for swiftly improvising the kinds of annotations I was intending to plot over the grid.</p>
<p>To make the tactical situation of the battle clearer to digest for the players and myself, it would be ideal if my quick drawings would just &quot;snap&quot; to the hexagonal lattice. A wall instead of a straight line would just hug the edges of the hexes; a bush or tree would just be a stamp over one hex; and so on. On our minds, it is trivial to derive (and &quot;colour&quot;) the nuanced scene from the simplified geometry, but not the other way around when one is calculating one&#39;s next move.</p>
<p>And not founding any comprehensive hexagon-drawing toolkit, I decided to make a quick module for it. It has tools for drafting lines, shapes, and stamps, all aligned to the lattice of your current map. If doing these kinds of sketches over hexagonal grids sounds like something that would fit your games, take a look at <strong>hexagons</strong> on <a href="https://github.com/luanpotter/shattered-wilds/tree/master/packages/hexagons">GitHub</a> and <a href="https://foundryvtt.com/packages/hexagons">Foundry VTT</a>. Contributions are very welcome!</p>
<figure style="display:flex;align-items:center;flex-direction:column"> <img src="https://luan.xyz/_astro/hexagons-in-action.DXKibseW.png" alt="An actual example from a scene in a previous campaign; I used the hexagons module to sketch the lay of the land of several possible encounters during prep and very quickly make any adjustments due to player actions and interactions." style="max-width:80%;height:auto"> <figcaption>An actual example from a scene in a previous campaign; I used the hexagons module to sketch the lay of the land of several possible encounters during prep and very quickly make any adjustments due to player actions and interactions.</figcaption> </figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Card Press Saga]]></title>
            <link>https://luan.xyz//blog/2026-02-03-the-card-press-saga</link>
            <guid isPermaLink="false">/blog/2026-02-03-the-card-press-saga</guid>
            <pubDate>Tue, 03 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A fully client-side webapp for generating print-then-cut PDFs for playing cards]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>If you just want to give it a try, jump straight into <a href="https://luan.xyz/projects/card-press">card-press</a>.<br>
The source code is available on <a href="https://github.com/luanpotter/card-press">GitHub</a>.<br>
For a step-by-step tutorial on how to execute the print-then-cut pipeline with the Cricut and card-press, check out <a href="https://luan.xyz/articles/print-and-play-with-cricut">Print &amp; Play with Cricut</a>.</p>
</blockquote>
<h2 id="the-story">The Story</h2>
<p>Recently I&#39;ve been studying the new <a href="https://cricut.com">Cricut Maker 4</a> that we got for the office. After a thorough intro by our resident expert, I started to wonder how well it would fare as a pipeline for the printing of prototype cards. It is an idea I&#39;ve had for a while (some sort of automated cutting machine), but never really pursued it.</p>
<p>Previously, as I was experimenting with my (still work-in-progress) games, <a href="https://luan.xyz/projects/dim-rift">Dimensional Rift</a> and <a href="https://luan.xyz/projects/flaky-flasks">Flaky Flasks</a>, I&#39;d either:</p>
<ul>
<li>print the cards at the office, and cut them myself, through an ever growing assortment of manual cutting implements - which was an excruciating amount of work and resulted in terrible quality due to my thorough lack of artistic ability; or</li>
<li>paid some pretty penny for the corner print shop - to mixed results in terms of final quality.</li>
</ul>
<p>As I plan to iterate on those and other game development projects, and since also have other pursuits that require mass printing-then-cutting (trying out new cards and decks from established games, or new games that are online-only, print-and-play, or otherwise niche and hard to find), I decided to give the Cricut idea a go. And, to my surprise, it did not take that much finagling to get it working!</p>
<h2 id="the-paper">The Paper</h2>
<p>I have now trialled reams and reams of papers; I found that 300 gms (that&#39;s g/m² - they measure by area density instead of thickness, I can only imagine due to it being a more stable measurement over environmental conditions) is about the thickest our printer can handle; it is not as thick as a real TCG card, but close enough specially if you are sleeving. A well known trick is to add a real card behind the prototype in the sleeve, which also gives you the card back (for transparent sleeves). I usually use non-transparent sleeves and found that a single-sleeved 300 gms proxy has a good give for playing, even without the backing of a real card.</p>
<p>The finish on the paper was also up for debate; I found some people prefer the &quot;glossy&quot;, while others were fascinated by the &quot;satin&quot;. At first I had some trouble with the glossy print shedding ink (and staining everything), but that was due to my own idiocy on misconfiguring the printer. After enough experimentation, the double-glossy paper (when doing card backs) or the single satin (for no backs) emerged as winners.</p>
<figure style="display:flex;align-items:center;flex-direction:column"> <img src="https://luan.xyz/_astro/card-press.BLnHleKw.jpg" alt="Showcasing the shine of both the satin (left) and glossy (right) finishes." style="max-width:80%;height:auto"> <figcaption>Showcasing the shine of both the satin (left) and glossy (right) finishes.</figcaption> </figure>
<p>The final variable was the paper size (i.e. dimensions). While I could easily squeeze a 3x3 MTG-sized grid on both A4 or Letter (and our printer can actually do even bigger paper sizes through a roll), the Cricut not only will exclusively cut a Letter-sized sheet (much to the dismay of any <a href="https://www.cl.cam.ac.uk/~mgk25/iso-paper.html">sane person</a>), but the required template has expansive margins and cut marks, precluding any hope of fitting more than a 3x2 grid (which even then barely fits). There is some wasted paper - but the benefits of automatic cutting are beyond worth it.</p>
<p>With all that in mind, my current best pick for paper ended up being the <a href="https://www.koalagp.com/products/bulk-prices-800-3200-sheets-koala-glossy-cardstock-paper-double-sided-80lb-300gsm-8-5x11">Koala 300 gms double-glossy</a>.</p>
<h2 id="the-workflow">The Workflow</h2>
<p>Even after creating and saving a &quot;template&quot; through very manual construction, the Cricut Design Space software (whether for the cut-and-print or anything else really) absolutely sucks for repeated workflows. You have to manually drag in the images (through a convoluted unskippable upload flow) on a new &quot;project&quot; each time; re-align, re-flatten and re-group; all by hand. I started to memorize coordinates so I could more easily input them. That is not a good sign. There seems to be no templating functionality whatsoever, no way to issue commands, nor to automate anything. Using it would be a nightmare for anything larger than a single page (of, remember, 6 measly cards).</p>
<p>So, I made my own software. Turns out the two parts of the process (printing and cutting) can be fully decoupled. My tool will generate the PDF for printing (using the Cricut or any other template as a base, with cut marks and all); and, then, to actually cut, you can just load up <a href="https://luan.xyz/files/cricut/cricut-mtg-template.zip">the empty Cricut project</a> (without the card images or any modifications required). The cutting part does not care whether there are images or not - we trick the machine into thinking it is just cutting 6 black rectangles. In reality the images are there, because we printed an altogether different PDF (from my tool), that just so happens to perfectly align with the Cricut project.</p>
<h2 id="the-software">The Software</h2>
<p><a href="https://luan.xyz">card-press</a> is a fully client-side webapp for generating print-then-cut PDFs for playing cards for TCGs, board games, or any other projects. Why yet another card template generator? I had a few design goals in mind; in order:</p>
<ul>
<li>support custom PDFs as templates (for the Cricut print-and-cut workflow);</li>
<li>webapp (easily accessible) but fully client-side (fully offline, no uploading of any kind);</li>
<li>ease of use; minimal but pleasing UI; zero fuss.</li>
</ul>
<p>Even not considering the Cricut requirement, I couldn&#39;t find anything that satisfied my other goals. It is definitely still a work in progress, but ready to start battle testing. I&#39;ve added additional features such as:</p>
<ul>
<li>very configurable and customizable;</li>
<li>multiple sessions and templates, all saved locally;</li>
<li>(mass) card upload via file picker, drag-and-drop, or pasting from clipboard;</li>
<li>import cards from established TCGs (and my own);</li>
<li>generating an additional &quot;backs&quot; pdf for printing on the other side of the sheet.</li>
</ul>
<p>One important caveat to note: since all data including templates <em>and card images</em> is stored locally (on your local storage and indexed db), it will inevitably become slow if you load up a large swath of very high-res images. Though you are hopefully not going to be printing multiple hundreds of cards six a time (there ought to be better solutions at that point), if you notice any resource hogging (disk or otherwise), your storage can easily be managed through the <em>Config</em> tab.</p>
<p>I hope <a href="https://luan.xyz/projects/card-press">card-press</a> helps anyone interested in card crafting!</p>
<h2 id="the-final-touch">The Final Touch</h2>
<p>The final touch was the <strong>Kadomaru Pro</strong> corner cutter, whose ease of use is only surpassed by how satisfying its actuation is. It was the smallest corner radius I could find for the price, but still a bit bigger than a standard MTG rounding. I haven&#39;t explored making a rounded-corner template on the Cricut, but I suspect that would work very well, and also be fully compatible with card-press (should anyone wish to explore it).</p>
<p>This was a fitting end as the whole process could have been described as me trying to &quot;cutting corners&quot; on my previously excruciatingly manual process (though in this case the end result is so much better - I am relieved no none shall endure my cutting accuracy ever again). I am now excited to get back to work on my card games, knowing how painless, good, and cheap my prototypes will be. Who says you can&#39;t pick all three?</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[First post]]></title>
            <link>https://luan.xyz//blog/2026-02-01-first-post</link>
            <guid isPermaLink="false">/blog/2026-02-01-first-post</guid>
            <pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting a repository of articles and posts]]></description>
            <content:encoded><![CDATA[<p>I've been meaning to revamp my website for a while; finally, with the launch of <a href="https://luan.xyz/projects/drop-in">drop-in</a>, I decided to bite the bullet. I had two design goals for my website that were at odds:</p>
<ul>
<li>I wanted it to be zero-CSS, just HTML and what your browser gives you</li>
<li>I also despise light-mode</li>
</ul>
<p>While I could expect all users to use some extension to change the CSS of all websites to dark mode (which would definitely work pretty perfectly on a non-CSS website), I kind of wanted to enshrine dark mode as a first-class experience. But I also just didn't want to write complex, arbitrary, ad-hoc CSS.</p>
<p>I've used <a href="https://simplecss.org/">simple css</a> on a few projects before (<a href="https://luan.xyz/projects/dim-rift">[1]</a>,<a href="https://luan.xyz/projects/flaky-flasks">[2]</a>), but I wanted something that was equally or even simpler, that I also could "drop in" to any semantic HTML with very basic assumptions, but also that looked the way I wanted it to look, inspired by my very bespoke design inclinations. So I extracted drop-in from <a href="https://luan.xyz/projects/card-press">another project</a> and decided to have it on the back as my "default" stylesheet for web projects, including this one.</p>
<p>Now, for the purists, I kept an option to quickly nuke all the CSS back to smithereens - so you can still enjoy the "og" experience of a pure HTML website. I think at the end of the day this is a good compromise. It is still comfortably below the <a href="https://512kb.club/">512 KB mark</a> (even after adding the <a href="https://github.com/tonsky/font-writer">Writer</a> custom font by <a href="https://github.com/tonsky">@tonsky</a>).</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How we tamed Hibernate ORM in Kotlin with Project Yawn]]></title>
            <link>https://luan.xyz//articles/yawn</link>
            <guid isPermaLink="false">/articles/yawn</guid>
            <pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Plus, a big announcement about Faire’s open source initiatives]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>This article was <a href="https://craft.faire.com/how-we-tamed-hibernate-orm-in-kotlin-with-project-yawn-17692cbbad0e">originally posted by me on Faire's Tech Blog</a>.</p>
</blockquote>
<p>At <a href="https://www.faire.com/">Faire</a>, we have a very robust <a href="https://craft.faire.com/how-faire-uses-kotlin-to-power-our-small-business-marketplace-3c3c7cafe4ec">Kotlin backend service infrastructure</a> that we’ve carefully honed over the years, powered by a collection of established libraries and frameworks used by the broader open-source community, all coupled with some special Faire glue to make it all work. And as many within the JVM world, for ease of database access without writing and mapping SQL queries to models by hand, we use <a href="https://hibernate.org/">Apache Hibernate</a>. The Hibernate ORM is probably the most famous (infamous?) ORM framework of all time, and it has set the standard—and many of the pain points—for ORMs in general for several decades (yep, I know—feeling old yet?). It’s a polarizing topic. The discourse is often that people hate it but still use it, which leads me to compare it with that old democracy adage: it’s the worst way to do things, except for all of those other ways that have been tried from time to time.</p>
<p>It’s undeniable how easy Hibernate can make setting up basic database access, especially for smaller CRUD applications, by directly mapping tables to your POJOs, requiring very little boilerplate and glue. But it can lead to many issues that lead a lot of people to choose alternatives. But we’re not here today to talk about those, or discuss how to do ORMs or DB access in general (I leave that for the philosophers in the comments). Instead, I wanted to share a small way in which we made our usage of Hibernate, specifically the legacy Criteria API that we still use, a little bit better at Faire—taming one of its pain points with a creative solution. Hint: it will involve type-safety and KSP ;)</p>
<h2 id="the-criteria-api">The Criteria API</h2>
<p>The Criteria API is a legacy way of building queries. There are newer alternatives on newer Hibernate versions, including some that aim to fix this exact pain point we’re going to talk about. But if you’re in a large codebase that has evolved and adapted around the Criteria API, you might find newer alternatives can actually be more verbose, or require a bigger paradigm shift or migration that your team might not be ready or willing to accept. In which case, you will probably be familiar with your code for building queries currently looking something like:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#E1E4E8">criteria.</span><span style="color:#B392F0">add</span><span style="color:#E1E4E8">(Restrictions.</span><span style="color:#B392F0">eq</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"column"</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">value</span><span style="color:#E1E4E8">))</span></span></code></pre>
<p>If that looks anything familiar—this article is for you. Now, at Faire, we already had, for quite a while, a wrapper over this to make it nicer to use with Kotlin:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#E1E4E8">session.query</span><span style="color:#F97583">&#x3C;</span><span style="color:#E1E4E8">Book</span><span style="color:#F97583">></span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">addEq</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"name"</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"Lord of the Rings"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">list</span><span style="color:#E1E4E8">() </span><span style="color:#6A737D">// returns a List&#x3C;Book></span></span></code></pre>
<p>That is a very thin wrapper we’ve been using and maintaining internally for years. However, it always bothered us that while we get back a fully typed <code>List&#x3C;Book></code>, consistent with the full-type-safety we know and expect in Kotlin, the actual arguments to the query were, let’s say, not ideal.</p>
<p>The column names are just <code>String</code>, and, even worse, the values are <code>Any</code> (that is from our Kotlin wrapper; the underlying Java API is just <code>Object</code> of course). If you make an incorrect assumption about your table, the best case-scenario is only catching that later on with unit tests (and that is the <em>best</em> scenario). After seeing again and again bugs and developer productivity hits, we had a dream of making this better.</p>
<p>But, as you can imagine, it was never a priority on top of other much-needed infrastructure improvements we’re always doing. Well, that all changed during one of our glorious <a href="https://craft.faire.com/crafting-a-hack-week-that-people-love-b0c2afe2e639">Hack Weeks</a> (an internal annual hackathon where everyone at Faire can participate and form teams to work on whatever projects their heart desires). And you can bet just what our heart desired.</p>
<p>So, during that pivotal Hack Week, we built a functional prototype of what would eventually replace our wrapper; a brand-new Hibernate Criteria API wrapper, with basically the same syntax we already knew and loved, minimally amended to provide one key benefit: <strong>full type-safety</strong>.</p>
<p>And over the course of the following 2 years (!!), we, slowly, whenever we had some spare time, migrated 61% of all queries across the entire codebase to the new infrastructure. As of now, we’re happy to say more than 11 of our production services are fully migrated. Given that, alongside the fact that all new queries are type-safe, we were able to reduce the number of magic-string induced incidents (and associated developer frustration only caught during tests) to zero.</p>
<p>As we rolled out this migration, we had to add support for different types of queries, refining the code-generation to power it, fixing bugs, listening to internal feedback, and thus homing in on what we now call, and are happy to introduce: <strong>Project Yawn</strong>.</p>
<h2 id="introducing-project-yawn">Introducing: Project Yawn</h2>
<p>This is what a Yawn query looks like:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#E1E4E8">session.</span><span style="color:#B392F0">query</span><span style="color:#E1E4E8">(BookTable) { books </span><span style="color:#F97583">-></span></span>
<span class="line"><span style="color:#B392F0"> addEq</span><span style="color:#E1E4E8">(books.name, </span><span style="color:#9ECBFF">"Lord of the Rings"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>That’s right! You get an object representing your Hibernate entity (<code>BookTable</code>), with all its fields. That means you get auto-complete, <em>intellisense,</em> and compile-time checks. But that’s not all—Yawn also knows the types of your columns, so it makes sure that the <code>name</code> column on the <code>books</code> table expects a String, and nothing else.</p>
<p>“But that is not going to cut it”, you might say, “I need complex queries!” Yawn has you covered—basically any query that can be written with Hibernate’s Criteria API can be written with Yawn, including complex and nested joins, projections, etc.</p>
<p>For example, here are the e-mails of all authors in the database whose favourite book is their own writing:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#F97583">val</span><span style="color:#E1E4E8"> emails </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> session.</span><span style="color:#B392F0">project</span><span style="color:#E1E4E8">(PersonTable) { people </span><span style="color:#F97583">-></span></span>
<span class="line"><span style="color:#F97583">  val</span><span style="color:#E1E4E8"> favouriteBooks </span><span style="color:#F97583">=</span><span style="color:#B392F0"> join</span><span style="color:#E1E4E8">(people.favouriteBook)</span></span>
<span class="line"><span style="color:#F97583">  val</span><span style="color:#E1E4E8"> favouriteBooksAuthors </span><span style="color:#F97583">=</span><span style="color:#B392F0"> join</span><span style="color:#E1E4E8">(favouriteBooks.author)</span></span>
<span class="line"><span style="color:#B392F0">  addIn</span><span style="color:#E1E4E8">(people.name, authors)</span></span>
<span class="line"><span style="color:#B392F0">  addEq</span><span style="color:#E1E4E8">(people.name, favouriteBooksAuthors.name)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0">  project</span><span style="color:#E1E4E8">(people.email)</span></span>
<span class="line"><span style="color:#E1E4E8">}.</span><span style="color:#B392F0">list</span><span style="color:#E1E4E8">()</span></span></code></pre>
<p>And we support much more: projection to data classes, sub-queries (detached and correlated), join references, and much more.</p>
<p>And that was all thanks to…</p>
<h2 id="the-magic-of-ksp">The magic of KSP</h2>
<p>In order to power the creation of the meta-model representations of our tables based on our Hibernate entities, we use the power of <a href="https://kotlinlang.org/docs/ksp-overview.html">Kotlin Symbol Processing</a>, the official meta-programming framework in Kotlin. That means we hook up compilations steps to the compiler itself—no external tools, no scripts. It is pure Kotlin code that is automatically run when you add the Yawn dependency, and integrates well with IntelliJ or your preferred editor (references and go to definition all work out-of-the-box).</p>
<p>We have generators that scan through any entities <code>Foo</code> annotated with <code>@YawnEntity</code> on our Gradle module and generate a <code>FooTable</code> definition to be used for queries, maintaining the same visibility modifiers as the original class. It has references to columns and relationships, allowing for type-safe joins, and works with all Hibernate use-cases we had so far in our vast codebase (the hardest technical challenges were settling the exact shape and design of our APIs to support these edge cases such as embedded entities, composite keys, references with different foreign keys, etc).</p>
<p>After a period of tweaking the interfaces to be more ergonomic, fighting with the underlying Hibernate implementations, and wrangling some complex generics, the generators are now just plain Kotlin code, so they’re easily amendable by the entire team if there’s any feature missing.</p>
<p>We’ve tinkered with and refined it as we added to more usages and more complex use cases. But we wanted more—we wanted to share what we did with the community, in case other projects using Hibernate could benefit (and contribute!) to Yawn. So… we did.</p>
<h2 id="oss">OSS</h2>
<p>We are thrilled to announce that we are officially <a href="https://github.com/Faire/yawn/">fully open-sourcing Project Yawn under the MIT license</a>! We believe in the power and community of open source, and while we use many tools and libraries, we want to contribute back with something we could share from our work.</p>
<p>You can check out the repository at <a href="https://www.notion.so/fdp-2b02efb5c25a80e68849c2bd1fe92c5b?pvs=21">github.com/faire/yawn</a> for instructions on how to get started. We also welcome contributions and constructive feedback, and would love if you could give us a star!</p>
<p>And that’s not all—this is just one of a few pieces we’re happy to announce as part of a broader company-wide commitment to open-source and the developer community. We’ve started to build a dedicated public-facing OSS page at <a href="http://faire.tech/open-source">faire.tech/open-source</a> where we aim to collect and catalogue projects we have (or have yet to) publish, as well as other contributions we’ve made over the years to existing and established libraries and tools we use every day.</p>
<p>If you’re interested in our other projects, I’d recommend checking out our <a href="https://github.com/Faire/faire-detekt-rules/">faire-detekt-rules</a>, a curated and opinionated collection of custom Detekt rules and configs that we use on our Kotlin modules. You can opt in many of the rules that help us catch bugs early on, standardize best practices, and just keep our code looking pristine.</p>
<p>If you’re looking to write more complex queries that Hibernate and Yawn can’t support, we highly recommend <a href="https://github.com/sqldelight/sqldelight">sqldelight</a> (which, fun fact, also uses KSP under the hood), in which case you might want to check out the <a href="https://www.notion.so/Faire-sqldelight-cockroachdb-dialect-2152efb5c25a800ea067de49c523e651?pvs=21">CRDB connector we open-sourced a while ago.</a></p>
<p>This is just the beginning of how we think Faire can contribute to the broader OSS community—stay tuned for new additions very soon as we work to extract other pieces of our codebase and infrastructure.</p><!-- cspell:disable-next-line -->
<p>Many thanks to everyone at Faire and elsewhere that has helped us along the way, including, but not limited to, <a href="https://www.linkedin.com/in/adrielmartinez/">Adriel Martinez</a>, <a href="https://www.linkedin.com/in/emilycoleary/">Emily O’Leary</a>, <a href="https://www.linkedin.com/in/jeanyang0/">Jean Yang</a>, <a href="https://www.linkedin.com/in/kevinbrightwell/">Kevin Brightwell</a>, <a href="https://www.linkedin.com/in/luan-nico/">Luan Nico</a>, <a href="https://www.linkedin.com/in/micahbeech/">Micah Beech</a>, <a href="https://www.linkedin.com/in/oren-kislev-99506a77/">Oren Kislev</a>, <a href="https://www.linkedin.com/in/quinn-budan/">Quinn Budan</a>, <a href="https://www.linkedin.com/in/stanislav-novosad-04861b161/">Stas Novosad</a>, and <a href="https://www.linkedin.com/in/zhipingcai/">Zhiping Cai</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Brief History of Promises]]></title>
            <link>https://luan.xyz//articles/brief-history-of-promises</link>
            <guid isPermaLink="false">/articles/brief-history-of-promises</guid>
            <pubDate>Sat, 29 Oct 2016 00:00:00 GMT</pubDate>
            <description><![CDATA[How Promises came about and evolved in Javascript]]></description>
            <content:encoded><![CDATA[<p>Most imperative (non-functional) languages have a tendency to perform all their operations synchronously. This means that when you call a method, in, say, Java, you expect it a priori to perform your operation on that precise moment, and only return when that's done, with a result. Even in JS, a simple code like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">var</span><span style="color:#E1E4E8"> result </span><span style="color:#F97583">=</span><span style="color:#B392F0"> longOperation</span><span style="color:#E1E4E8">(args);</span></span></code></pre>
<p>Makes it very clear that the method will hold execution until it's done, so it can return the result string. While that's good and all, in a more event based language like JS, that has a single thread, it can be dangerous to go around performing a long chain of operations in a single event dispatch. Therefore, it's very common to see a method like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#B392F0">longOperation</span><span style="color:#E1E4E8">(args, </span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">  display</span><span style="color:#E1E4E8">(result);</span></span>
<span class="line"><span style="color:#E1E4E8">});</span></span></code></pre>
<p>This new version of <code>longOperation</code>, now written in JS, takes the same <code>args</code> as before but also takes a function (making usage of JS incredible powerful functional programming paradigm), that is callback. A callback is not but a function that is going to be "called back" when the first function ends. The existence of such callback, instead of a return statement, should make it clear the first function is asynchronous, i.e., it returns immediately, and just gives the result later on. This is a simple distinction that must be made; for instance, take the following two snippets:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#6A737D">// test A</span></span>
<span class="line"><span style="color:#B392F0">longOperation</span><span style="color:#E1E4E8">(args, </span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#E1E4E8">  console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(result);</span></span>
<span class="line"><span style="color:#E1E4E8">});</span></span>
<span class="line"><span style="color:#E1E4E8">console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"after"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D">// test B</span></span>
<span class="line"><span style="color:#F97583">var</span><span style="color:#E1E4E8"> result </span><span style="color:#F97583">=</span><span style="color:#B392F0"> longOperation</span><span style="color:#E1E4E8">(args);</span></span>
<span class="line"><span style="color:#E1E4E8">console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(result);</span></span>
<span class="line"><span style="color:#E1E4E8">console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"after"</span><span style="color:#E1E4E8">);</span></span></code></pre>
<p>Note that test B will always give the described result, i.e., it will first print the result and then 'after'. However, one must note that the first (test A) example will always first print 'after' and then prints the result. This is a simple yet fundamental shift of perspective that would shape the JS development significantly throughout its history. Yet when we try to accomplish something just a little bit more complex, we run into some annoyances. Let's take a look at the following, more in depth example:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#B392F0">longOperation1</span><span style="color:#E1E4E8">(args, </span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result1</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">  longOperation2</span><span style="color:#E1E4E8">(result1, </span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result2</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">    display</span><span style="color:#E1E4E8">(result2);</span></span>
<span class="line"><span style="color:#B392F0">    triggerScreenChange</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> () {</span></span>
<span class="line"><span style="color:#B392F0">      warnUser</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">response</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">        sendServerFeedback</span><span style="color:#E1E4E8">(response);</span></span>
<span class="line"><span style="color:#E1E4E8">      });</span></span>
<span class="line"><span style="color:#E1E4E8">    });</span></span>
<span class="line"><span style="color:#E1E4E8">  });</span></span>
<span class="line"><span style="color:#E1E4E8">});</span></span></code></pre>
<p>As you can see, the code quickly gets a mess as the lines get further and further to the right. This phenomenon shared by every imperative C-like language is labelled callback hell, or more appropriately, Pyramid of Doom (referencing the triangular shape formed by each line start).</p>
<p>How can we make it prettier and easier to understand? Well, let's begin with a simple premise (no pun intended): instead of receiving a callback, the function will return an object that will work as a configurer, that will allow you to setup the callback later on. The API will look like something like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">var</span><span style="color:#E1E4E8"> configurer </span><span style="color:#F97583">=</span><span style="color:#B392F0"> longOperation</span><span style="color:#E1E4E8">(args);</span></span>
<span class="line"><span style="color:#E1E4E8">configurer.</span><span style="color:#B392F0">setupCallback</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">response</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">  display</span><span style="color:#E1E4E8">(response);</span></span>
<span class="line"><span style="color:#E1E4E8">});</span></span></code></pre>
<p>Well, that seems pretty similar. Firstly, let's study how one would be able to go around doing that. Let's assume we have a synchronous <code>opSync</code>; we could then create the function <code>op</code>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">var</span><span style="color:#B392F0"> op</span><span style="color:#F97583"> =</span><span style="color:#F97583"> function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">args</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#F97583">  var</span><span style="color:#E1E4E8"> callback </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> null</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583">  var</span><span style="color:#E1E4E8"> configurer </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">    then</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">aCallback</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#E1E4E8">      callback </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> aCallback;</span></span>
<span class="line"><span style="color:#E1E4E8">    },</span></span>
<span class="line"><span style="color:#E1E4E8">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0">  setTimeout</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> () {</span></span>
<span class="line"><span style="color:#F97583">    var</span><span style="color:#E1E4E8"> result </span><span style="color:#F97583">=</span><span style="color:#B392F0"> opSync</span><span style="color:#E1E4E8">(arg);</span></span>
<span class="line"><span style="color:#B392F0">    callback</span><span style="color:#E1E4E8">(result);</span></span>
<span class="line"><span style="color:#E1E4E8">  }, </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">  return</span><span style="color:#E1E4E8"> configurer;</span></span>
<span class="line"><span style="color:#E1E4E8">};</span></span></code></pre>
<p>This is a very naïve approach, of course, as it doesn't account for the possibility of the callback not being set upon resolution, for example. But it clearly shows how easy it is to turn a sync operation into a <em>configurer</em>. Now let's see how we would use the API:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#B392F0">op1</span><span style="color:#E1E4E8">(args).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result1</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">  op2</span><span style="color:#E1E4E8">(result1).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result2</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">    op3</span><span style="color:#E1E4E8">(result2).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result3</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">      op4</span><span style="color:#E1E4E8">(result3).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">result4</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#B392F0">        sendServerFeedback</span><span style="color:#E1E4E8">(result4);</span></span>
<span class="line"><span style="color:#E1E4E8">      });</span></span>
<span class="line"><span style="color:#E1E4E8">    });</span></span>
<span class="line"><span style="color:#E1E4E8">  });</span></span>
<span class="line"><span style="color:#E1E4E8">});</span></span></code></pre>
<p>Well... I guess nothing changed, right? Well, that's because we failed to see the greatest advantage of this schema. Our <code>then</code> function was <code>void</code>, but we can make it return another <em>configurer</em>, to attach callbacks after it has ended. Now you can see where this is going... Basically this is making a Builder for the PoD! The structure would look like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#B392F0">op1</span><span style="color:#E1E4E8">(args).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(op2).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(op3).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(op4).</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(sendServerFeedback);</span></span></code></pre>
<p>That's right, we can do all that with a single like, as long as our functions are <em>configurer</em> compatible. Now that we understood the concept, let's call our <em>configurer</em> by their actual name: promises. So someone could build this incredible library, that's similar to what we already did, but actually returns subsequent promises and also checks for errors and more? Well, of course they did that. Actually, plenty of people did.</p>
<p>One example is <em>q</em>, a very simple JS lib that does that; its syntax would look something like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#79B8FF">Q</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">fcall</span><span style="color:#E1E4E8">(promisedStep1)</span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(promisedStep2)</span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(promisedStep3)</span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(promisedStep4)</span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">then</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">value4</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D">    // Do something with value4</span></span>
<span class="line"><span style="color:#E1E4E8">  })</span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">catch</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">function</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">error</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D">    // Handle any error from all above steps</span></span>
<span class="line"><span style="color:#E1E4E8">  })</span></span>
<span class="line"><span style="color:#E1E4E8">  .</span><span style="color:#B392F0">done</span><span style="color:#E1E4E8">();</span></span></code></pre>
<p>Taken directly from their docs. Pretty neat, hu?</p>
<p>So for some time people were very happy using several of <em>q</em>-like libs, but some libs started depending on these promise libs, and things started to get messy again, as one would need to write a converter to convert between different terminologies. Eventually a bunch of people got together and made <a href="https://promisesaplus.com">A+, a spec for all promise libs to follow</a>.</p>
<p>After that, this standard got accepted as native in JS, and ECMA 6 finally added the Promise object. It is A+ compatible, native, and made obsolete all promises lib. Now everyone could use that and live happily ever after - more details on the story <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise">in this article</a>. It also added very interesting methods, like <code>Promise.all()</code>, which are of crucial importance for larger apps to remain concise and pretty.</p>
<p>After that, there was more development, and now we have a Stage 3 proposal for the next release to add the <code>await</code>/<code>async</code> keywords to the language and <a href="https://ponyfoo.com/articles/understanding-javascript-async-await">change promises once again</a>.</p>
<p>That's it!</p>]]></content:encoded>
        </item>
    </channel>
</rss>