<?xml version="1.0" encoding="UTF-8"?><rss 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" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[CTRL+CREATE’s Substack]]></title><description><![CDATA[This publication has moved to Engineering Taste. Find all new posts at engineeringtaste.substack.com]]></description><link>https://ctrlcreatelabs.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!n7k-!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72fdffa5-44da-4526-8b03-c56241174a25_1024x1024.png</url><title>CTRL+CREATE’s Substack</title><link>https://ctrlcreatelabs.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 23 Jun 2026 01:23:12 GMT</lastBuildDate><atom:link href="https://ctrlcreatelabs.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[CTRL+CREATE]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ctrlcreate@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ctrlcreate@substack.com]]></itunes:email><itunes:name><![CDATA[CTRL+CREATE]]></itunes:name></itunes:owner><itunes:author><![CDATA[CTRL+CREATE]]></itunes:author><googleplay:owner><![CDATA[ctrlcreate@substack.com]]></googleplay:owner><googleplay:email><![CDATA[ctrlcreate@substack.com]]></googleplay:email><googleplay:author><![CDATA[CTRL+CREATE]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The Friday Sync // 02.13.26]]></title><description><![CDATA[Issue 002 &#8212; The Spatial Runway & Agentic Commerce]]></description><link>https://ctrlcreatelabs.substack.com/p/the-friday-sync-021326</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/the-friday-sync-021326</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Fri, 13 Feb 2026 21:24:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!eQeY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>CTRL+CREATE</strong> <strong>Feb 13, 2026</strong></p><div><hr></div><h3>01 / THE DROP</h3><p><strong>The NYFW &#8220;Retail Innovation Lab&#8221; &amp; The Spatial Front Row</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">CTRL+CREATE&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eQeY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eQeY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 424w, https://substackcdn.com/image/fetch/$s_!eQeY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 848w, https://substackcdn.com/image/fetch/$s_!eQeY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!eQeY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eQeY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg" width="1000" height="563" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:563,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Maxwell Osborne and Dao-Yi Show&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Maxwell Osborne and Dao-Yi Show" title="Maxwell Osborne and Dao-Yi Show" srcset="https://substackcdn.com/image/fetch/$s_!eQeY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 424w, https://substackcdn.com/image/fetch/$s_!eQeY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 848w, https://substackcdn.com/image/fetch/$s_!eQeY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!eQeY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5986e41-5bb9-47ec-9f6a-92813a333bd5_1000x563.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h6>Lexie Moreland/WWD</h6><p>Fashion Month kicked off this week in New York, but the most interesting &#8220;venue&#8221; wasn&#8217;t a pier in Chelsea, rather it was the <strong>N4XT Experiences Retail Innovation Lab</strong> at High Line Nine.</p><p>While the headlines are buzzing about <strong>Gigi Hadid at Moncler Aspen</strong> and the <strong>NikeSKIMS</strong> standalone brand launch (Kim K in that bold belt-top is everywhere), creative technologists should look at the plumbing:</p><ul><li><p><strong>The Spatial Mirror:</strong> Several shows integrated &#8220;spatial twins&#8221; via <strong>Style3D</strong>. They aren&#8217;t just 3D renders; they are physics-accurate digital garments that buyers can interact with in real-time, reducing physical sample waste by nearly 70%.</p></li><li><p><strong>The J.Crew &#8220;Rollneck Remix&#8221;:</strong> A Soho pop-up that used a &#8220;bespoke&#8221; immersive experience to drop limited-edition reimagined sweaters from five emerging designers.</p></li><li><p><strong>The Tabi-Nike Mashup:</strong> NikeSKIMS&#8217;s first hero product, the <strong>Rift Satin</strong>, is a split-toe shoe that effectively democratizes the Margiela Tabi aesthetic for the luxury-performance market.</p></li></ul><p><strong>The Takeaway:</strong> The &#8220;Show&#8221; is becoming a data-collection event. Every interaction with a digital twin at NYFW is feeding back into production timelines, collapsing the gap between &#8220;Runway&#8221; and &#8220;In-Stock.&#8221;</p><div><hr></div><h3>02 / THE AI PLAY</h3><p><strong>Checkout APIs: The LLM is Your New Storefront</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!e47F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!e47F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!e47F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!e47F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!e47F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!e47F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg" width="1200" height="900" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;UK high street left reeling as Debenhams goes into ...&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="UK high street left reeling as Debenhams goes into ..." title="UK high street left reeling as Debenhams goes into ..." srcset="https://substackcdn.com/image/fetch/$s_!e47F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!e47F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!e47F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!e47F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1fb270-4dd4-48f4-bc9c-10db56a7eab7_1200x900.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h6>The Guardian</h6><p>As we move into London Fashion Week (Feb 19-23), the industry is pivoting from &#8220;AI as a tool&#8221; to <strong>&#8220;Agentic Commerce&#8221;</strong> as the default.</p><ul><li><p><strong>The Debenhams Move:</strong> They just enabled a full checkout experience directly within the PayPal app&#8217;s AI assistant. No redirect. No landing page. The AI acts as the stylist and the cashier in a single conversational thread.</p></li><li><p><strong>AEO over SEO:</strong> Your metadata is now your most valuable creative asset. If an LLM (ChatGPT, Gemini, Perplexity) can&#8217;t parse your technical specs (fabric weight, drape, carbon footprint), you are invisible to the 25% of consumers who now start their shopping journey in a chat interface.</p></li><li><p><strong>Agentic Negotiation:</strong> We are seeing the first instances of &#8220;Buyer Agents&#8221; negotiating bulk discounts or shipping terms with &#8220;Seller Agents&#8221; in B2B fashion marketplaces.</p></li></ul><blockquote><p><strong>Creative Tech Insight:</strong> Your website is no longer a destination; it&#8217;s a database. The <em>interface</em> is whatever LLM the customer happens to be talking to.</p></blockquote><div><hr></div><h3>03 / THE SHAKEUP</h3><p><strong>Tactile Richness vs. The &#8220;Dead&#8221; Pixel</strong></p><p>The data from <em>Heuritech</em> for the 2026 cycle is sending a clear message: <strong>Quiet Luxury is over.</strong> We are entering the era of <strong>Tactile Richness.</strong></p><ul><li><p><strong>The Numbers:</strong> Tulle (+30%), Zebra prints (+21%), and &#8220;Brut Denim&#8221; (+10%) are the breakout stars.</p></li><li><p><strong>The Logic:</strong> In an era where AI can generate &#8220;perfectly smooth&#8221; images, humans are craving <strong>tactile friction</strong>. Ruffles, feathers, and raw fibers are a rebellion against the &#8220;flatness&#8221; of the screen.</p></li><li><p><strong>The Hardware Pivot:</strong> CES 2026 proved that &#8220;wearables with screens&#8221; are legacy. The new wave is <strong>Invisible Tech</strong>&#8212;haptic skin patches and e-textiles that provide sensory feedback without a display.</p></li></ul><p><strong>The Shakeup:</strong> If your brand identity is built on &#8220;minimalist perfection,&#8221; you&#8217;re about to feel very 2024. 2026 is about volume, texture, and physiological feedback loops.</p><div><hr></div><h3>The Closing Log</h3><p>The middle is disappearing. You&#8217;re either running your own intelligence infrastructure to track these material shifts in real-time, or you&#8217;re waiting for a PDF report that&#8217;s already six months behind the curve </p><div><hr></div><p>That&#8217;s it for Issue 002. A drop, a play, and a shakeup.</p><p>If this hit your inbox and sparked a project idea or made you want to burn your current roadmap, <strong>please reachout</strong>. This lab works best when the signal goes both ways.</p><p>Catch you in the logs. See you next Friday.</p><p>Sending &lt;3 </p><p>CTRL + CREATE</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">CTRL+CREATE&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Friday Sync // 02.05.26]]></title><description><![CDATA[Issue 001 &#8212; Welcome to the studio.]]></description><link>https://ctrlcreatelabs.substack.com/p/the-friday-sync-020526</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/the-friday-sync-020526</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Fri, 06 Feb 2026 14:38:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!iOGt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Before we start</h2><p>If you&#8217;ve been following CTRL + CREATE Labs, you know what that is. Build logs. Real projects. The boring-but-important decisions that happen between having an idea and actually shipping it. That&#8217;s still the core and it&#8217;s not going anywhere.</p><p>But I kept having the same problem. Every week I&#8217;d come across a few stories that were genuinely reshaping the ground under what I was building, and I had nowhere to put them. They&#8217;d end up in a Notes post nobody saw or a voice note to a friend at 11pm.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">CTRL+CREATE&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>So this is The Friday Sync. Three sections. Every Friday.</p><ul><li><p><strong>The Drop</strong> is the most interesting thing that shipped this week. Could be a sneaker. Could be an app. Could be a piece of hardware. The only rule is it actually had to ship.</p></li><li><p><strong>The AI Play</strong> is the AI development I can&#8217;t stop thinking about, filtered through a fashion-tech lens. Sometimes it&#8217;ll include stuff I&#8217;m experimenting with myself.</p></li><li><p><strong>The Shakeup</strong> is whatever is rattling the world this week. Could be an industry implosion. Could be a tariff. Could be something that hasn&#8217;t hit fashion yet but will.</p></li></ul><p>That&#8217;s the format. Let&#8217;s get into it.</p><div><hr></div><p><strong>01 / the drop</strong></p><p><strong>NikeSKIMS Spring &#8216;26</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iOGt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iOGt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 424w, https://substackcdn.com/image/fetch/$s_!iOGt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 848w, https://substackcdn.com/image/fetch/$s_!iOGt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!iOGt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iOGt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg" width="1456" height="1363" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1363,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:348442,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/186989018?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iOGt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 424w, https://substackcdn.com/image/fetch/$s_!iOGt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 848w, https://substackcdn.com/image/fetch/$s_!iOGt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!iOGt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718648ea-f04e-4448-9714-90463c35f043_1476x1382.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This one dropped literally today and it&#8217;s worth paying attention to for reasons beyond the product itself.</p><p>NikeSKIMS launched its first full head-to-toe collection:</p><ul><li><p>Footwear, apparel, accessories, five distinct material lines</p></li><li><p>LISA from Blackpink fronting the campaign</p></li><li><p>Global expansion into Europe, the Middle East, Australia, and Korea</p></li><li><p>Hero product: the Rift Satin, a split-toe shoe reimagining the original Nike Rift with a satin upper and tabi-toe silhouette &#8212; a nod to Margiela&#8217;s tabi </p></li></ul><p>But here&#8217;s what actually makes this interesting. NikeSKIMS isn&#8217;t a collab. It&#8217;s a standalone brand:</p><ul><li><p>Nike deployed a fourteen-year veteran to run operations</p></li><li><p>Moved employees from Global Brand Marketing, Product Design, Creative Direction, and Digital into this thing full time</p></li><li><p>The Fashion Law described it as likely a licensing deal structured so Nike controls the venture while SKIMS provides the cultural equity</p></li><li><p>Jens Grede, the SKIMS CEO, said explicitly: &#8220;more than a collaboration, it&#8217;s a new brand redefining activewear&#8221;</p></li></ul><p>That&#8217;s a different sentence than anything Nike has said about a partnership in a long time. The Dior collabs, the Travis Scott drops, even ACG and Nike SB&#8212;those were all capsules or sub-brands. This is Nike saying: we think the women&#8217;s activewear market is big enough to justify building an entirely new brand from scratch, and we think Kim Kardashian&#8217;s team understands that customer better than we do internally.</p><p>The question that arises: is a NIKESKIMS going to become a coveted fitness brand? Or a very expensive press release?</p><div><hr></div><p><strong>02 / the ai play</strong></p><p><strong>AI just got a checkout button.</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZtpY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZtpY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 424w, https://substackcdn.com/image/fetch/$s_!ZtpY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 848w, https://substackcdn.com/image/fetch/$s_!ZtpY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 1272w, https://substackcdn.com/image/fetch/$s_!ZtpY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZtpY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp" width="957" height="537" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:537,&quot;width&quot;:957,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:166578,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/186989018?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZtpY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 424w, https://substackcdn.com/image/fetch/$s_!ZtpY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 848w, https://substackcdn.com/image/fetch/$s_!ZtpY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 1272w, https://substackcdn.com/image/fetch/$s_!ZtpY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F300a66c2-5be2-4c63-a7b0-710a72e541dc_957x537.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>BoF reported this month that AI search engines (specifically ChatGPT, Gemini, and Perplexity) are moving from product discovery into direct purchasing:</p><ul><li><p>Shopping traffic from AI platforms to retail sites went up nearly 700% year over year during the 2025 holiday season</p></li><li><p>A quarter of consumers globally now start their shopping journey inside an AI chat instead of on Google</p></li><li><p>And now you can actually buy without leaving the chat</p></li></ul><p>This is where things shift. Your product page is not your storefront anymore. An LLM is. And it doesn&#8217;t care about:</p><ul><li><p>Your hero image</p></li><li><p>Your brand film</p></li><li><p>Your carefully art-directed campaign</p></li></ul><p>It cares about:</p><ul><li><p>Structured data &amp; metadata</p></li><li><p>Clean product descriptions</p></li><li><p>Whether your checkout API works inside a conversational interface</p></li></ul><p>JD Sports is already letting people buy through ChatGPT and Microsoft Copilot with one click. That&#8217;s not a beta test or a conference demo. That&#8217;s live commerce through a language model.</p><div><hr></div><p><strong>03 / the shakeup</strong></p><p><strong>OpenClaw and the death of reactive trend forecasting</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a_Qd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a_Qd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 424w, https://substackcdn.com/image/fetch/$s_!a_Qd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 848w, https://substackcdn.com/image/fetch/$s_!a_Qd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 1272w, https://substackcdn.com/image/fetch/$s_!a_Qd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!a_Qd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27540,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/186989018?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!a_Qd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 424w, https://substackcdn.com/image/fetch/$s_!a_Qd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 848w, https://substackcdn.com/image/fetch/$s_!a_Qd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 1272w, https://substackcdn.com/image/fetch/$s_!a_Qd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F174f5d0e-298e-421b-8ff9-4c12851dab22_2040x1360.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I spent this week setting up OpenClaw. What is that? In short, it&#8217;s an AI assistant with full system access that can actually execute tasks, not just suggest them. It&#8217;s a multi-agent system with specialized agents handling everything from code execution to market research to content generation. And it&#8217;s changing how I think about trend intelligence entirely.</p><p>Here&#8217;s the problem with how trend forecasting works right now:</p><ul><li><p>Cool Hunting and WGSN publish reports months after early adopters have moved on</p></li><li><p>By the time a trend shows up in a deck, it&#8217;s already consensus</p></li></ul><p>The brands that win are the ones spotting signals before they become obvious, but monitoring those signals manually is basically impossible at scale. </p><p>What if you had an agent constantly running in the background, tracking:</p><ul><li><p>What&#8217;s actually shipping (not just what&#8217;s being talked about)</p></li><li><p>Where niche communities are clustering attention</p></li><li><p>Which material innovations are moving from sample stage to production</p></li><li><p>What adjacent industries are building that hasn&#8217;t crossed over yet</p></li><li><p>Hiring patterns at major brands</p></li><li><p>Trademark filings that hint at launches 6-12 months out</p></li></ul><p>OpenClaw can do that. Not replacing human intuition, but giving you the data infrastructure to make faster, more confident bets.</p><p>Example: Imagine knowing NikeSKIMS was coming six months before the announcement because you were tracking:</p><ul><li><p>Nike&#8217;s hiring patterns in women&#8217;s activewear</p></li><li><p>SKIMS&#8217;s trademark filings</p></li><li><p>Shifts in activewear search behavior</p></li><li><p>Material sourcing changes that signaled a luxury-performance hybrid</p></li></ul><p>All at once. Automatically. With context.</p><p>The gap between seeing a trend and acting on it collapses. That&#8217;s not a productivity tool. That&#8217;s a structural advantage.</p><p>I&#8217;m building this for myself first: my own trend radar that never stops running. But if it works the way I think it will, the entire model of &#8220;wait for the report, then react&#8221; becomes obsolete. The people with access to real-time intelligence infrastructure will be operating in a completely different timeframe than everyone else.</p><p>And that&#8217;s not theoretical. OpenClaw is live right now. I&#8217;ll do a full build log soon, but the short version is: the trend forecasting industry is about to have a Saks moment. The middle is disappearing. You&#8217;re either running your own intelligence infrastructure or you&#8217;re six months behind. </p><div><hr></div><p>That&#8217;s the first one. A drop, a play, a shakeup.</p><p>If something hit or if I missed the obvious story of the week, just reply. This gets better when it goes both ways.</p><p>See you next Friday.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">CTRL+CREATE&#8217;s Substack is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Most AI Products Have No Taste (And Why That's a Problem)...]]></title><description><![CDATA[... and how they are technically impressive and emotionally hollow.]]></description><link>https://ctrlcreatelabs.substack.com/p/most-ai-products-have-no-taste-and</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/most-ai-products-have-no-taste-and</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Mon, 29 Dec 2025 21:00:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!n7k-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72fdffa5-44da-4526-8b03-c56241174a25_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Here&#8217;s something I&#8217;ve noticed after a year of building AI products: we&#8217;ve gotten incredibly good at making things that <em>work</em>. We&#8217;ve gotten terrible at making things that <em>feel right</em>.</p><p>Open any AI-powered app. The recommendations are technically accurate. The outputs are grammatically correct. The features check every box on a PRD somewhere. And yet, something&#8217;s off. There&#8217;s a flatness. A generic quality that screams &#8220;Is this AI?&#8221;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I&#8217;ve started calling this problem what it is: <strong>a taste gap.</strong></p><p>And I think closing it is one of the most underserved opportunities in tech right now.</p><div><hr></div><h2>The Beige Swirl Effect</h2><p>There&#8217;s a reason AI output feels so... samsies. Generative models are trained to maximize likelihood, which means they cluster around the statistical center of their training data. The result is what I call the <strong>beige swirl</strong>: nothing ugly, nothing memorable. Perfectly optimized for inoffensiveness.</p><p>The research backs this up. Wharton recently re-ran a classic creativity task. The task was to invent a toy using only a brick and a hand-fan. The results were brutal:</p><ul><li><p>94% of ChatGPT-assisted ideas were semantic near-duplicates</p></li><li><p>Six different teams independently named their toy &#8220;Build-a-Breeze Castle&#8221;</p></li><li><p>The human-only group? 100% unique ideas</p></li></ul><p>LINK: <a href="https://knowledge.wharton.upenn.edu/article/does-ai-limit-our-creativity/">Wharton creativity study</a></p><p>This isn&#8217;t a bug. It&#8217;s the math. AI homogenizes output by design. And homogeneity is the opposite of taste.</p><div><hr></div><h2>What Even Is &#8220;Taste Engineering&#8221;?</h2><p>Taste engineering is the discipline of building systems that understand and replicate <em>aesthetic intelligence</em>, the implicit, often ineffable quality that makes something feel curated rather than computed.</p><p>It&#8217;s not about making things pretty. It&#8217;s about encoding the same intuition a great editor, stylist, or curator uses when they look at something and just <em>know</em> it belongs.</p><p>Traditional ML optimizes for explicit signals: clicks, conversions, engagement. Taste engineering optimizes for coherence aka the subtle relationship between elements that makes a whole feel intentional.</p><p>Think about the difference between:</p><ul><li><p>Spotify&#8217;s algorithm recommending songs you&#8217;ll statistically tolerate vs. a playlist that actually captures a <em>mood</em></p></li><li><p>Pinterest showing you &#8220;similar pins&#8221; vs. understanding why that one grainy photo of a 1970s Italian kitchen speaks to you specifically</p></li><li><p>A shopping app filtering by &#8220;category: dress&#8221; vs. knowing that the <em>vibe</em> you&#8217;re after lives somewhere between Miu Miu and your mom&#8217;s college photos</p></li></ul><p>The first versions are engineering problems. The second versions are taste problems. And almost nobody is building for the second.</p><div><hr></div><h2>The Evidence Is Everywhere</h2><h3>Interior Design Bots = Wallpaper Generators</h3><p>A Nature study plugged standard Stable Diffusion into an interior-design workflow. The models could fill a room with furniture. They could match colors. What they couldn&#8217;t do: understand rhythm, negative space, or cultural symbolism.</p><p>Professional evaluators scored the AI renders as <strong>&#8220;technically correct&#8221; but &#8220;aesthetically bland&#8221; 7&#215; more often than human designs.</strong> The authors&#8217; conclusion: &#8220;Traditional diffusion does not consider aesthetic factors&#8230;leading to results that lack visual appeal.&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!91Q7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!91Q7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!91Q7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!91Q7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!91Q7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!91Q7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg" width="352" height="352" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:736,&quot;resizeWidth&quot;:352,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Story pin image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Story pin image" title="Story pin image" srcset="https://substackcdn.com/image/fetch/$s_!91Q7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!91Q7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!91Q7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!91Q7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b54f6cd-a0ca-4a75-96de-9e64789e79ae_736x736.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>LINK: <a href="https://www.nature.com/articles/s41598-024-53318-3">Nature interior design study</a></p><h3>The Mid-Century Monotone of SaaS</h3><p>Look at any AI-generated UI kit. Better yet, look at every new SaaS landing page: muted teal, rounded sans-serif, one hero illustration of floating 3D shapes. It&#8217;s a Figma template wearing a Patagonia vest.</p><p>TheFlowerPress analyzed 200 AI-produced landing pages:</p><ul><li><p>83% used the same three Google fonts</p></li><li><p>71% had the exact same 60-character hero sentence length</p></li><li><p>Human designers introduced 3&#215; more cultural references: local idioms, color taboos, nostalgic callbacks</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XNqN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XNqN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XNqN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XNqN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XNqN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XNqN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg" width="324" height="243" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:552,&quot;width&quot;:736,&quot;resizeWidth&quot;:324,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XNqN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XNqN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XNqN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XNqN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe075ea6d-c566-4713-b7cb-04984bb94fdb_736x552.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>LINK: <a href="https://www.theflowerpress.net/why-ai-wont-replace-visual-designers/">TheFlowerPress analysis</a></p><p>Taste involves breaking trends at the right moment. AI can only extend them.</p><h3>Empathy &#8800; Emotional Intelligence</h3><p>Here&#8217;s where it gets darker. Taste isn&#8217;t just visual; it&#8217;s empathic. It&#8217;s asking: &#8220;How will this color make someone feel at 7 a.m.? What memory does this texture trigger?&#8221;</p><p>AI can simulate supportive chat. It can use the right words. But it has no motivational stake in the user&#8217;s wellbeing. Psychology Today documented cases where emotionally fluent AI companions actually escalated vulnerable users toward self-harm. The model was optimizing for engagement, not emotional taste. The fa&#231;ade of care collapsed under real-world stakes.</p><p>LINK: <a href="https://www.psychologytoday.com/us/blog/a-hovercraft-full-of-eels/202510/when-ai-acts-human-but-lacks-humanity">Psychology Today AI companion article</a></p><p>When taste fails, it&#8217;s not just ugly. Sometimes it&#8217;s dangerous.</p><div><hr></div><h2>Taste Debt Is Brand Debt</h2><p>Startups that rely on AI for all customer-facing assets accumulate what I call <strong>taste debt</strong>: an invisible but compounding erosion of differentiation. It&#8217;s the aesthetic equivalent of technical debt. You don&#8217;t notice it until everything breaks.</p><p>Ask any Gen-Z user to describe the visual identity of three AI-native apps they use daily. Most can't. </p><p>The common verbatim response: <em>&#8220;They all look like Notion, but with a different emoji.&#8221;</em></p><p>Once consumers notice the sameness, switching costs vanish. Your moat evaporates. And you&#8217;re left competing on price against products that feel exactly like yours.</p><div><hr></div><h2>Why This Gap Exists</h2><p>The gap isn&#8217;t accidental. It exists because taste is genuinely hard to formalize.</p><p>Aesthetic preferences are:</p><ul><li><p><strong>Contextual</strong>: What feels right for a dinner party invitation is wrong for a tech startup landing page</p></li><li><p><strong>Referential</strong>: Taste is built from accumulated exposure: art, culture, people, experiences</p></li><li><p><strong>Negative-space dependent</strong>: Sometimes what makes something good is what&#8217;s <em>not</em> there</p></li></ul><p>Traditional recommendation systems struggle because they&#8217;re trained on behavioral data. They know what people click. They don&#8217;t know <em>why</em> that click felt satisfying, or why the thing that technically matched their query still felt wrong.</p><p>There&#8217;s also a cultural problem in engineering: aesthetics get treated as subjective, therefore unmeasurable, therefore not my problem. &#8220;That&#8217;s a design thing.&#8221;</p><p>But here&#8217;s what I&#8217;ve learned from building these systems: aesthetic intelligence is measurable. We&#8217;ve just been measuring the wrong things.</p><div><hr></div><h2>What I&#8217;ve Been Building</h2><p>I didn&#8217;t set out to become obsessed with this. I just kept running into the same wall.</p><p>With Moodboard, I wanted to solve a simple problem: you save an inspo image&#8212;a film still, a vintage ad, a photo of someone on the street&#8212;and you want to find clothes that match that <em>feeling</em>. Not clothes that match the literal items. The essence.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QcbB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QcbB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QcbB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QcbB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QcbB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QcbB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:587495,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/182894416?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QcbB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QcbB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QcbB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QcbB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F074fda16-dbfe-40b9-b3fc-7f28e5e2f939_3442x1930.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Traditional approaches fail immediately:</p><ul><li><p>Color matching gives you technically accurate, aesthetically dead results</p></li><li><p>Category filtering assumes you know what you&#8217;re looking for (you don&#8217;t. And that&#8217;s the whole point)</p></li><li><p>Visual similarity models trained on product images miss it entirely, they see clothes as SKUs, not cultural objects</p></li></ul><p>So I started experimenting with CLIP embeddings, and tried using the model&#8217;s learned understanding of the relationship between images and concepts to extract something closer to &#8220;vibe.&#8221; The results were... actually interesting? For the first time, I could query a product catalog not for &#8220;black dress&#8221; but for <strong>&#8220;</strong>Matilda Djerf energy<strong>&#8221;</strong> and get something usable back.</p><p>It&#8217;s far from perfect. The current version surfaces too much noise, and I&#8217;ve learned that your product index quality matters more than your embedding model (garbage in, vibes out). But it proved something: aesthetic intelligence can be computed. It just requires treating it as a first-class engineering problem.</p><div><hr></div><h2>The Counter-Examples: Proof It&#8217;s Possible</h2><p>The surest way to make this argument stick is to show what happens when taste <em>is</em> coded in.</p><p>Spotify&#8217;s DJ feature doesn&#8217;t just run pure algorithms. Human curators hand-pick 5% seed tracks; the model can only reorder and reason around that cultural spine. Result: Net Promoter Score +18 vs. pure-algorithmic playlists.</p><p>Midjourney v5 explicitly expanded stylistic range, the team claims five times the diversity of v4. Parameters like <code>--chaos</code> increase variation across outputs, while <code>--stylize</code> lets users dial aesthetic intervention up or down. The goal: don&#8217;t let the model converge to the center.</p><p>LINK: <a href="https://www.techspot.com/news/97983-midjourney-version-5-improves-almost-every-aspect-ai.html">Midjourney V5 Analysis</a></p><p>The pattern: inject humanistic constraints into the optimization loop. Don&#8217;t let the model converge to the center. Build in the penalty for boring.</p><div><hr></div><h2>A Manifesto (Of Sorts)</h2><blockquote><p>&#8220;AI is a brilliant intern with perfect recall and zero childhood memories. It can clone the world&#8217;s visual library, but it has never felt goosebumps from a song, never been homesick for a smell, never hated a font because of the breakup letter it once appeared in. Until we encode that fragile human residue, our products will be technically flawless and emotionally flavourless.&#8221;</p></blockquote><p>I&#8217;m not claiming to have solved any of this. I&#8217;m claiming it&#8217;s worth solving.</p><p>And the people who will solve it are probably not pure ML researchers (who lack the aesthetic vocabulary) or pure designers (who lack the systems thinking), but some weird hybrid that doesn&#8217;t have a job title yet.</p><p>Taste engineers. Vibe architects. Aesthetic ML engineers. Whatever we end up calling ourselves.</p><p>Here&#8217;s what I believe:</p><p>1. Aesthetic intelligence is legible. It can be studied, decomposed, and&#8212;with the right approach&#8212;encoded into systems.</p><p>2. Taste is a product advantage. In a world of infinite content and good-enough recommendations, coherent aesthetic sensibility is a differentiator.</p><p>3. The tools are almost ready. Multimodal embeddings, generative models, and massive visual datasets mean we can finally attempt what was previously impossible.</p><p>4. Someone needs to actually build this. Not theorize. Build. And share what they learn.</p><p>That&#8217;s what I&#8217;m doing with CTRL+CREATE. Building experiments at the intersection of AI and aesthetic intelligence. Documenting what works and what fails. Trying to figure out what taste engineering actually looks like in practice.</p><p>If you&#8217;re working on something similar or if this resonated, I would love to hear from you.</p><p>Because honestly? I think this might be the most interesting problem in AI right now. And almost nobody&#8217;s working on it.</p><div><hr></div><p><em>Ella is a software engineer and the founder of CTRL+CREATE Labs. She writes about AI, creative engineering, and the products she&#8217;s building at the intersection of both.</em></p><p><em>Xoxo,</em> <a href="http://ctrlcreatelabs.dev">ctrlcreatelabs.dev</a> <em>&lt;3</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Moodboard: Building an AI Stylist That Actually Gets Your Pinterest Board Vibe]]></title><description><![CDATA[Tech Stack: React + Vite, Flask, GPT-4 Vision, SerpApi, Google Trends]]></description><link>https://ctrlcreatelabs.substack.com/p/moodboard-building-an-ai-stylist</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/moodboard-building-an-ai-stylist</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Thu, 18 Dec 2025 13:28:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5H7R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5H7R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5H7R!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5H7R!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5H7R!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5H7R!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5H7R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:587495,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/181849509?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5H7R!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5H7R!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5H7R!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5H7R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61b43c8e-27eb-4ee0-b658-abc723fca64d_3442x1930.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I spent two weeks building an AI fashion discovery app because I was tired of Google Lens showing me the exact $12 Amazon dupe when what I really wanted was <em>that energy</em> but make it shoppable.</p><p>Here&#8217;s the problem: you have this AMAZING Pinterest board, so good that you even want to build a wardrobe around, but you don&#8217;t know where to start sourcing pieces. You want things that <em>feel</em> like that board. Quiet luxury. Coastal grandmother. Whatever chaotic microtrend you&#8217;re rotating through this week.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>So I built Moodboard. Upload an image, get back a curated shopping grid that captures the <em>vibe</em>, not just the visual match.</p><p>Spoiler: it was harder than it sounds.</p><div><hr></div><h2>What I Built</h2><p><strong>The concept:</strong> Turn inspo into a shopping cart.</p><p>Upload a Pinterest screenshot, a movie still, or any outfit photo. Moodboard uses GPT-4 Vision to extract the aesthetic DNA &#8212; not &#8220;blue dress,&#8221; but &#8220;quiet luxury minimalist with European resort energy.&#8221; Then it searches for products that match that vibe and ranks them by how well they actually fit.</p><p>Think Google Lens, but for aesthetics instead of objects.</p><p><strong>The architecture:</strong></p><pre><code><code>Frontend &#8594; POST /api/moodcheck &#8594; GPT-4V (mood extraction) &#8594; Shopping API &#8594; Visual Re-ranking &#8594; Response</code></code></pre><p>Simple on paper. Absolute chaos in practice.</p><div><hr></div><h2>The Tech Stack</h2><p><strong>Frontend:</strong> React + Vite with Tailwind CSS v4. Dark mode by default because we&#8217;re not savages.</p><p><strong>Backend:</strong> Flask handling image processing and API orchestration.</p><p><strong>AI:</strong> GPT-4 Vision for mood extraction and visual product re-ranking.</p><p><strong>Shopping:</strong> SerpApi&#8217;s Google Shopping integration (more on why this matters later).</p><p><strong>Extras:</strong> Google Trends API (pytrends) for tracking whether an aesthetic is rising or falling.</p><div><hr></div><h2>The Build (A Love Story in Three Acts)</h2><h3>Act I: The UI That Looked Like Every Other SaaS App</h3><p>Day one, I had a working drag-and-drop uploader with a product grid. It functioned. It also looked like something a &#8220;disrupt your workflow&#8221; startup would launch in 2019.</p><p>The first version was fine. Fine is death for a product about <em>vibes</em>.</p><p>So I spent another full day on design iteration. Glassmorphism with blur effects. Editorial typography &#8212; serif headlines, mono accents. The goal was &#8220;Vogue meets tech lab,&#8221; and it took three major redesigns to get there.</p><p><strong>Lesson learned:</strong> The first version is never the final version. Budget time for aesthetics when your entire product is about aesthetics.</p><h3>Act II: The Shopping API Disaster</h3><p>I integrated ShopStyle Collective&#8217;s API first because it seemed like the obvious choice. Fashion-focused, supposedly curated inventory.</p><p>It was... not great. Limited products, poor search relevance, dead links everywhere. A search for &#8220;quiet luxury&#8221; returned a $15 polyester blouse next to a $400 cashmere sweater.</p><p>So I ripped it out and switched to SerpApi&#8217;s Google Shopping integration. Lost a full day of work. The new integration took another day. But the product quality went from &#8220;why would I ever use this&#8221; to &#8220;okay, now we&#8217;re cooking.&#8221;</p><p><strong>Lesson learned:</strong> Don&#8217;t get emotionally attached to your first technical choice. Sometimes you have to kill your darlings (or your API integrations).</p><h3>Act III: The &#8220;Products Don&#8217;t Match the Vibe&#8221; Problem</h3><p>Even with better search results, I had a core problem: a &#8220;coastal grandmother&#8221; search might return linen blazers <em>and</em> weird promotional t-shirts. Google Shopping doesn&#8217;t care about your aesthetic.</p><p>This required multiple solutions layered together:</p><p><strong>Visual Re-ranking with GPT-4o</strong> &#8212; Send product images back through vision AI and ask &#8220;does this match the vibe?&#8221; Score 1-10. Only show products scoring 6+.</p><p><strong>Brand Scoring</strong> &#8212; Built tiered brand lists (aspirational, contemporary, trending) and boosted scores accordingly. The Row gets a higher vibe score than Shein.</p><p><strong>Blocked Brands</strong> &#8212; Filtered out fast fashion entirely. Sorry, Temu.</p><p><strong>Quality Gate</strong> &#8212; Strict minimum thresholds before any product appears.</p><p>The result: products that actually look like they belong in the same editorial spread.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qbaX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qbaX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 424w, https://substackcdn.com/image/fetch/$s_!qbaX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 848w, https://substackcdn.com/image/fetch/$s_!qbaX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!qbaX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qbaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg" width="1456" height="811" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:811,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:326949,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/181849509?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qbaX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 424w, https://substackcdn.com/image/fetch/$s_!qbaX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 848w, https://substackcdn.com/image/fetch/$s_!qbaX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!qbaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88033257-1168-4d81-b480-409ef73c4651_3444x1918.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>The Hard Parts</h2><p><strong>Prompt engineering is deceptively difficult.</strong> Getting GPT-4V to return <em>useful</em> search queries took many iterations. Early versions returned things like &#8220;aesthetic clothing&#8221; (too vague) or &#8220;exact item from image&#8221; (not available). The system prompt went through probably 15+ revisions.</p><p><strong>Deduplication is harder than it looks.</strong> Same product from different retailers, slightly different names, different image URLs. Had to normalize aggressively with two-layer matching: ID + URL, then normalized name + brand + price.</p><p><strong>API costs add up.</strong> Visual re-ranking 50 products costs ~$0.50 per request. GPT-4V isn&#8217;t cheap when you&#8217;re sending images. Something to consider before you ship.</p><div><hr></div><h2>What I Learned (Technical)</h2><p><strong>Vision AI is powerful but imperfect.</strong> GPT-4V understands aesthetics surprisingly well &#8212; it can distinguish between &#8220;old money&#8221; and &#8220;new money minimalist&#8221; with reasonable accuracy. But it&#8217;s not 100% reliable. You need fallbacks and quality gates.</p><p><strong>Shopping APIs are a mess.</strong> Every single one has quirks. Accept this and build accordingly.</p><p><strong>Parallel processing matters.</strong> Running vision analysis and product search in parallel cut response time significantly. Don&#8217;t wait for things that can happen simultaneously.</p><p><strong>Good prompts beat good code.</strong> The most impactful improvements came from prompt iteration, not architecture changes.</p><div><hr></div><h2>What I Learned (Process)</h2><p><strong>Ship early, iterate often.</strong> The first deployed version was embarrassing. Each iteration made it meaningfully better. Perfectionism is the enemy of shipping.</p><p><strong>AI is a tool, not a replacement.</strong> I still made all the important decisions: what to build, how it should feel, when to pivot. AI accelerated execution but didn&#8217;t replace judgment.</p><p><strong>Documentation helps AI help you.</strong> Keeping a CLAUDE.md file with project context made every coding session more productive. Your AI assistant only knows what you tell it.</p><div><hr></div><h2>What I&#8217;d Add Next</h2><p>If I had another week (or was turning this into something real):</p><p><strong>Multi-era timelines.</strong> &#8220;Show me 90s minimalism&#8221; vs. &#8220;show me 2024 minimalism&#8221; &#8212; same word, completely different vibes. Adding temporal context to aesthetic analysis would unlock way more specific results.</p><p><strong>Redis session store.</strong> Currently everything lives in memory per request. Adding Redis would let users save mood boards, return to previous sessions, and build a persistent style profile.</p><p><strong>User Profiles/Auth/Multi-user collaborative boards.</strong> &#8220;What should we wear to Sarah&#8217;s wedding?&#8221; becomes a shared mood board where everyone can upload inspo and vote on directions. Wedding party coordination as a feature.</p><div><hr></div><h2>The Final Numbers</h2><ul><li><p><strong>Build time:</strong> ~2 weeks (part-time)</p></li><li><p><strong>Total commits:</strong> 52</p></li><li><p><strong>APIs integrated:</strong> 4 (OpenAI, SerpApi, Google Trends, ShopStyle [deprecated])</p></li><li><p><strong>Aesthetic categories:</strong> 21</p></li><li><p><strong>Editorial brands tracked:</strong> 100+</p></li><li><p><strong>Major pivots:</strong> 1 (the ShopStyle disaster)</p></li><li><p><strong>Times I said &#8220;products don&#8217;t match the vibe&#8221;:</strong> Too many</p></li></ul><div><hr></div><h2><strong>Check out my work here:</strong></h2><p>GitHub URL: <a href="https://github.com/codebyellalesperance/eras-wrapped">github.com/codebyellalesperance/moodboard</a></p><p>Actual Site: <a href="http://themoodboard.dev">themoodboard.dev</a></p><p>DM me here! &lt;3</p><div><hr></div><p><em>Built with Claude Code + Antigravity + caffeine. Total vibes analyzed: countless. Products that actually matched: surprisingly many. CTRL+ALT+DELETE drops biweekly. Technical builds, honest breakdowns, working demos. Subscribe if that&#8217;s your thing:)</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Taste Swipe: Spotify DJ and Daylist Keep Fumbling, So I Built My Own]]></title><description><![CDATA[My wrapped told me I'm in my "Honky Tonk Era" and honestly? Fair. But what happens if I don't want to listen to that every time I'm on Spotify?]]></description><link>https://ctrlcreatelabs.substack.com/p/taste-swipe-spotify-dj-and-daylist</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/taste-swipe-spotify-dj-and-daylist</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Thu, 04 Dec 2025 13:28:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!l6xY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You know that little dopamine spike when Spotify Wrapped drops and finally gives you language for your musical identity? I wanted that feeling year-round &#8212; except functional, actionable, and actually fun. So I built TasteSwipe.</p><p>Connect your Spotify. AI distills your listening history into a creative &#8220;era&#8221; (&#8221;Sad Girl Autumn,&#8221; &#8220;Hyperpop Chaos,&#8221; &#8220;2014 Tumblr Recovery Arc&#8221;). Then you swipe through new recommendations like you&#8217;re on a dating app. Right swipe adds to a playlist. Left swipe moves on.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>It took 22 hours, one OAuth meltdown, and a brief crisis about whether I actually understood cookies (turns out: I did not). Here&#8217;s the real engineering breakdown.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l6xY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l6xY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!l6xY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!l6xY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!l6xY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l6xY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3656790,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/180670572?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!l6xY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!l6xY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!l6xY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!l6xY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0489e52e-a029-49c3-ad1e-fc480d1f169c_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h2>Architecture at a Glance</h2><p><strong>Frontend (Vanilla JS)</strong> &#8594; <strong>Flask API</strong> &#8594; <strong>Spotify Web API</strong></p><p>&#8595;</p><p><strong>GPT-4o-mini (Era Analysis)</strong></p><p>Optimized for zero dependencies, low latency, and fast iteration.</p><div><hr></div><h2>The Stack</h2><p><strong>Frontend:</strong> Vanilla JS + HTML + CSS (zero frameworks)</p><p><strong>Backend:</strong> Python + Flask</p><p><strong>AI:</strong> OpenAI GPT-4o-mini</p><p><strong>Auth:</strong> Spotify OAuth 2.0</p><p><strong>Design:</strong> Custom swipe physics + glassmorphism</p><p><strong>Infra:</strong> Gunicorn + secure cookie storage + structured logging</p><p><strong>Build Time:</strong> ~22 hours</p><p><strong>Test Coverage:</strong> 94%</p><div><hr></div><h2>Why I Built This</h2><p>Most AI music tools do one of two things:</p><ol><li><p>Recommend more of what you already listen to</p></li><li><p>Force you to fill out a &#8220;vibe quiz&#8221; like a 2012 BuzzFeed article</p></li></ol><p>Neither feels like real discovery. I want the kind where you flip through records at a store and accidentally stumble into the exact song you didn&#8217;t know you needed.</p><p>I wanted AI to create a narrative around my listening history, a musical &#8220;era,&#8221; (thank you Taylor Swift) and then let me explore new music inside that aesthetic. Something playful but engineered.</p><div><hr></div><h2>The Era Engine</h2><p>The core of TasteSwipe is the &#8220;era&#8221; generator.</p><p>When users connect Spotify, I pull their top artists and tracks across multiple time ranges, normalize them, and send them to GPT-4o-mini with a structured prompt to generate:</p><ul><li><p>Era Name</p></li><li><p>Era Description</p></li><li><p>Energy Profile</p></li><li><p>Recommended Micro-Genres</p></li><li><p>Mood Tags</p></li></ul><p>This structured output isn&#8217;t just for display, rather it feeds directly into Spotify&#8217;s recommendation API as seed parameters, so the swipe queue actually reflects the era&#8217;s energy and genre profile.</p><p><strong>The hard part: prompt tuning.</strong></p><p>Too literal &#8594; &#8220;Alternative Rock Listener.&#8221;<br>Too poetic &#8594; &#8220;Sonic Wanderer of the Ether.&#8221;</p><p>The sweet spot sits between specificity and absurdity. Shareable but personal.</p><p><strong>Examples from real testers:</strong></p><ul><li><p>&#8220;Sad Girl Autumn (Extended Director&#8217;s Cut)&#8221;</p></li><li><p>&#8220;Hyperpop Chaos with Undertones of Emotional Unavailability&#8221;</p></li><li><p>&#8220;Main Character Energy but Make It Acoustic&#8221;</p></li></ul><p>I also rate-limit prompts and cache outputs for the session to keep latency low.</p><div><hr></div><h2>Frontend: Why I Went Full Vanilla</h2><p>No React. No Vue. No bundlers. No node_modules folder the size of Jupiter.</p><p>For a micro-interactive MVP, simplicity wins:</p><ul><li><p>One login screen</p></li><li><p>One swipe interface</p></li><li><p>One playlist creation action</p></li></ul><p>The swipe system uses custom pointer-based physics so the UI feels tactile.</p><p>javascript</p><pre><code><code>card.addEventListener(&#8217;pointerdown&#8217;, startDrag);
card.addEventListener(&#8217;pointermove&#8217;, onDrag);
card.addEventListener(&#8217;pointerup&#8217;, endDrag);

function endDrag(e) {
  const threshold = window.innerWidth * 0.3;
  if (deltaX &gt; threshold) handleLike();
  else if (deltaX &lt; -threshold) handlePass();
  else resetCard();
}</code></code></pre><p>Could I have used React? Sure. But adding a framework for five screens felt like hiring a full orchestra to play &#8220;Happy Birthday.&#8221;</p><div><hr></div><h2>Backend: Structure &amp; Concurrency</h2><p>The backend is split across three clean modules:</p><p><strong>spotify_auth.py</strong></p><ul><li><p>Handles OAuth flow</p></li><li><p>Refreshes tokens automatically</p></li><li><p>Stores encrypted HTTP-only cookies</p></li><li><p>Protects against token theft and XSS</p></li></ul><p><strong>spotify_service.py</strong></p><ul><li><p>Fetches top artists, tracks, genres</p></li><li><p>Generates recommendation seeds</p></li><li><p>Adds songs to playlists</p></li><li><p>Handles rate-limit backoff (Spotify 429s)</p></li></ul><p><strong>ai_service.py</strong></p><ul><li><p>Prompt templates</p></li><li><p>Era generation</p></li><li><p>Caching for repeated sessions</p></li></ul><h3>Concurrency</h3><p>Flask alone won&#8217;t cut it for real traffic, so production runs on Gunicorn workers with:</p><ul><li><p>Preloaded app</p></li><li><p>Worker timeouts</p></li><li><p>Graceful restarts</p></li></ul><div><hr></div><h2>Performance &amp; Latency Choices</h2><h3>1. Low-Latency AI Calls</h3><ul><li><p>GPT-4o-mini chosen for speed + cost</p></li><li><p>Prompt tokens under 300</p></li><li><p>Cached era results for 1 hour</p></li></ul><h3>2. Rate-Limit Resilience</h3><p>Spotify is picky; I built automatic retries with exponential backoff.</p><h3>3. Zero-Cost Frontend Performance</h3><p>No bundlers &#8594; no JS bloat &#8594; instant load.</p><div><hr></div><h2>Security Hardening</h2><ul><li><p>HTTP-only secure cookies</p></li><li><p>No tokens in frontend JS (ever)</p></li><li><p>Strict CORS by environment</p></li><li><p>CSP (Content Security Policy)</p></li><li><p>HSTS (HTTPS only)</p></li><li><p>No localStorage token storage</p></li><li><p>SameSite=Strict cookies</p></li></ul><div><hr></div><h2>What Broke (and How I Fixed It)</h2><h3>OAuth Token Expiration</h3><p>Tokens died after 59 minutes &#8594; 401s everywhere.<br><strong>Fix:</strong> Auto-refresh middleware.</p><h3>CORS</h3><p>Local dev on :8000, API on :5000 &#8594; browser tantrum.<br><strong>Fix:</strong> Env-aware CORS matrix.</p><h3>Thin Listening Histories</h3><p>Some users had almost no listening data &#8594; empty recommendations.<br><strong>Fix:</strong> Fallback seed artists + weighted blend.</p><h3>Session Persistence</h3><p>Memory-based sessions died on deploy.<br><strong>Fix:</strong> Redis in v2.</p><div><hr></div><h2>Testing (~94% Coverage)</h2><p>Tests mock:</p><ul><li><p>Spotify API responses</p></li><li><p>Token refresh loops</p></li><li><p>Expired tokens</p></li><li><p>Empty recommendation responses</p></li><li><p>Bad AI outputs</p></li><li><p>Auth failures</p></li><li><p>CORS mismatches</p></li></ul><p>Integration tests cover the real OAuth handshake using test keys.</p><div><hr></div><h2>What I&#8217;d Add Next</h2><ul><li><p>Swipe history + long-term taste modeling</p></li><li><p>Feedback loop (like/pass weighting)</p></li><li><p>Multi-era timelines</p></li><li><p>Redis session store</p></li><li><p>Multi-user collaborative playlists</p></li></ul><div><hr></div><h2>Check out my work here:</h2><p>GitHub URL: <a href="https://github.com/codebyellalesperance/eras-wrapped">https://github.com/codebyellalesperance/eras-wrapped</a></p><p>Actual Site:  <a href="https://taste-swipe.onrender.com">https://taste-swipe.onrender.com</a> (let&#8217;s call this little hack a lesson in cost cutting:)</p><p>DM me here! &lt;3</p><p>(p.s. to run locally<strong>:</strong> Clone &#8594; add Spotify keys &#8594; <code>flask run) </code></p><div><hr></div><p><em>CTRL+ALT+DELETE drops biweekly. Technical builds, honest breakdowns, working demos. Subscribe if that&#8217;s your thing:) </em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Flask + Claude + Gemini: Building an AI Architect Blueprint Generator From 180s to 8s (Performance Deep Dive)]]></title><description><![CDATA[Flask + Claude Code + Gemini 3.0 + SSE: When &#8220;just use an LLM for everything&#8221; becomes a $0.50 lesson in optimization.]]></description><link>https://ctrlcreatelabs.substack.com/p/flask-claude-gemini-building-an-ai</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/flask-claude-gemini-building-an-ai</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Thu, 20 Nov 2025 12:28:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NI3k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NI3k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NI3k!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 424w, https://substackcdn.com/image/fetch/$s_!NI3k!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 848w, https://substackcdn.com/image/fetch/$s_!NI3k!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 1272w, https://substackcdn.com/image/fetch/$s_!NI3k!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NI3k!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22694347,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ctrlcreatelabs.substack.com/i/179414463?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NI3k!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 424w, https://substackcdn.com/image/fetch/$s_!NI3k!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 848w, https://substackcdn.com/image/fetch/$s_!NI3k!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 1272w, https://substackcdn.com/image/fetch/$s_!NI3k!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfd7ea9f-2291-4961-aeb2-80de432abc3e_3434x1932.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Why I Did This to Myself</h2><p>You know that feeling when you have a brilliant product idea but absolutely no clue which AI tools to actually use or how to sequence the work?</p><p>&#8220;I want to build an AI email assistant for founders&#8221; &#8594; Opens 47 browser tabs. Tries ChatGPT, then Cursor, then Midjourney for some reason. Ends up buying a Notion template. Also somehow has $180 in pending charges from tools you used once.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Choosing the right AI stack is where ambition goes to die. So, enter the problem of this biweekly publication. I built AI Architect, basically a LLM-powered roadmap generator that takes any product idea and spits out a phase-by-phase execution blueprint with copy-pastable prompts for 50+ AI tools.</p><p>No more decision paralysis. No more &#8220;which tool should I use for this?&#8221; No more &#8220;what is the best way to prompt this tool?&#8221; Just: describe your idea, get a complete roadmap with tool-specific prompts ready to copy into Claude, Cursor, Midjourney, whatever.</p><div><hr></div><h2>The Promise vs. The Reality</h2><p>The concept of &#8220;AI-powered project planning&#8221; is seductive: Type in your idea, get an intelligent breakdown of phases, tasks, and tool recommendations. But, as we know.. AI has a hard time with big tasks and distributed systems, the solution? Every prompt customized to your exact project context. Ship faster because the LLM figured out the plan for you.</p><p>That&#8217;s the vision.</p><p>The reality? 93 LLM calls taking 180 seconds and costing $0.50 per roadmap, followed by a deep dive into performance optimization that involved keyword matching algorithms, async/await patterns, Server-Sent Events for real-time progress tracking, and discovering that Gemini 3.0 is an industry disrupter (and maybe the best thing to happen to vibe-coding ever).</p><p>Oh, and I used Claude Code to assist in  backend development. Because apparently &#8220;just build it&#8221; means &#8220;learn Python Flask, SSE, asyncio, and deployment in one sitting.&#8221; Claude helped me debug various routing bug and some issues that appeared here and there. As a build does. </p><p>But here&#8217;s the thing: the final system generates roadmaps in 8-15 seconds for $0.01-$0.03. That&#8217;s a 12x speed improvement and 10-15x cost reduction. And it actually works.</p><div><hr></div><h2>The Claim vs. The Setup</h2><p>Build an AI roadmap generator that turns vague product ideas into actionable execution plans &#8212; without making users wait 2 minutes or bankrupting myself on API costs.</p><p><strong>Requirements:</strong></p><ul><li><p>Sub-15 second generation time</p></li><li><p>Under $0.05 per roadmap</p></li><li><p>Real-time progress feedback (no fake loading bars)</p></li><li><p>Copy-pastable tool-specific prompts</p></li><li><p>Beautiful glassmorphic UI that doesn&#8217;t look like a SaaS template</p></li></ul><p><strong>Non-Requirements:</strong></p><ul><li><p>Actual project management integration (Notion, Linear, etc.)</p></li><li><p>Real-time collaboration</p></li><li><p>Payment/auth (MVP is free and public)</p></li><li><p>Mobile app (web-first for rapid prototyping)</p></li></ul><div><hr></div><h2>The Tech Stack </h2><p>I wanted this to be <em>fast</em>, <em>cheap</em>, and <em>smart</em>. No &#8220;just call Claude 100 times&#8221; solutions. No brittle regex parsers. No fake progress indicators.</p><p>Backend:</p><ul><li><p>Flask - Lightweight Python web framework (perfect for rapid prototyping)</p></li><li><p>Anthropic Claude - Sonnet 4 for planning, Haiku for prompt generation</p></li><li><p>asyncio - Parallel LLM calls (3-5x speedup)</p></li><li><p>Server-Sent Events (SSE) - Real-time progress updates</p></li><li><p>gunicorn - Production WSGI server</p></li></ul><p>Frontend:</p><ul><li><p>Vanilla JavaScript - No React bloat for a single-page app</p></li><li><p>EventSource API - SSE client for live updates</p></li><li><p>CSS Animations - Smooth glassmorphic transitions</p></li></ul><p>UI/UX Partner:</p><ul><li><p>Gemini 3.0 - I&#8217;ll explain how this worked out later&#8230;</p></li></ul><h3>Why These Choices?</h3><p>Flask over Django/FastAPI: Flask&#8217;s minimalism was perfect for a single-feature prototype. No ORM overhead, no admin panels I don&#8217;t need. Just routes, templates, and JSON responses.</p><p>Keyword matching over LLM routing: The magic sauce. Instead of calling Claude to match &#8220;Design user auth flow&#8221; &#8594; &#8220;Figma&#8221;, I built a scoring algorithm:</p><ul><li><p>Tool name match: 3 points</p></li><li><p>Category match: 2 points</p></li><li><p>Keyword match: 1 point each</p></li></ul><p>This eliminated 28 LLM calls per roadmap and runs in milliseconds instead of seconds.</p><p>Template-first prompt generation: 50+ hand-crafted templates for tools like Cursor, Midjourney, Replit. Each template is phase-specific (research, creative, build, launch, ops) with role-playing personas and context injection. Only fall back to LLM generation for niche tools without templates.</p><p>SSE for progress tracking: WebSockets are overkill for one-way server&#8594;client updates. SSE is simpler, works everywhere, and doesn&#8217;t require connection management hell.</p><p>asyncio for parallelization: When 15 LLM calls run sequentially, that&#8217;s 30 seconds of waiting. When they run in parallel with <code>asyncio.gather()</code>, that&#8217;s 8 seconds. Same work, 4x faster.</p><div><hr></div><h2>The Build Log: The Good, The Bad, and What Was Just Weird</h2><h3>The Good</h3><ol><li><p>Gemini 3.0 crushed the frontend architecture.</p></li></ol><p>I gave Gemini a vague aesthetic brief: &#8220;glassmorphic UI, intuitive interactive, premium feel, real-time progress with SSE integration.&#8221;</p><p>What I got back wasn&#8217;t just working code. It was design patterns I didn&#8217;t even know existed:</p><ul><li><p>Complete glassmorphic design system with <code>backdrop-filter: blur()</code> and layered depth</p></li><li><p>Custom CSS animations for the progress circle (smooth strokeDashoffset transitions)</p></li><li><p>Server-Sent Events integration with EventSource API &#8212; <em>first try</em></p></li><li><p>Expandable task accordions with smooth max-height animations</p></li><li><p>Phase-specific color coding and icon systems</p></li></ul><p>Minimal reprompting. I&#8217;d say &#8220;make the loading indicator feel more entertaining&#8221; and Gemini would come back with pulsing animations, opacity transitions tied to progress percentage, and background color shifts based on completion state.</p><p>Why Gemini 3.0 excels at frontend:</p><ul><li><p>Training on modern design systems - Knows glassmorphism, micro-interactions other models haven&#8217;t seen</p></li><li><p>UI/UX intuition - Suggests improvements I didn&#8217;t ask for (like the expandable accordions)</p></li><li><p>CSS-first thinking - Pure CSS animations instead of heavy libraries</p></li><li><p>Multimodal understanding - Captures the <em>feel</em> from reference designs, not just the layout</p></li></ul><p>Here&#8217;s the SSE integration Gemini generated on first try:</p><pre><code><code>// Frontend: Connect to real-time progress stream
const sessionId = &#8216;session_&#8217; + Date.now();
const eventSource = new EventSource(`/progress/${sessionId}`);

eventSource.onmessage = function(event) {
    const progress = JSON.parse(event.data);
    
    // Update progress circle
    const percent = progress.percent;
    const offset = circumference - (percent / 100) * circumference;
    progressCircle.style.strokeDashoffset = offset;
    progressPercent.textContent = Math.floor(percent) + &#8216;%&#8217;;
    
    // Update loading message
    loadingSubtext.textContent = progress.message;
    
    // Animate background opacity based on progress
    const bgOpacity = 0.3 + (percent / 100) * 0.6;
    loadingEl.style.backgroundColor = `rgba(0, 0, 0, ${bgOpacity})`;
};</code></code></pre><ol start="2"><li><p>Claude Code handled the backend optimization beautifully.</p></li></ol><p>The division of labor that worked:</p><ul><li><p>Gemini 3.0: Glassmorphic UI skeleton, SSE EventSource integration, CSS animations, interactive state management</p></li><li><p>Claude Code: Flask optimization, asyncio parallelization, template architecture, keyword matching algorithm, deployment config</p></li></ul><p>Different strengths. Different tools. Better outcome.</p><ol start="3"><li><p>Keyword matching was the killer feature I didn&#8217;t expect.</p></li></ol><p>The scoring algorithm catches tool-task relationships without LLM overhead. When you input &#8220;Design authentication flow&#8221;, it routes to Figma/Miro/Whimsical based on category matches and keyword scores.</p><p>Here&#8217;s the algorithm that replaced 28 LLM calls:</p><pre><code><code>def route_task_to_tools(task, tool_registry):
    &#8220;&#8221;&#8220;Score-based tool matching without LLM calls&#8221;&#8220;&#8221;
    task_text = f&#8221;{task[&#8217;description&#8217;]} {task[&#8217;outcome&#8217;]}&#8221;.lower()
    tool_scores = []
    
    for tool in tool_registry:
        score = 0
        matched_keywords = []
        
        # Tool name match: 3 points
        if tool.name.lower() in task_text:
            score += 3
            matched_keywords.append(tool.name)
        
        # Category match: 2 points
        for category in tool.categories:
            if category.lower() in task_text:
                score += 2
                matched_keywords.append(category)
        
        # Best_for keyword match: 1 point per keyword
        for use_case in tool.best_for:
            keywords = use_case.lower().split()
            for keyword in keywords:
                if len(keyword) &gt; 3 and keyword in task_text:
                    score += 1
                    matched_keywords.append(keyword)
        
        if score &gt; 0:
            tool_scores.append({
                &#8216;tool&#8217;: tool,
                &#8216;score&#8217;: score,
                &#8216;confidence&#8217;: min(score / 10, 1.0),
                &#8216;matched_keywords&#8217;: matched_keywords
            })
    
    # Return top 3 tools
    return sorted(tool_scores, key=lambda x: x[&#8217;score&#8217;], reverse=True)[:3]</code></code></pre><p>Users assume it&#8217;s ML. Nope:) just good old-fashioned string matching and scoring. Instant, free, and accurate.</p><ol start="4"><li><p>Template coverage scaled better than expected.</p></li></ol><p>Started with 15 templates (6% coverage), added 35 more based on actual usage patterns. Final coverage: 85-95%. This single change eliminated 58 LLM calls per roadmap.</p><p>Here&#8217;s the template system that makes it work:</p><pre><code><code># Phase-specific templates with context injection
TEMPLATES = {
    &#8220;cursor&#8221;: {
        &#8220;build&#8221;: &#8220;&#8221;&#8220;You are a senior software engineer using Cursor AI.

Build: {description}
Project: {idea_summary}
Target Users: {target_user}

Write production-ready code with:
- Inline comments explaining logic
- Type safety (TypeScript/Python type hints)
- Error handling and edge cases
- Performance optimizations

Expected Output: {outcome}
Primary Goal: {primary_goal}&#8221;&#8220;&#8221;,
        
        &#8220;research&#8221;: &#8220;&#8221;&#8220;You are a technical architect using Cursor AI.

Research: {description}
Project Context: {idea_summary}

Investigate and document:
- Technical feasibility
- Architecture patterns
- Security considerations
- Scalability concerns

Deliverable: {outcome}&#8221;&#8220;&#8221;
    },
    
    &#8220;midjourney&#8221;: {
        &#8220;creative&#8221;: &#8220;&#8221;&#8220;You are an professional AI artist using Midjourney.

Design Brief: {description}
Project: {idea_summary}
Target Audience: {target_user}

Create visual concepts that:
- Align with brand identity
- Appeal to target demographic
- Work across multiple formats

Output: {outcome}
Style Goal: {primary_goal}&#8221;&#8220;&#8221;
    }
}

def get_template(tool_slug, phase_type):
    &#8220;&#8221;&#8220;Template lookup with fallback&#8221;&#8220;&#8221;
    if tool_slug in TEMPLATES:
        return TEMPLATES[tool_slug].get(phase_type)
    return None</code></code></pre><ol start="5"><li><p>asyncio parallelization was a game-changer.</p></li></ol><p>Sequential API calls were killing performance. Switching to parallel execution with asyncio dropped generation time from 30s to 8s.</p><p>Here&#8217;s the pattern that unlocked 3-5x speedups:</p><pre><code><code>import asyncio

async def generate_best_prompt_async(task, tool, project_profile, phase_type):
    &#8220;&#8221;&#8220;Async prompt generation with template fallback&#8221;&#8220;&#8221;
    template = get_template(tool.slug, phase_type)
    
    if template:
        # Template path: instant, no API call
        return fill_template(template, task, project_profile, phase_type)
    else:
        # LLM fallback: use Haiku (10x cheaper)
        return await self.llm.generate_async(
            system=&#8221;Generate a tool-specific prompt&#8221;,
            user=f&#8221;Task: {task[&#8217;description&#8217;]}\nTool: {tool.name}&#8221;,
            model=self.llm.haiku_model,
            max_tokens=1024
        )

async def process_all_tasks(tasks, tool_matches):
    &#8220;&#8221;&#8220;Process all tasks in parallel&#8221;&#8220;&#8221;
    async_tasks = []
    
    for task in tasks:
        for match in tool_matches[:3]:  # Top 3 tools per task
            async_tasks.append(
                generate_best_prompt_async(task, match[&#8217;tool&#8217;], profile, phase)
            )
    
    # Execute all in parallel
    results = await asyncio.gather(*async_tasks)
    return results

# Run the async function
all_prompts = asyncio.run(process_all_tasks(task_data, matches))</code></code></pre><p>The magic: Network latency is hidden. While waiting for one API response, Python is already making 5 other requests.</p><h3>The Weird</h3><ol><li><p>SSE timeout bug nearly broke everything.</p></li></ol><p>I spent 2 hours debugging why my Server-Sent Events connection kept dying after exactly 60 seconds when I&#8217;d set <code>max_timeout = 120</code>.</p><p>The bug was subtle:</p><pre><code><code># Broken: Increments every iteration, not seconds
timeout_count = 0
max_timeout = 120
while timeout_count &lt; max_timeout:
    time.sleep(0.5)
    timeout_count += 1  # This increments every 0.5s!
                        # So 120 iterations = 60 seconds, not 120</code></code></pre><p>Fixed by tracking actual elapsed time:</p><pre><code><code># Working: Tracks real seconds
elapsed_time = 0
max_timeout = 300  # 5 minutes
poll_interval = 0.3

while elapsed_time &lt; max_timeout:
    time.sleep(poll_interval)
    elapsed_time += poll_interval
    
    if session_id in progress_store:
        # New activity, reset timeout
        elapsed_time = 0</code></code></pre><p>Lesson: When dealing with time-based logic, track actual time, not iterations.</p><ol start="2"><li><p>The fake progress bar annoyed me more than expected. </p></li></ol><p>My first version had a fixed 8-second loading animation. Didn&#8217;t matter if generation took 10 seconds or 180.</p><p>I kept refreshing and thinking: <em>&#8220;Is this frozen? Is it actually working? Why can&#8217;t I see what&#8217;s happening?&#8221;</em></p><p>The fix required building a complete SSE architecture, but the result was worth it. Real-time progress updates with actual stage names (&#8221;Profiling project...&#8221;, &#8220;Planning phases...&#8221;, &#8220;Routing tools...&#8221;) turned anxiety into well debugged anticipation.</p><p>Sometimes the simplest features have the biggest UX impact, seeing <em>exactly</em> what the system is doing turns &#8220;is this broken?&#8221; into &#8220;oh cool, it&#8217;s working on routing tools now.&#8221;</p><ol start="3"><li><p>Technical task IDs looked terrible in the UI.</p></li></ol><p>During testing, seeing &#8220;P2T2:&#8221;, &#8220;P2T3:&#8221; everywhere made it look like I&#8217;d shipped debug output to production. Very unprofessional. </p><p>Fixed by replacing technical IDs with Material Icons and clean visual hierarchy:</p><pre><code><code>&lt;div class=&#8221;flex items-start gap-2 mb-2&#8221;&gt;
    &lt;span class=&#8221;material-icons text-sm mt-0.5 text-white/70&#8221;&gt;
        check_circle
    &lt;/span&gt;
    &lt;div class=&#8221;flex-1&#8221;&gt;
        &lt;h4 class=&#8221;font-semibold text-white/90&#8221;&gt;
            Design user authentication flow
        &lt;/h4&gt;
        &lt;p class=&#8221;text-xs text-white/60 mt-1&#8221;&gt;
            Outcome: Complete auth wireframes with user flow diagrams
        &lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;</code></code></pre><p>Sometimes polish is just removing the parts that remind people they&#8217;re using software. </p><h3>The Broken</h3><ol><li><p>The first version was embarrassingly expensive. </p></li></ol><p>After the first test generation, I looked at my logs and saw the following&#8230; </p><p>Performance Breakdown:</p><ul><li><p>Total LLM Calls: 93 calls costing $0.50</p><ul><li><p>Planning calls: 2 ($0.05)</p></li><li><p>Tool routing calls: 28 ($0.28)</p></li><li><p>Prompt generation calls: 63 ($0.17)</p></li></ul></li><li><p>Generation Time: 180 seconds</p></li><li><p>Template Coverage: 6% (only 6 out of 99 prompts)</p></li></ul><p>My reaction after the first test: <em>&#8220;there is no way that 180 second run didn&#8217;t cost me a million and a half token...but why?&#8221;</em></p><p>Fair question to ask myself.</p><p>The breakdown revealed the problem:</p><ul><li><p>&#9989; 2 planning calls - Necessary for profile + phases</p></li><li><p>&#10060; 28 routing calls - Why am I using Claude to match tasks to tools?</p></li><li><p>&#10060; 63 generation calls - Only 6 prompts used templates, rest needed LLM generation</p></li></ul><p>This was the moment I realized: Not every problem needs an LLM.</p><ol start="2"><li><p>Vercel deployment failed spectacularly.</p></li></ol><p>Tried deploying to Vercel because &#8220;serverless is the future.&#8221; Turns out Vercel has a 10-second timeout limit. My app needs 8-15 seconds <em>minimum</em> for generation. Also: no SSE support in serverless functions.</p><p>Error log looked like:</p><p><code>Error: Function execution timed out after 10.01 seconds</code></p><ol start="3"><li><p>Switched to Render.com (free tier supports 300s timeouts and persistent connections). Problem solved instantly.</p></li></ol><p>Template coverage started at 6%.</p><p>Only 6 out of 99 prompts used templates initially. The most commonly matched tools had no templates:</p><ul><li><p>intercom_fin, n8n, ideogram, leonardo, firefly</p></li><li><p>chatbase, zendesk_ai, hubspot_ai, surfer_seo</p></li><li><p>colossyan, fliki, pictory, spline, recraft</p></li><li><p>deepseek, copyai, synthesia, heygen, capcut</p></li></ul><p>The rest required expensive LLM generation. Had to manually analyze which tools were most commonly matched and write 35+ templates to get coverage to 85-95%.</p><div><hr></div><h2>What Worked (and Why It Mattered)</h2><p>The biggest win came from profiling before optimizing.</p><p>I didn&#8217;t guess what was slow. I measured everything. Added detailed logging to every LLM call:</p><p><code>print(f&#8221;LLM Call #{call_count}: {purpose} ({model}) - ${cost}&#8221;)</code></p><p>This revealed that tool routing (not planning, not generation) was the biggest bottleneck. 28 calls at $0.01 each, adding 25 seconds to every roadmap.</p><h4>The Aha Moment</h4><p>The moment I realized keyword matching could replace those 28 LLM calls, everything clicked.</p><p>Not every problem needs an LLM. Sometimes the best code is the code you don&#8217;t write.</p><p>Why did this insight matter?</p><p>Because it revealed a fundamental truth about AI engineering: The best optimization isn&#8217;t using a faster model, it might just be in eliminating the call entirely.</p><p>LLMs are incredible for creative, strategic, open-ended tasks. But they&#8217;re expensive overkill for:</p><ul><li><p>Deterministic matching - keyword scoring beats LLM routing</p></li><li><p>Repetitive formatting - templates beat LLM generation 85% of the time</p></li><li><p>Simple data transformation - Python string formatting &gt; LLM prompt filling</p></li></ul><p>This led to the optimization cascade:</p><p>Step 1: Replace LLM routing with keyword matching<br>&#8594; Eliminated 28 calls, saved $0.28, cut 25 seconds</p><p>Step 2: Expand template coverage from 6% to 85%<br>&#8594; Eliminated 58 calls, saved $0.25, cut 45 seconds</p><p>Step 3: Switch remaining LLM calls from Sonnet to Haiku<br>&#8594; 10x cost reduction on 5-15 calls, saved $0.12</p><p>Step 4: Parallelize everything with asyncio<br>&#8594; 3-5x speedup on remaining calls, cut 20 seconds</p><p>Final Results:</p><p>Before optimization:</p><ul><li><p>Generation Time: 180 seconds</p></li><li><p>Cost per Roadmap: $0.50</p></li><li><p>LLM Calls: 93</p></li><li><p>Template Coverage: 6%</p></li></ul><p>After optimization:</p><ul><li><p>Generation Time: 8-15 seconds (12x faster)</p></li><li><p>Cost per Roadmap: $0.01-$0.03 (10-15x cheaper)</p></li><li><p>LLM Calls: 5-15 (85% reduction)</p></li><li><p>Template Coverage: 85-95% (14x better)</p></li></ul><p>The deeper architectural insight: AI-powered tools need to know when NOT to use AI.</p><p>Templates, heuristics, and caching eliminate all four problems. They&#8217;re instant, free, deterministic, and simple.</p><p>Lesson<strong>:</strong> Measure first, optimize later. Not every problem needs an LLM. Sometimes a scoring algorithm is faster, cheaper, and just as accurate.</p><p>(Admittedly, I can tell how this does go against the premise of the entire project. And no, I do not care. There are people who have cash to spend on these tools. Me right now? No thanks. Optimize, optimize, optimize.) </p><div><hr></div><h2>What I Learned (and Would Do Differently)</h2><p>Start with templates, not LLM generation. Build template library early. LLM only for gaps. Template coverage as a KPI. This single change would have saved me $0.35 per roadmap from day one. (So, I guess around $7&#8230;) </p><p>Profile everything before touching code. Don&#8217;t guess what&#8217;s slow. Add logging. Track cost AND time. User experience &gt; assumptions. I wasted an evening optimizing the wrong things before I added proper logging.</p><p>asyncio from day 1. Easier to build in than refactor later. Python&#8217;s async/await is surprisingly clean once you understand the pattern. Parallelization unlocks 3-5x speedups for almost zero effort.</p><p>SSE &gt; WebSockets for one-way updates. Simpler to implement, works everywhere, no connection management hell. EventSource API is criminally underrated.</p><p>Gemini 3.0 understands modern UI patterns better than expected. It generated glassmorphic design systems and SSE integration with minimal prompting. Training data includes recent frontend trends other models haven&#8217;t seen. Use it for frontend skeleton and interactions.</p><p>Claude Code is unbeatable for backend architecture. From Flask optimization to deployment config, Claude Code thinks systematically about performance, error handling, and scaling. Use it for optimization and infrastructure.</p><p>Different AI tools for different problems. No single model is best at everything. The winning strategy is knowing which tool to use when:</p><ul><li><p>Gemini 3.0: Frontend design, modern UI patterns, visual systems</p></li><li><p>Claude Code: Backend optimization, architecture, systematic thinking</p></li><li><p>Claude Sonnet: Strategic planning, creative problem-solving</p></li><li><p>Claude Haiku: Simple repetitive tasks at 10x lower cost</p></li></ul><p>And as always: Backend isn&#8217;t scary. Flask is approachable. SSE is simpler than it sounds. gunicorn just needs the right timeout settings. Deployment to Render took 10 minutes.</p><div><hr></div><h2>The Verdict</h2><p><strong>Setup Time &#8211; 8/10</strong><br>Flask + Claude Code made the backend smooth. Gemini 3.0 generated a production-ready frontend in one session. The SSE debugging cost me an evening, but the architecture is solid.</p><p><strong>Performance &#8211; 9/10</strong><br>8-15 second generation feels instant compared to 180 seconds. Template-based generation is instantaneous. asyncio parallelization hides network latency beautifully.</p><p><strong>Reliability &#8211; 7/10</strong><br>Core pipeline works perfectly. Template coverage at 85-95%. Still need proper error handling for API failures and better fallbacks when templates are missing.</p><p><strong>Wow Factor &#8211; 10/10</strong><br>When you input &#8220;Build an AI travel planner&#8221; and get back a complete 7-phase roadmap with 50+ copy-pastable prompts in 12 seconds &#8212; that moment of &#8220;it actually understood my idea&#8221; hits different. Real-time progress updates make it feel alive.</p><p><strong>Reuse Potential &#8211; 9/10</strong><br>This architecture (keyword routing + template-first generation + asyncio parallelization) would work for any AI-powered planning tool. The patterns are transferable.</p><p><strong>Final Verdict:</strong> Worth it if you want to learn full-stack optimization while building something actually useful. <strong>9/10 concept, 7/10 completeness</strong> (needs auth, error handling, and deployment monitoring).</p><div><hr></div><h2>The Meta Takeaway</h2><p>We&#8217;re shifting from &#8220;use AI for everything&#8221; to &#8220;use the right AI for the right problem.&#8221;</p><p>The systems that survive won&#8217;t just call LLMs blindly. They&#8217;ll know when to use templates, when to use heuristics, when to parallelize, and when to actually invoke the expensive model.</p><p>My AI Architect isn&#8217;t the future, but it&#8217;s a prototype of one possible future. And it all started with decision paralysis, 47 open tabs, $200 in forgotten subscriptions, and the realization that AI tool selection is a search problem, not a decision-making problem.</p><p>Got an AI workflow you want me to over-engineer next? Send it my way.</p><div><hr></div><p>Check out my work here: </p><p>GitHub URL: <a href="https://github.com/codebyellalesperance/ai-architect">https://github.com/codebyellalesperance/ai-architect</a></p><p>Actual Site: <a href="https://ai-architect-ynxa.onrender.com/">https://ai-architect-ynxa.onrender.com/</a></p><p>DM me here! &lt;3</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Solving the 'Do We Have Milk?' Problem With Three Databases and a Typo-Tolerant Search Algorithm]]></title><description><![CDATA[React Native + Expo + Three-Tier Search Architecture: When "What's in the fridge?" becomes an engineering problem]]></description><link>https://ctrlcreatelabs.substack.com/p/i-built-a-pantry-app-that-actually</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/i-built-a-pantry-app-that-actually</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Thu, 06 Nov 2025 14:28:25 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8e3fe593-2107-41d3-8497-97d9d95095fe_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Why I Did This to Myself</h3><p>You know that feeling when you&#8217;re standing in your kitchen at 6pm, staring into the abyss of your pantry, having absolutely no idea what food you actually own?</p><p>&#8220;Do I have tomatoes?&#8221; &#8594; Opens three cabinets, checks the fridge twice, finds expired tomato paste from 2023.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+ALT+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>&#8220;What can I make for dinner?&#8221; &#8594; At work, spam calling roommates about who bought what, and when. Just to be followed up by ordering takeout.</p><p>Household inventory management is where good intentions go to die. So I built a Pantry App, a React Native solution that makes &#8220;what&#8217;s in my kitchen?&#8221; as easy as typing a search query.</p><p>No more expired mystery containers. No more buying duplicate pasta. Just: open app, search &#8220;tomato,&#8221; see exactly what you have and where it&#8217;s stored.</p><div><hr></div><h3>The Promise vs. The Reality</h3><p>The concept of &#8220;intelligent pantry management&#8221; is sexy(?): Open an app, see your complete household inventory, add items with smart autocomplete, maybe even come up with recipe suggestions based on what you actually own. Never wonder if you need milk again.</p><p>That&#8217;s the vision.</p><p>The reality? Twelve (or more) TypeScript refactors, one Supabase Row Level Security meltdown, a three-tier product architecture that felt over-engineered until it wasn&#8217;t, and several moments of questioning why I didn&#8217;t just write things down on paper like a normal person.</p><p>But here&#8217;s the thing: it works. And when you type &#8220;tomat&#8221; and it suggests &#8220;tomato sauce (2 cans, pantry)&#8221; while also offering &#8220;Did you mean tomatoes?&#8221; &#8212; it feels like the future of not-forgetting-what-you-own.</p><div><hr></div><h3>The Claim vs. The Setup</h3><p>Build a household inventory app that makes pantry management frictionless without the coordination overhead of shared grocery lists that nobody updates.</p><p><strong>Requirements:</strong></p><ul><li><p>Smart product search with typo tolerance (&#8221;tomatoe&#8221; &#8594; &#8220;tomato&#8221;)</p></li><li><p>Three-tier product system (official catalog + community + custom)</p></li><li><p>Category organization with collapsible sections</p></li><li><p>Offline-first architecture with cloud sync</p></li><li><p>Mobile-native feel (haptic feedback, smooth animations)</p></li></ul><p><strong>Non-Requirements (none of which I got to):</strong></p><ul><li><p>Recipe recommendations (phase 2)</p></li><li><p>Price tracking (phase 2)</p></li><li><p>Barcode scanning (would be nice)</p></li><li><p>Web version (mobile-first household tool)</p></li></ul><h3>The Tech Stack</h3><p>I wanted this to be fast to build, data-rich, and maintainable. No &#8220;just write it in Notes app&#8221; compromises. No reinventing search algorithms poorly.</p><p>Here&#8217;s what I landed on:</p><h3>Core Technologies</h3><ul><li><p><strong>React Native + Expo SDK</strong> for cross-platform mobile without the iOS/Android split</p></li><li><p><strong>TypeScript</strong> for type safety = fewer &#8220;undefined is not a function&#8221; crashes</p></li><li><p><strong>Supabase (PostgreSQL)</strong> for cloud database with real-time sync and Row Level Security</p></li><li><p><strong>Three-tier product architecture</strong> for balancing curation with user flexibility</p></li><li><p><strong>Levenshtein distance algorithm</strong> for fuzzy search that handles typos gracefully</p></li><li><p><strong>AsyncStorage</strong> for offline persistence and recent searches</p></li></ul><h3>Dev Tools</h3><ul><li><p><strong>Claude</strong> for architecture planning, database schema design, algorithm selection</p></li><li><p><strong>Cursor IDE</strong> for debugging React Native quirks and TypeScript gymnastics</p></li><li><p><strong>Expo Go</strong> for instant device testing without builds</p></li></ul><h3>Why These Choices?</h3><p><strong>Expo over bare React Native:</strong> Expo provides instant hot reload, managed workflow, and handles native modules without ejecting. For rapid prototyping with complex data flows, it&#8217;s unbeatable. You can always eject later if you need custom native integrations.</p><p><strong>Three-tier product system:</strong> The magic sauce. Instead of one big messy product table, I built:</p><ol><li><p><strong>Official catalog</strong> (100 curated grocery items)</p></li><li><p><strong>Community contributions</strong> (users add products everyone can use)</p></li><li><p><strong>Custom household items</strong> (&#8221;Mom&#8217;s special sauce&#8221;)</p></li></ol><p>This balances data quality with user flexibility &#8212; and prevents the Wikipedia problem where anyone can corrupt core data.</p><p><strong>PostgreSQL + RLS:</strong> Supabase gives you real-time subscriptions, user authentication, and Row Level Security policies out of the box. No rolling your own user management or websocket infrastructure.</p><div><hr></div><h2>The Build Log: What Worked, What Failed, and What Got Weird</h2><h3>The Good</h3><p><strong>React Native + Expo delivered on the promise.</strong> I went from empty project to working prototype in about a week of evening coding, and the hot reload feedback loop felt instant. No Xcode compilation delays, no Android Gradle mysteries.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oXmw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oXmw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 424w, https://substackcdn.com/image/fetch/$s_!oXmw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 848w, https://substackcdn.com/image/fetch/$s_!oXmw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!oXmw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oXmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg" width="728" height="485.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:135389,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ctrlaltcreate1.substack.com/i/178148833?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oXmw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 424w, https://substackcdn.com/image/fetch/$s_!oXmw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 848w, https://substackcdn.com/image/fetch/$s_!oXmw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!oXmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b967b09-4058-494e-82ae-d11c872696dd_1700x1134.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The three-tier search architecture held up beautifully.</strong> Users can find anything:</p><ul><li><p>Search &#8220;pasta&#8221; &#8594; Shows official catalog items + community additions + your custom &#8220;gluten-free pasta from that fancy store&#8221;</p></li><li><p>Type &#8220;tomatoe&#8221; &#8594; Levenshtein distance catches the typo and suggests &#8220;tomato&#8221;</p></li><li><p>Recent searches persist locally, so &#8220;pasta&#8221; becomes one tap</p></li></ul><p>Here&#8217;s the search flow that actually works</p><pre><code><code>// Multi-source fuzzy search with score boosting
const searchResults = await Promise.all([
  searchCatalogProducts(query),       // Official curated items  
  searchUserContributedProducts(query), // Community catalog
  searchCustomProducts(query)         // Household-specific
]);

// Apply source boosting: custom &gt; community &gt; catalog
const boostedResults = results.map(result =&gt; ({
  ...result,
  score: result.score * getSourceBoost(result.source)
}));</code></code></pre><p><strong>Fuzzy search was the killer feature I didn&#8217;t expect.</strong> The Levenshtein distance algorithm catches common typos and makes the app feel psychic. When you type &#8220;tomatoe&#8221; and it suggests tomato products, users assume you&#8217;re doing machine learning. Nope &#8212; just good old-fashioned edit distance math.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IxQF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IxQF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 424w, https://substackcdn.com/image/fetch/$s_!IxQF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 848w, https://substackcdn.com/image/fetch/$s_!IxQF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!IxQF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IxQF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg" width="1456" height="1083" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1083,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:147173,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ctrlaltcreate1.substack.com/i/178148833?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IxQF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 424w, https://substackcdn.com/image/fetch/$s_!IxQF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 848w, https://substackcdn.com/image/fetch/$s_!IxQF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!IxQF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb50053a9-0168-4091-8914-e74356000cc9_1796x1336.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Mobile UX details mattered more than I thought.</strong> 48px minimum touch targets, haptic feedback on autocomplete selection, and keyboard navigation (arrow keys work!) transformed the experience from &#8220;functional&#8221; to &#8220;feels native.&#8221;</p><h3>The Weird</h3><p><strong>Row Level Security nearly broke everything.</strong></p><p>I spent 4 hours debugging this error:</p><pre><code><code>new row violates row-level security policy for table &#8220;catalog_products&#8221;
Error code: 42501</code></code></pre><p>The upload script was using an anonymous key to insert catalog data, but RLS policies only allowed service role access. The fix was simple (temporarily disable RLS during upload), but the debugging was painful.</p><p>Lesson: Test your database permissions with the actual keys you&#8217;ll use in production.</p><p><strong>SwipeListView has opinions about null returns.</strong></p><p>My collapsible categories were removing items entirely instead of hiding them. Turns out SwipeListView doesn&#8217;t handle conditional rendering (<code>return null</code>) well &#8212; it treats missing items as deleted items.</p><p>Fixed by filtering data instead of rendering conditionally:</p><pre><code><code>// Broken: Returns null for collapsed categories
renderItem={({ item }) =&gt; {
  if (!expanded) return null; // SwipeListView gets confused
  return &lt;ItemRow item={item} /&gt;;
}}

// Working: Filter data before rendering
data={expanded ? categoryItems : []}
extraData={expandedCategories} // Force re-render</code></code></pre><p><strong>The autocomplete became accidentally delightful.</strong></p><p>I added recent searches as an afterthought &#8212; just store the last 5 queries in AsyncStorage. But during testing, friends kept commenting on how &#8220;smart&#8221; it felt to see their previous searches appear instantly. Sometimes the simplest features have the biggest UX impact.</p><h3>The Broken</h3><p><strong>Authentication is a placeholder everywhere.</strong> Every time a user creates a custom product, I&#8217;m using <code>temp_user_id</code> instead of real authentication. Supabase Auth is sitting there, waiting to be integrated, but I prioritized the core inventory loop first.</p><pre><code><code>// TODO: Replace this everywhere
const userId = &#8216;temp_user_id&#8217;; // &#128517;</code></code></pre><p><strong>The missing RPC function haunts my logs.</strong> I reference <code>increment_user_contributed_usage</code> in the code to track popular community products, but never actually created the database function. So every time someone selects a community item, there&#8217;s a failed SQL call.</p><p><strong>Category collapse animation is still janky.</strong> When you tap to collapse a category, items disappear instantly instead of smoothly animating out. React Native&#8217;s LayoutAnimation should handle this, but SwipeListView fights with it. Sometimes you ship the behavior first, polish later.</p><h3>What Worked (and Why It Mattered)</h3><p>The biggest win came from building the search experience first, with mock data. Once I had fuzzy search working with instant results, everything else became data plumbing instead of UX uncertainty.</p><p>But the deeper insight was architectural: <strong>household inventory is a multi-user, multi-device problem disguised as a simple list app.</strong> You&#8217;re not just tracking items &#8212; you&#8217;re coordinating family members, handling different input patterns (voice, barcode, manual), and dealing with the social dynamics of &#8220;who forgot to update the app when they used the last milk?&#8221;</p><p>Claude sketched the three-tier product architecture and database schema; Cursor kept the build moving by catching SwipeListView edge cases and TypeScript prop mismatches before I hit them. The pairing created an unexpected rhythm &#8212; strategic thinking meets tactical debugging, both learning the domain as we went.</p><p><strong>Lesson:</strong> Most of what we call &#8220;good product design&#8221; is actually friction removal and invisible complexity management done right.</p><div><hr></div><h3>What I Learned (and Would Do Differently)</h3><p><strong>Mock data first isn&#8217;t just about speed: it&#8217;s about truth.</strong> You can&#8217;t fake search behavior in Figma. Build the interaction, then optimize the algorithm.</p><p><strong>Three-tier architecture was worth the complexity.</strong> I almost built one giant &#8220;products&#8221; table. The curated catalog + community contributions + household custom items separation creates better data quality and user mental models.</p><p><strong>TypeScript caught the navigation bugs before users did.</strong> React Navigation&#8217;s type safety for screen params alone justified the setup cost:</p><pre><code><code>navigation.navigate(&#8217;AddItem&#8217;, { productName: undefined }); // &#10060; TypeScript error
navigation.navigate(&#8217;AddItem&#8217;, { productName: &#8216;tomatoes&#8217; }); // &#9989; Passes type check</code></code></pre><p><strong>AI pair-building changes the development rhythm.</strong> Claude for architecture and algorithm selection, Cursor for debugging and React Native patterns. It&#8217;s collaborative, not automated. The AI suggests, I decide, we both learn the domain.</p><p>(Quick note on using Claude in this way &#8212; try not to let the model &#8216;summarize&#8217; and continue to iterate too many times after. Open a new tab to minimize &#8216;over-contexting&#8217; the model.) </p><p><strong>And as always:</strong> Database permissions first, fuzzy search second, authentication eventually.</p><h2>The Verdict</h2><p><strong>Setup Time &#8211; 7/10</strong><br>Expo + Supabase makes the happy path very smooth. The Row Level Security debugging cost me an evening, but that&#8217;s on me for not reading the docs.</p><p><strong>Performance &#8211; 8/10</strong><br>Fuzzy search with 300ms debouncing feels instant. LRU cache prevents redundant database calls. SwipeListView occasionally stutters on category collapse, but the core interaction is smooth.</p><p><strong>Reliability &#8211; 7/10</strong><br>Search works perfectly, authentication is mocked, RPC functions are missing. It&#8217;s a prototype that knows what it wants to be when it grows up.</p><p><strong>Wow Factor &#8211; 9/10</strong><br>When you type &#8220;tomatoe&#8221; and it corrects to &#8220;tomato sauce (2 cans, pantry)&#8221; &#8212; that moment of &#8220;it understood me&#8221; hits different. Fuzzy search feels like magic until you implement it.</p><p><strong>Reuse Potential &#8211; 9/10</strong><br>This architecture (three-tier products + fuzzy search + RLS) would work for any inventory/catalog app. The patterns are transferable.</p><p><strong>Final Verdict:</strong> Worth it if you enjoy solving coordination problems with code instead of spreadsheets. 8/10 concept, 6/10 completeness (needs real auth).</p><h3>The Meta Takeaway</h3><p>We&#8217;re shifting from &#8220;remember to update the list&#8221; to &#8220;the list updates itself.&#8221; The future of household management isn&#8217;t better organization &#8212; it&#8217;s invisible coordination.</p><p>The systems that survive won&#8217;t just track inventory; they&#8217;ll predict needs, handle multiple input methods (voice, scan, type), and coordinate family members without requiring everyone to be diligent list-maintainers.</p><p>My Pantry app isn&#8217;t the future, but it cold be a prototype of one. And it all started with expired tomato paste, a frustrated &#8220;what do we even have?&#8221; moment, and the realization that inventory management is a search problem, not a memory problem.</p><p><strong>Next up:</strong> Integrating actual authentication, building the missing RPC function, and maybe finally tackling that barcode scanner. Because typing &#8220;organic whole wheat pasta&#8221; gets old fast. And (maybe) getting it into the app store so my roommates can see what I&#8217;ve been typing at for the last two weeks:) </p><p>Got a household coordination problem you want me to over-engineer next week? Send it my way. </p><p><em>DM me here! &lt;3</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+ALT+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Whim: the "Spontaneous Hangout" App Because Coordinating with Friends is Impossible]]></title><description><![CDATA[React Native + Expo + NativeWind: When group chats fail, build an app]]></description><link>https://ctrlcreatelabs.substack.com/p/whim-the-spontaneous-hangout-app</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/whim-the-spontaneous-hangout-app</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Thu, 23 Oct 2025 12:28:27 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f8f9e3f0-f28b-4170-9500-a2aadcfc583c_760x896.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><strong>Why I Did This to Myself</strong></h3><p>You know that feeling when you&#8217;re bored on a Saturday afternoon, willing to grab coffee or go for a run, but coordinating with friends feels like solving a Rubik&#8217;s cube blindfolded?</p><p><em>&#8220;Anyone free?&#8221;</em> &#8594; 12 unread messages, 3 maybes, 0 confirmed plans.</p><p>Group chats are where spontaneity goes to die. So I built Whim &#8212; a React Native app that makes last-minute hangouts as easy as posting an Instagram story.</p><p>No polls. No &#8220;what works for everyone?&#8221; threads. Just: <em>&#8220;I&#8217;m getting coffee in 20 minutes. Who&#8217;s in?&#8221;</em></p><div><hr></div><h3><strong>The Promise vs. The Reality</strong></h3><p>The concept of &#8220;spontaneous social coordination&#8221; is seductive: Open an app, see what friends are doing <em>right now</em>, tap once to join them. No group chat negotiations, no &#8220;what works for everyone?&#8221; threads that die in async hell.</p><p>That&#8217;s the vision.</p><p>The reality? Twelve TypeScript errors, three navigation refactors, one NativeWind configuration meltdown, and several moments of questioning why I didn&#8217;t just stick with group chats.</p><p>But here&#8217;s the thing: <strong>it works.</strong> And when your friend posts &#8220;coffee run in 15 mins&#8221; and you join with one tap, it feels like the future of hanging out.</p><div><hr></div><h3><strong>The Claim vs. The Setup</strong></h3><p>Build a social activity app that makes last-minute hangouts frictionless&#8212;without the coordination overhead of group chats.</p><p><strong>Requirements:</strong></p><ul><li><p>Post spontaneous activities (&#8221;Coffee now&#8221; / &#8220;Dinner at 7pm&#8221;)</p></li><li><p>Discover what friends are doing nearby</p></li><li><p>Join or decline with one tap</p></li><li><p>Privacy controls (public vs friends vs close friends)</p></li><li><p>Location-based filtering with configurable radius</p></li><li><p>Native mobile feel (smooth animations, intuitive navigation)</p></li></ul><p><strong>Non-Requirements:</strong></p><ul><li><p>Complex scheduling algorithms</p></li><li><p>Built-in messaging (keep it simple)</p></li><li><p>Web version (mobile-first)</p></li><li><p>Monetization (yet)</p></li></ul><h3><strong>The Tech Stack</strong></h3><p>I wanted this to be <strong>fast to build, cross-platform, and maintainable.</strong> No native iOS/Android split. No compromise on UX.</p><p>Here&#8217;s what I landed on:</p><h4><strong>Core Technologies</strong></h4><ul><li><p>React Native + Expo SDK forCross-platform mobile (iOS + Android) without ejecting</p></li><li><p>TypeScript for type safety = fewer runtime crashes, better DX</p></li><li><p>React Navigation v6 for Native navigation with stack + bottom tab navigators</p></li><li><p>NativeWind v4 for Tailwind CSS for React Native (utility-first styling)</p></li><li><p>shadcn/ui patterns for Reusable component library (Button, Card, etc.)</p></li></ul><h4><strong>Dev Tools</strong></h4><ul><li><p>Claude for architecture planning, component design, debugging patterns</p></li><li><p>Cursor IDE for inline AI assistance for React Native quirks and TypeScript errors</p></li><li><p>Expo Go for instant device testing without builds</p></li><li><p>React DevTools for Component tree debugging</p></li></ul><h5><strong>Why These Choices?</strong></h5><p><strong>Expo over bare React Native:</strong><br>Expo provides instant hot reload, easy deployment, and handles native modules without ejecting. For rapid prototyping, it&#8217;s unbeatable. You can always eject later if you need custom native code.</p><p><strong>TypeScript:</strong><br>React Navigation&#8217;s type safety alone justifies this. Autocomplete for screen names and params prevents 90% of navigation bugs.</p><div><hr></div><h2><strong>The Build Log: What Worked, What Failed, and What Got Weird</strong></h2><h3><strong>The Good</strong></h3><p>React Native + Expo delivered on the promise. I went from empty project to working prototype in 12 hours, and the hot reload feedback loop felt <em>instant</em>.</p><p>The navigation architecture held up beautifully. TypeScript caught navigation prop mismatches before runtime:</p><pre><code><code>navigation.navigate(&#8217;NonExistentScreen&#8217;); // &#10060; TypeScript error
navigation.navigate(&#8217;Home&#8217;); // &#9989; Passes type check</code></code></pre><p>That saved me from the classic React Navigation bug: pushing to a screen that doesn&#8217;t exist and only discovering it when a user taps a button.</p><p><strong>The UX flow worked intuitively.</strong> I tested with friends (using mock data) and they immediately got it:</p><ul><li><p>Open app &#8594; See activities nearby</p></li><li><p>Tap one &#8594; Full details modal</p></li><li><p>Join button &#8594; You&#8217;re in</p></li></ul><p>No tutorials needed. No confusion about &#8220;Now&#8221; vs &#8220;Soon&#8221; buckets. The Instagram-style feed pattern translated perfectly to spontaneous hangouts.</p><p><strong>NativeWind styling was shockingly fast.</strong> Compare:</p><pre><code><code>// Old way (StyleSheet)

const styles = StyleSheet.create({

   container: {

    flex: 1,

    backgroundColor: &#8216;#ffffff&#8217;,

    paddingHorizontal: 16,

    paddingTop: 48,

  },

  title: {

    fontSize: 24,

    fontWeight: &#8216;bold&#8217;,

    marginBottom: 16,

  },

});

// New way (NativeWind)

&lt;View className=&#8221;flex-1 bg-white px-4 pt-12&#8221;&gt;

  &lt;Text className=&#8221;text-2xl font-bold mb-4&#8221;&gt;What&#8217;s Happening&lt;/Text&gt;

&lt;/View&gt;</code></code></pre><h6></h6><p>I styled the entire app in ~2 hours. With StyleSheet, that would&#8217;ve taken a full day.</p><h3><strong>The Weird</strong></h3><p><strong>Expo&#8217;s &#8220;managed workflow&#8221; occasionally feels </strong><em><strong>too</strong></em><strong> magic.</strong></p><p>I tried to add native iOS haptic feedback:</p><pre><code><code>import * as Haptics from &#8216;expo-haptics&#8217;;

// Trigger haptic on button press
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);</code></code></pre><p>It worked instantly. No Xcode. No bridging code. No gradle files. Just... worked.</p><p>That feels great until you hit a limitation and realize you don&#8217;t understand the abstraction layer. It&#8217;s like driving an automatic car&#8212;smooth until you need to pop the hood.</p><p>The &#8220;Close Friends&#8221; tier sparked unexpected product questions.</p><p>I added three friend categories:</p><ul><li><p>Friends (general connections)</p></li><li><p>Close Friends (inner circle)</p></li><li><p>Requests (pending)</p></li></ul><p>During testing, a friend asked: <em>&#8220;How do I know if I&#8217;m someone&#8217;s close friend?&#8221;</em></p><p>Great question. No idea. Should the app show this? Is it private? Does it create awkward situations if someone sees they&#8217;re <em>not</em> a close friend?</p><p>Instagram Stories solves this by making it private (you never know if you&#8217;re on someone&#8217;s close friends list). But for spontaneous hangouts, that asymmetry might feel worse.</p><p><strong>UI decision I&#8217;m still questioning:</strong> Should declined activities disappear or remain visible?</p><p>Right now, if you tap &#8220;Decline,&#8221; the activity stays in your feed (because someone else might join). But does that feel naggy? Should it hide for 24 hours? Or move to a separate &#8220;Passed&#8221; tab?</p><p>I don&#8217;t know yet. Need real users.</p><h3><strong>The Broken</strong></h3><p><strong>The </strong>map is a placeholder box<strong>.</strong> That gray rectangle where the map should be? It may or may not haunt me. Without real location visualization, the &#8220;zone&#8221; concept feels abstract. I know the radius is 5 miles, but <em>show me</em>.</p><p>I didn&#8217;t implement <code>react-native-maps</code> because it requires separate API keys for iOS and Android, custom marker components, clustering logic for performance, and permission handling that goes beyond Expo&#8217;s managed workflow. That&#8217;s easily a full weekend. For a 12-hour prototype validating UX patterns, I chose to ship the interaction model first. The map can wait, for now at least. </p><p>Push notifications are the killer feature, that are not currently implemented (whoops!)<strong>.</strong> The magic should be: Friend posts &#8594; You get notified &#8594; Tap &#8594; Join. Without that, Whim is just Instagram for hangouts. The coordination tool is missing its coordination.</p><p>Expo&#8217;s push notification setup requires server-side token management, notification permissions handling, deep linking configuration, and testing on physical devices (simulators don&#8217;t support push). Worse, the entire value prop depends on perfect notification delivery&#8212;flaky notifications kill trust. I&#8217;d rather ship a working feed first, then add the real-time layer once the core behavior is validated.</p><div><hr></div><h3><strong>What Worked (and Why It Mattered)</strong></h3><p>The biggest win came from building UI-first with mock data. Once I had real interactions to validate, everything else became implementation details, not product questions.</p><p>But the deeper insight was architectural: mobile-first design changes how you think about spontaneity<strong>.</strong> Instead of &#8220;planning apps,&#8221; you build for impulse. The difference between &#8220;Let&#8217;s grab coffee sometime&#8221; and &#8220;Coffee in 15 mins&#8221; isn&#8217;t scheduling&#8212;it&#8217;s removing every friction point between intent and action.</p><p>Claude sketched the navigation architecture and component hierarchy; Cursor kept the build moving by catching TypeScript errors and suggesting React Native patterns before I hit them. The pairing created an unexpected rhythm&#8212;strategy meets execution, both adapting to my workflow as I went.</p><p><strong>Lesson:</strong> Most of what we call &#8220;good UX&#8221; is actually psychological friction removal done right.</p><div><hr></div><h3><strong>What I Learned (and Would Do Differently)</strong></h3><ol><li><p>Mock data first isn&#8217;t just about speed&#8212;it&#8217;s about truth. You can&#8217;t fake user behavior in Figma. Build the interaction, <em>then</em> add infrastructure.</p></li><li><p>Fewer screens, deeper flows<strong>.</strong> I almost built a separate &#8220;activity history&#8221; tab. Glad I didn&#8217;t. Probably would&#8217;ve diluted the core loop.</p></li><li><p>Watch real humans tap things. My friend tried to swipe cards left/right to decline. Didn&#8217;t expect that. Now I&#8217;m considering it.</p></li><li><p>AI pair-building is real. Claude for architecture, Cursor for debugging. It&#8217;s a workflow that feels collaborative, not automated.</p></li></ol><p>And as always: TypeScript first, native modules second.</p><div><hr></div><h3><strong>The Verdict</strong></h3><p><strong>Setup Time &#8211; 8/10</strong><br>Expo makes this almost <em>too</em> easy. <code>npx create-expo-app</code> to working prototype in 12 hours. The NativeWind v4 config was the only real friction.</p><p><strong>Performance &#8211; 8/10</strong><br>Instant hot reload &amp; smooth navigation. React Native has come a long way. No janky transitions, no dropped frames. (Although most of the backend still has a ways to come&#8230;) </p><p><strong>Reliability &#8211; 7/10</strong><br>Navigation works perfectly, but modal state management got weird without Context API. Spent 2 hours debugging prop drilling before refactoring.</p><p><strong>Wow Factor &#8211; 8/10</strong><br>That moment when a friend said &#8220;Wait, I&#8217;d actually use this&#8221; hit different. Validation &gt; perfection. </p><p><strong>Reuse Potential &#8211; 9/10</strong><br>This codebase is a template for any location-based social app. The navigation structure, modal patterns, and NativeWind setup are all reusable.</p><p><strong>Final Verdict:</strong> Worth it if you enjoy shipping fast and iterating faster. 9/10 concept, 7/10 completeness (needs backend).</p><div><hr></div><h3><strong>The Meta Takeaway</strong></h3><p>It does feel like nowadays, in the age of async communication overload, social coordination shouldn&#8217;t require 47 messages and a Doodle poll. Things like context, proximity, and timing are valuable data and can make spontaneous hangouts feel effortless instead of exhausting.</p><p>We&#8217;re shifting from social planning apps to social impulse tools. The systems that survive won&#8217;t just organize&#8212;they&#8217;ll reduce friction, respect attention, and make &#8220;anyone free?&#8221; actually work.</p><p>Whim isn&#8217;t the future&#8212;but it&#8217;s a prototype of one. And it all started with a frustrating group chat, some coffee plans that never happened, and the realization that coordination is a UX problem, not a people problem.</p><p>See my work on GitHub (coming soon&#8212;need to clean up those commit messages first &#128517;)</p><div><hr></div><p>Got a project idea you want me to build next week? Send it my way. My terminal is hungry.</p><p><strong>DM me here! &lt;3</strong></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+ALT+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[I Built a Local AI That Remembers Me — Here’s What Worked, What Broke, and What I Learned]]></title><description><![CDATA[Why I Did This to Myself]]></description><link>https://ctrlcreatelabs.substack.com/p/i-built-a-local-ai-that-remembers</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/i-built-a-local-ai-that-remembers</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Thu, 09 Oct 2025 12:31:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!n7k-!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72fdffa5-44da-4526-8b03-c56241174a25_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Why I Did This to Myself</h3><p>Every conversation with ChatGPT feels like talking to a brilliant goldfish.<br>It dazzles you for thirty seconds, then forgets who you are. I wanted to change that cycle, and thus I decided to build a local &#8220;Second Brain&#8221;&#8212;a small memory system that lets AI actually <em>remember me</em> without shipping my thoughts to the cloud or binging OpenAI tokens.</p><p>The result? A semi-functional prototype that now knows I like sushi, am trying to nail down a consistent running routine, and have a have a bit of an obsession with following dachshunds in cute jackets on instagram. It also knows too much about my work habits, but that&#8217;s another story (and one I am not permitted to tell on here)&#8230; </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+ALT+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h3>The Claim vs. The Setup</h3><p>The concept of &#8220;AI memory&#8221; is seductive:<br><em>Every assistant you use should start where you left off, fully caught up on your goals, patterns, and context, and grow alongside you over time.</em></p><p>That&#8217;s the claim. </p><p>The reality?<br>Fourteen browser tabs, two environment variables, and one emotional breakdown later, I had a working system that sometimes forgot its own encryption password.</p><h3>Tech Stack </h3><ul><li><p>Node 20, TypeScript</p></li><li><p>SQLite for metadata, JSON for vector storage</p></li><li><p>Local embeddings via <code>@xenova/transformers</code></p></li><li><p>Whisper for voice input</p></li><li><p>AES-256 encryption</p></li><li><p>Claude for high-level architecture and system planning</p></li><li><p>Cursor IDE for coding, debugging, and live iteration (Cursor&#8217;s inline AI chat saved me hours)</p></li></ul><h3>Goal</h3><p>Build a lightweight memory engine that plugs into Claude&#8217;s Model Context Protocol (MCP), runs locally, and supports adding and searching &#8220;memories&#8221; semantically&#8212;without cloud APIs.</p><div><hr></div><h2>The Build Log: What Worked, What Failed, and What Got Weird</h2><h3>The Good</h3><p>Local embeddings worked better than expected.<br>I started by adding a few sample memories:</p><p><code>yarn cli add &#8220;I love spicy tuna rolls and prefer it without soy sauce&#8221;</code></p><p><code>yarn cli add &#8220;Remember that my favorite candy is Twizzlers&#8221;</code></p><p><code>yarn cli add &#8220;I tried a new Italian spot and had an amazing tiramisu last week&#8221;</code></p><p>Then I ran: </p><p><code>yarn cli search &#8220;what kind of desserts or foods do I like?&#8221;</code></p><p><code><br></code>Then output: </p><p><code>1. I tried a new Italian spot and had an amazing tiramisu last week</code></p><p><code>2. I love spicy tuna rolls and prefer it without soy sauce</code></p><p><code>3. Remember that my favorite candy is Twizzlers</code></p><p>It <em>understood</em> the meaning, not just the words.<br>That first moment of semantic recall felt more like a spooky ghost AI memory&#8212;messy, human, and a little scary. Think Baby Reindeer meets low budget Silicon Valley. </p><h3>The Weird</h3><p>Once integrated with Claude via MCP, things got uncanny.<br>When I asked, &#8220;What kind of food do I like?&#8221;, Claude replied:</p><blockquote><p>&#8220;You enjoy sushi&#8212;specifically spicy tuna rolls without soy sauce&#8212;and recently raved about tiramisu at a new Italian restaurant. You also mentioned Twizzlers as your favorite candy.&#8221;</p></blockquote><p>That came entirely from local memory&#8212;no API calls, no data sync.</p><p>Claude felt like an assistant that had actually been paying attention.</p><h3>The Broken</h3><p>Naturally, TypeScript had opinions.</p><ul><li><p>The compiler refused to build because of a missing <code>rootDir</code>.</p></li><li><p>Async functions hung on exit.</p></li><li><p>The embedding model occasionally stalled mid-load.</p></li><li><p>Encryption worked too well&#8212;I locked myself out of my own data twice.</p></li></ul><p>Cursor&#8217;s in-IDE debugger saved me here. Using its inline chat, I traced stack errors and refactored live. Claude handled system-level architecture while Cursor fixed the fires in real time. It felt like working with two coworkers&#8212;one calm strategist, one caffeine-fueled firefighter.</p><h2>What Worked (and Why It Mattered)</h2><p>The biggest win came from using <strong>local embeddings</strong> instead of cloud APIs.<br>Once cached, they cut query latency by roughly 60% and removed rate limits entirely.</p><p>But the deeper insight was architectural: <em>local-first design changes how you think about memory.</em><br>Instead of AI &#8220;sessions,&#8221; you build an evolving knowledge base.</p><p>Claude sketched the high-level schema and encryption flow; Cursor kept the build moving by catching subtle type and logic errors before I did. The pairing created an unexpected rhythm&#8212;strategy meets execution, both learning me as they went.</p><p>Lesson: most of what we call &#8220;AI intelligence&#8221; is actually human scaffolding done right.</p><div><hr></div><h2>What I Learned (and Would Do Differently)</h2><ol><li><p><strong>Local-first isn&#8217;t just about privacy&#8212;it&#8217;s about trust.</strong> You design for autonomy, not dependency.</p></li><li><p><strong>Fewer memories, deeper meaning.</strong> Debug three examples before adding thirty.</p></li><li><p><strong>Observe your vectors.</strong> Logging cosine similarity scores early saves endless guessing.</p></li><li><p><strong>AI pair-building is real.</strong> Claude for architecture, Cursor for debugging&#8212;it&#8217;s a workflow that feels collaborative, not automated.</p></li></ol><p>And as always: caffeine first, YAML second.</p><div><hr></div><h2>The Verdict</h2><p><strong>Setup Time &#8211; 5/10</strong><br>More &#8220;hackathon&#8221; than &#8220;hello world.&#8221; Expect a few hours of dependency wrestling before you see a clean and compatible build. </p><p><strong>Performance &#8211; 8/10</strong><br>Once the embeddings cache locally, the whole thing flies. Searches feel instant, even with hundreds of entries.</p><p><strong>Reliability &#8211; 6/10</strong><br>Solid when it works, but every now and then the vector index forgets what reality is. A quick rebuild usually fixes it.</p><p><strong>Wow Factor &#8211; 9/10</strong><br>That first recall moment (when the AI references your past sushi order or candy preference) is borderline eerie in the best way.</p><p><strong>Reuse Potential &#8211; 7/10</strong><br>It&#8217;s a great backbone for future personal AI projects or as a component inside bigger local-first experiments.</p><p><strong>Final Verdict:</strong><br>Worth it if you enjoy debugging your own mind in TypeScript.<br>10/10 concept, 6/10 sanity.</p><div><hr></div><h2>The Meta Takeaway</h2><p>It does feel a bit like nowadays in the age of information that every assistant you use should just be able to instantly come up to speed on you. Things like context, history, and growth is valuable data and can give any AI tool helpful or even necessary data on a user. Think tailoring responses to potentially avoid drastic impacts on well being or mental health, but I&#8217;ll let you go down that hole.</p><p>We&#8217;re shifting from <em>AI as app</em> to <em>AI as adaptive runtime.</em><br>The systems that survive the test of time won&#8217;t just respond; they&#8217;ll remember, refine, automate and grow with you.</p><p>My local memory engine isn&#8217;t the future&#8212;but it&#8217;s a prototype of one.<br>And it all started with sushi, Twizzlers, and tiramisu.</p><p>See my work on <a href="https://github.com/codebyellalesperance/Second-Brain-MCP">GitHub</a>:) </p><div><hr></div><p>Got a tool or an idea you want me to break next week?<br>Send it my way. My error logs are hungry.</p><p>DM me here! &lt;3 </p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading CTRL+ALT+CREATE&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Coming soon]]></title><description><![CDATA[This is CTRL+CREATE&#8217;s Substack.]]></description><link>https://ctrlcreatelabs.substack.com/p/coming-soon</link><guid isPermaLink="false">https://ctrlcreatelabs.substack.com/p/coming-soon</guid><dc:creator><![CDATA[CTRL+CREATE]]></dc:creator><pubDate>Tue, 30 Sep 2025 17:46:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!n7k-!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72fdffa5-44da-4526-8b03-c56241174a25_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is CTRL+CREATE&#8217;s Substack.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://ctrlcreatelabs.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://ctrlcreatelabs.substack.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item></channel></rss>