<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Enrique Uribe</title>
    <description>The latest articles on DEV Community by Enrique Uribe (@uribejr).</description>
    <link>https://dev.to/uribejr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg</url>
      <title>DEV Community: Enrique Uribe</title>
      <link>https://dev.to/uribejr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/uribejr"/>
    <language>en</language>
    <item>
      <title>I Got the Adidas World Cup Ball Collection for My Birthday, So I Built a Digital Museum for It</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:00:25 +0000</pubDate>
      <link>https://dev.to/uribejr/i-got-the-adidas-world-cup-ball-collection-for-my-birthday-so-i-built-a-digital-museum-for-it-5009</link>
      <guid>https://dev.to/uribejr/i-got-the-adidas-world-cup-ball-collection-for-my-birthday-so-i-built-a-digital-museum-for-it-5009</guid>
      <description>&lt;p&gt;For my birthday, I got the Adidas FIFA World Cup ball collection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z32xs3ogl2t3pfvqw7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z32xs3ogl2t3pfvqw7z.png" alt="world cup mini ball collection" width="800" height="804"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fifteen mini balls. Fifteen tournaments. A shelf full of football history.&lt;/p&gt;

&lt;p&gt;The soccer lover in me loved it immediately. The developer in me had a different reaction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if each physical ball could unlock its own digital museum page?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So over the weekend I built &lt;strong&gt;World Cup Ball Scanner&lt;/strong&gt;: a browser-based museum for the collection. You can point your phone camera at a ball, let the app read the printed name, and jump into that tournament's story, stats, legends, and highlight video.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://world-cup-visions.vercel.app" rel="noopener noreferrer"&gt;https://world-cup-visions.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub repo: &lt;a href="https://github.com/UribeJr/world-cup-visions" rel="noopener noreferrer"&gt;https://github.com/UribeJr/world-cup-visions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ckxnwyqf9rofocn1rfx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ckxnwyqf9rofocn1rfx.gif" alt="gallery-scrolling-gif" width="560" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The app turns a physical collection into an interactive exhibit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Camera scanner&lt;/strong&gt;: reads the printed ball name with in-browser OCR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3D gallery&lt;/strong&gt;: a WebGL sphere of every Adidas World Cup match ball from 1970 to 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tournament pages&lt;/strong&gt;: swipeable detail pages with host, champion, final score, attendance, legends, fun facts, and ball design history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlight videos&lt;/strong&gt;: each ball can play an owner-supplied tournament clip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No backend&lt;/strong&gt;: static React app, self-hosted OCR assets, no accounts, no analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was not just "make a database of balls." I wanted it to feel like walking up to a museum placard, except the placard reacts to the physical object in your hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://world-cup-visions.vercel.app" rel="noopener noreferrer"&gt;▶ Try the live demo →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The product idea
&lt;/h2&gt;

&lt;p&gt;I kept coming back to one simple user flow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pick up a ball -&amp;gt; scan it -&amp;gt; unlock the year.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gave the project a clear shape. The app did not need user accounts, comments, admin dashboards, or a content management system. It needed to answer a very specific moment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I have this ball in my hand. What happened at that World Cup?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So every screen is built around that.&lt;/p&gt;

&lt;p&gt;The home screen pushes you toward scanning or browsing. The gallery gives you a visual way to explore the full collection. The detail pages give each tournament room to breathe.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1asfccpygqvfqed6lin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1asfccpygqvfqed6lin.png" alt="mobile gallery view" width="390" height="844"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the scanner works
&lt;/h2&gt;

&lt;p&gt;The scanner uses &lt;a href="https://github.com/naptha/tesseract.js" rel="noopener noreferrer"&gt;Tesseract.js&lt;/a&gt;, running fully in the browser.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the camera with &lt;code&gt;getUserMedia&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Show a framing guide so the printed ball name sits in a predictable area.&lt;/li&gt;
&lt;li&gt;Crop the frame to that guide before OCR.&lt;/li&gt;
&lt;li&gt;Normalize OCR text: uppercase, strip diacritics, collapse whitespace.&lt;/li&gt;
&lt;li&gt;Match against ball keywords with priority and light fuzzy matching.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The keyword priority matters more than I expected.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;TELSTAR&lt;/code&gt; is ambiguous. There is the 1970 Telstar, the 1974 Telstar Durlast, and the 2018 Telstar 18. If OCR only sees &lt;code&gt;TELSTAR&lt;/code&gt;, the app should not confidently guess the wrong one.&lt;/p&gt;

&lt;p&gt;So the matcher checks more specific phrases first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TELSTAR 18&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DURLAST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;then bare &lt;code&gt;TELSTAR&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a result is genuinely ambiguous, the app asks the user instead of pretending to know.&lt;/p&gt;

&lt;p&gt;That was a good reminder: for physical-world software, "I don't know, help me choose" can be better UX than a false positive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhgc7hlp72rqh5m7g1t2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhgc7hlp72rqh5m7g1t2.jpg" alt="scanning brazuca ball example" width="800" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3D gallery
&lt;/h2&gt;

&lt;p&gt;The gallery was the part I wanted to feel special.&lt;/p&gt;

&lt;p&gt;I could have made a grid of cards, and the app would have worked. But a grid did not feel like a collection. It felt like a catalog.&lt;/p&gt;

&lt;p&gt;So I built a WebGL sphere gallery with Three.js and GSAP. The cards wrap around the viewport, you can drag to rotate, and selecting a ball transitions into the detail page.&lt;/p&gt;

&lt;p&gt;There is also a list fallback for reduced motion or browsers without WebGL support, because a fun visual should not block the core experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the tournament pages feel like exhibits
&lt;/h2&gt;

&lt;p&gt;Each ball page combines two kinds of content:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structured data&lt;/strong&gt;: host, winner, runner-up, final score, teams, matches, goals, attendance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authored exhibit text&lt;/strong&gt;: ball design, material, historical context, legends, memorable moments, and fun facts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jabulani 2010&lt;/strong&gt; is not just "Spain won." It is the first World Cup in Africa, the vuvuzela soundtrack, Iniesta's extra-time winner, and one of the most controversial balls ever made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brazuca 2014&lt;/strong&gt; is Germany's fourth title, Brazil's 7-1 trauma, and the ball that restored confidence after Jabulani's aerodynamics controversy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trionda 2026&lt;/strong&gt; is the upcoming expanded tournament across Canada, Mexico, and the United States.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app is basically a little sports-history layer wrapped around a physical collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built with
&lt;/h2&gt;

&lt;p&gt;The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite + React + TypeScript&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-router&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tesseract.js for browser OCR&lt;/li&gt;
&lt;li&gt;Three.js for the gallery scene&lt;/li&gt;
&lt;li&gt;GSAP for gallery/hero motion&lt;/li&gt;
&lt;li&gt;Vitest for matcher and data tests&lt;/li&gt;
&lt;li&gt;Vercel for hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repo is public here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/UribeJr/world-cup-visions" rel="noopener noreferrer"&gt;https://github.com/UribeJr/world-cup-visions&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI helped
&lt;/h2&gt;

&lt;p&gt;I used Cursor and AI assistance throughout the build, but I tried to keep it as a collaborator rather than the driver.&lt;/p&gt;

&lt;p&gt;The useful parts were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scaffolding boring glue code faster;&lt;/li&gt;
&lt;li&gt;refactoring matcher logic and tests;&lt;/li&gt;
&lt;li&gt;debugging Vercel deployment issues;&lt;/li&gt;
&lt;li&gt;polishing the README and public repo;&lt;/li&gt;
&lt;li&gt;checking edge cases I might have missed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The parts I still had to own were the product decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the museum should feel like;&lt;/li&gt;
&lt;li&gt;when to scan vs browse;&lt;/li&gt;
&lt;li&gt;how much motion was too much;&lt;/li&gt;
&lt;li&gt;what to cut so it stayed a weekend project;&lt;/li&gt;
&lt;li&gt;how to connect the physical birthday gift to a digital experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That balance felt right. AI helped me move faster, but the point of the project was still personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;p&gt;If I keep working on it, I would like to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tune OCR keywords after testing each physical ball in different lighting;&lt;/li&gt;
&lt;li&gt;add a more polished scan success animation;&lt;/li&gt;
&lt;li&gt;add more behind-the-scenes stories for each tournament.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;This started as a birthday gift sitting on a shelf.&lt;/p&gt;

&lt;p&gt;By Sunday night, it had become a tiny browser museum: part scanner, part gallery, part football-history rabbit hole.&lt;/p&gt;

&lt;p&gt;That is my favorite kind of weekend project: personal enough to care about, technical enough to learn from, and small enough to actually finish.&lt;/p&gt;

&lt;p&gt;If you have a minute, try it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://world-cup-visions.vercel.app" rel="noopener noreferrer"&gt;https://world-cup-visions.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/UribeJr/world-cup-visions" rel="noopener noreferrer"&gt;https://github.com/UribeJr/world-cup-visions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What physical collection would you digitize this way?&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>AdblockNot - The Adblocker you've never wanted.</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Sat, 11 Apr 2026 22:13:14 +0000</pubDate>
      <link>https://dev.to/uribejr/adblocknot-the-adblock-youve-never-wanted-c00</link>
      <guid>https://dev.to/uribejr/adblocknot-the-adblock-youve-never-wanted-c00</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Adblock Not
&lt;/h1&gt;

&lt;p&gt;Have you ever sat there watching a youtube ad and said "dang, I just wish there were more ads?" Welcome to AdblockNot!&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;code&gt;Adblock Not&lt;/code&gt;, the ad blocker you've never wanted.&lt;/p&gt;

&lt;p&gt;Most ad blockers try to make the web less annoying. &lt;code&gt;Adblock Not&lt;/code&gt; takes the opposite approach. It sees your desire for an ad-free experience and responds with the worst possible product decision: before it blocks ads, it makes you watch one.&lt;/p&gt;

&lt;p&gt;Not a skippable one, either.&lt;/p&gt;

&lt;p&gt;To make this bad idea feel more official, I wrapped the whole thing in a fake early-2000s cable/satellite interface. The overlay looks like a retro TV guide, the ads are presented like channels, and if you do not like the current ad, you can always change the channel to a different ad.&lt;/p&gt;

&lt;p&gt;I also wanted it to tap into a very specific kind of commercial nostalgia: the era of weird infomercials, overdramatic voiceovers, late-night paid programming, and commercials you did not exactly love, but somehow still remember forever.&lt;/p&gt;

&lt;p&gt;That is the entire value proposition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is an ad blocker built on a terrible premise&lt;/li&gt;
&lt;li&gt;it treats ad blocking like premium content authorization&lt;/li&gt;
&lt;li&gt;it adds friction where absolutely none was needed&lt;/li&gt;
&lt;li&gt;it is less a utility and more a hostage negotiation with nostalgic television design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current lineup includes multiple gloriously inconvenient commercials, including &lt;code&gt;Fushigi&lt;/code&gt;, &lt;code&gt;Education Connection&lt;/code&gt;, &lt;code&gt;Nike Football&lt;/code&gt;, &lt;code&gt;Sears&lt;/code&gt;, and &lt;code&gt;Zoobooks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So yes: I made software whose core feature is disrespecting your time in a more organized and stylish way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/DSFYu1wlt9M"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3mmtzb6coacqsmmrxub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3mmtzb6coacqsmmrxub.png" alt="adblocknot_fushigi_commercial" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfphd2ghz0oe9bacz4qv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfphd2ghz0oe9bacz4qv.png" alt="adblocknot_channelguide" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1ozmkogizy862yunzdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1ozmkogizy862yunzdj.png" alt="adblocknot_nike_football_commercial" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/UribeJr" rel="noopener noreferrer"&gt;
        UribeJr
      &lt;/a&gt; / &lt;a href="https://github.com/UribeJr/adblocknot" rel="noopener noreferrer"&gt;
        adblocknot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Adblock Not&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Adblock Not&lt;/code&gt; is the ad blocker you've never wanted.&lt;/p&gt;
&lt;p&gt;It is a joke Chrome MV3 extension built around a deliberately terrible idea: before it blocks ads, it makes you watch an ad. To make the experience feel more official, the whole gate is wrapped in a retro early-2000s TV interface with channel switching, a fake guide, and a lineup of bundled commercials.&lt;/p&gt;
&lt;p&gt;This is not a serious ad blocker. It is an anti-product with excellent commitment to the bit.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What It Does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Detects when a page should show the &lt;code&gt;Adblock Not&lt;/code&gt; gate.&lt;/li&gt;
&lt;li&gt;Opens a fullscreen retro TV-style overlay before ad blocking is allowed.&lt;/li&gt;
&lt;li&gt;Makes you watch one of several bundled ad videos to completion, or wait through a fallback timer if playback fails.&lt;/li&gt;
&lt;li&gt;Lets you "change the channel" to a different ad, which is obviously not real relief.&lt;/li&gt;
&lt;li&gt;Includes a retro guide UI so you can manually pick…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/UribeJr/adblocknot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Adblock Not&lt;/code&gt; is a Chrome Manifest V3 extension built with plain JavaScript, HTML, and CSS.&lt;/p&gt;

&lt;p&gt;The basic idea was simple: if an ad blocker is supposed to remove interruptions, I wanted to build one that introduces a brand new interruption first.&lt;/p&gt;

&lt;p&gt;The extension injects a fullscreen content-script overlay onto pages, then uses a background service worker to track whether the user has satisfied the completely unnecessary ad-watching requirement.&lt;/p&gt;

&lt;p&gt;Main pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;manifest.json&lt;/code&gt; wires up the MV3 extension, content script, service worker, and bundled media assets.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/content/gate-overlay.js&lt;/code&gt; renders the fake TV UI, loads the bundled ad videos, handles channel switching, opens the guide, and controls the unlock flow.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/content/gate-overlay.css&lt;/code&gt; gives the whole experience the retro cable-box look.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/background/service-worker.js&lt;/code&gt; stores unlock state and decides whether the gate should appear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of my favorite details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The entire joke works because the product logic is coherent. Every part of it is committed to making ad blocking slightly more annoying.&lt;/li&gt;
&lt;li&gt;The guide UI is based on old cable/satellite channel guides because that era already felt one tiny step away from self-parody.&lt;/li&gt;
&lt;li&gt;The bundled ads are part of the joke, but they are also part of the nostalgia. They come from that very specific "how is this on television again?" era of commercials that burned themselves into your brain.&lt;/li&gt;
&lt;li&gt;The video player uses custom controls to keep the fake-TV vibe intact.&lt;/li&gt;
&lt;li&gt;"Change the channel" is my favorite feature because it sounds like a mercy feature, but it is actually just a different route to the same suffering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technically, it was also a fun constraint: make something that is dumb on purpose, but polished enough that the joke lands immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;Primary category:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Community Favorite&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why:&lt;/p&gt;

&lt;p&gt;I built this as a pure anti-product. The concept is immediate, the joke is obvious, and the whole thing is committed to the bit: an ad blocker that forces you to watch ads before it helps you avoid ads.&lt;/p&gt;

&lt;p&gt;Alternate category I could also argue for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Best Ode to Larry Masinter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why:&lt;/p&gt;

&lt;p&gt;The project is built in the spirit of lovingly useless internet/software humor. It is not practical, it is not responsible, and it is absolutely committed to the bit.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Good luck to all the winners! This was fun to dive into as a pretty 'basic' notion user. MCP really helped learn the ins and outs of what notion can provide.</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Wed, 01 Apr 2026 18:24:29 +0000</pubDate>
      <link>https://dev.to/uribejr/good-luck-to-all-the-winners-this-was-fun-to-dive-into-as-a-pretty-basic-notion-user-mcp-really-45no</link>
      <guid>https://dev.to/uribejr/good-luck-to-all-the-winners-this-was-fun-to-dive-into-as-a-pretty-basic-notion-user-mcp-really-45no</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-story__hidden-navigation-link"&gt;I built an AI behavioral Brain with Notion MCP&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Notion MCP Challenge Submission 🧠&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/uribejr" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" alt="uribejr profile" class="crayons-avatar__image" width="800" height="856"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/uribejr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Enrique Uribe
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Enrique Uribe
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3416869" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/uribejr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" class="crayons-avatar__image" alt="" width="800" height="856"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Enrique Uribe&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" id="article-link-3416869"&gt;
          I built an AI behavioral Brain with Notion MCP
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/notionchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;notionchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;12&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Mon, 30 Mar 2026 15:20:22 +0000</pubDate>
      <link>https://dev.to/uribejr/-1hd7</link>
      <guid>https://dev.to/uribejr/-1hd7</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-story__hidden-navigation-link"&gt;I built an AI behavioral Brain with Notion MCP&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Notion MCP Challenge Submission 🧠&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/uribejr" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" alt="uribejr profile" class="crayons-avatar__image" width="800" height="856"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/uribejr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Enrique Uribe
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Enrique Uribe
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3416869" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/uribejr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" class="crayons-avatar__image" alt="" width="800" height="856"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Enrique Uribe&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" id="article-link-3416869"&gt;
          I built an AI behavioral Brain with Notion MCP
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/notionchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;notionchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;12&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Sat, 28 Mar 2026 14:01:25 +0000</pubDate>
      <link>https://dev.to/uribejr/-den</link>
      <guid>https://dev.to/uribejr/-den</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-story__hidden-navigation-link"&gt;I built an AI E-Commerce Brain with Notion MCP&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Notion MCP Challenge Submission 🧠&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/uribejr" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" alt="uribejr profile" class="crayons-avatar__image" width="800" height="856"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/uribejr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Enrique Uribe
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Enrique Uribe
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3416869" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/uribejr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" class="crayons-avatar__image" alt="" width="800" height="856"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Enrique Uribe&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" id="article-link-3416869"&gt;
          I built an AI E-Commerce Brain with Notion MCP
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/notionchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;notionchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;7&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>I built an AI behavioral Brain with Notion MCP</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Fri, 27 Mar 2026 20:30:12 +0000</pubDate>
      <link>https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6</link>
      <guid>https://dev.to/uribejr/i-built-an-ai-e-commerce-brain-with-notion-mcp-4bi6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built an AI e-commerce brain for a Shopify storefront using Notion MCP as the behavior control layer.&lt;/p&gt;

&lt;p&gt;On the surface, it looks like one shopping assistant. Under the hood, it behaves more like a network of specialists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a training bag specialist&lt;/li&gt;
&lt;li&gt;a gloves and wraps specialist&lt;/li&gt;
&lt;li&gt;a cart review specialist&lt;/li&gt;
&lt;li&gt;an order history specialist&lt;/li&gt;
&lt;li&gt;an add-to-cart specialist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is split intentionally.&lt;/p&gt;

&lt;p&gt;The app keeps ownership of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;intent classification and routing&lt;/li&gt;
&lt;li&gt;Shopify integration&lt;/li&gt;
&lt;li&gt;ranking and filtering&lt;/li&gt;
&lt;li&gt;session and conversation state&lt;/li&gt;
&lt;li&gt;validation and fallback logic&lt;/li&gt;
&lt;li&gt;performance and caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notion owns the behavior layer for each intent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tone and voice&lt;/li&gt;
&lt;li&gt;intent instructions&lt;/li&gt;
&lt;li&gt;recommendation strategy&lt;/li&gt;
&lt;li&gt;questions to ask&lt;/li&gt;
&lt;li&gt;do / do not rules&lt;/li&gt;
&lt;li&gt;example responses&lt;/li&gt;
&lt;li&gt;behavior notes&lt;/li&gt;
&lt;li&gt;synced intent synonyms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means I can change how the AI behaves per scenario without rewriting prompts throughout the codebase.&lt;/p&gt;

&lt;p&gt;Instead of one generic chatbot, I now have a commerce system made up of specialist branches like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;search_training_bags&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_gloves_wraps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cart_review&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orders_selection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;order_detail&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the behavior visible, editable, and operational.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes8tqbm2jiis8r4x09ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes8tqbm2jiis8r4x09ju.png" alt="preview of notion dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also built a publish model so Notion is safe to use in production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edit behavior in Notion&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Sync from Notion&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Publish the behavior into the app&lt;/li&gt;
&lt;li&gt;Run the storefront from the published config locally for fast runtime performance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also built the reverse flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Sync to Notion&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the app can push its current behavior definitions back into the Notion database.&lt;/p&gt;

&lt;p&gt;This turned Notion from a documentation layer into a real control plane for the AI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8s2eyv5e7tqdm0j243o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8s2eyv5e7tqdm0j243o.png" alt="notion sync flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;In the demo, I walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Notion behavior control panel&lt;/li&gt;
&lt;li&gt;the specialist intent rows&lt;/li&gt;
&lt;li&gt;the sync workflow&lt;/li&gt;
&lt;li&gt;changing behavior in Notion and seeing the storefront assistant respond differently&lt;/li&gt;
&lt;li&gt;category-specific shopping flows like training bags and gloves/wraps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/rCZ4Tis-Pmk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Parse Notion rows into behavior config
&lt;/h3&gt;

&lt;p&gt;This is the layer that turns a Notion database row into a specialist behavior object the app can use at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseNotionBehaviorRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;intent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeIntentKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enabledRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;enabledRaw&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;enabledRaw&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;intentSynonyms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;intent_synonyms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;synonyms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trigger_phrases&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;toneVoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tone_voice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;voice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;intentInstructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;intent_instructions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instructions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;questionsToAsk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;questions_to_ask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;questions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;recommendationStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product_recommendation_strategy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recommendation_strategy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;doRules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;do&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;do_rules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;doNotRules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;do_not&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;do_not_rules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;upsellCrossSellStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upsell_cross_sell_strategy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upsell_strategy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cross_sell_strategy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;exampleResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example_responses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;examples&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;behaviorNotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pickProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;behavior_notes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sync behavior from Notion into the app
&lt;/h3&gt;

&lt;p&gt;This is the publish step. It pulls the latest behavior definitions from Notion and stores a published config in the app for fast local runtime use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncChatBehaviorFromNotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchNotionBehaviorDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publishedConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizePublishedBehaviorConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published_notion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;behaviorConfigJson&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publishedConfig&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;behaviorConfigSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publishedConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;behaviorSyncedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;behaviorCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publishedConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_BEHAVIOR_CACHE_MS&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_CACHE_MS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_CACHE_MS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;publishedConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sync behavior back to Notion
&lt;/h3&gt;

&lt;p&gt;This is the reverse direction. It pushes app-side behavior definitions back into the Notion control panel so the system stays visible and editable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncChatBehaviorToNotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitizeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databaseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitizeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_BEHAVIOR_DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing NOTION_API_TOKEN or NOTION_BEHAVIOR_DATABASE_ID.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shop&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publishedConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseBehaviorConfigJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;behaviorConfigJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;cloneDefaultConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effectiveConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizePublishedBehaviorConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;publishedConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publishedConfig&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;queryNotionBehaviorRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rowMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;existingRows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseNotionBehaviorRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;rowMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effectiveConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intents&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;intentKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedBehavior&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;effectiveConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
      &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;behavior&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildNotionBehaviorProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intentKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mergedBehavior&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingPageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rowMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intentKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingPageId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateNotionBehaviorPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;existingPageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createNotionBehaviorPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pushedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;intentCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Runtime behavior loading in storefront chat
&lt;/h3&gt;

&lt;p&gt;This is where the storefront chat loads the published behavior config for the active shop before generating a response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;behaviorConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getChatBehaviorConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shopDomain&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;shop&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then later, the response generator uses the active specialist behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateAssistantCopy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;allowFollowups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;policyAnswer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chatHistory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;behaviorConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;behaviorIntent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... build shortlist and conversation context ...&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recommendationInstruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;behaviorIntent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_training_bags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;trainingBagFollowupInstruction&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;standardFollowupInstruction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// behaviorConfig + behaviorIntent shape the final response&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Admin actions for the two sync buttons
&lt;/h3&gt;

&lt;p&gt;This is the UI-facing action layer that powers Sync From Notion and Sync To Notion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actionType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync_behavior_to_notion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncChatBehaviorToNotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;behaviorPush&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pushedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;pushedAt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Behavior pushed to Notion for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;intentCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; intents.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync_behavior_from_notion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncChatBehaviorFromNotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;behaviorSync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;published_notion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;syncedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;fetchedAt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Behavior synced from Notion.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion MCP is what made this system possible in a meaningful way.&lt;/p&gt;

&lt;p&gt;I used Notion MCP to create and operate a behavior database that acts like a control room for the storefront AI.&lt;/p&gt;

&lt;p&gt;Each row in Notion represents an intent or a specialized branch, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;search&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_training_bags&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_gloves_wraps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cart_review&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orders_selection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;order_detail&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each row contains structured behavior fields like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Tone Voice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Intent Synonyms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Intent Instructions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Questions To Ask&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Product Recommendation Strategy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Do&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Do Not&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Example Responses&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Behavior Notes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This let me turn Notion into an operator-facing behavior system instead of just a place to store notes.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the training bag specialist can focus on freestanding vs hanging bag decisions&lt;/li&gt;
&lt;li&gt;the gloves and wraps specialist can focus on MMA gloves, boxing gloves, bag gloves, wraps, padding, and protection&lt;/li&gt;
&lt;li&gt;the cart review specialist can behave more like a checkout assistant&lt;/li&gt;
&lt;li&gt;the order specialists can stay concise and service-oriented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is that Notion MCP did not just help me document the system. It helped me build the system that controls the AI behavior.&lt;/p&gt;

&lt;p&gt;That unlocked a much stronger workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identify a weak response&lt;/li&gt;
&lt;li&gt;update the specialist behavior in Notion&lt;/li&gt;
&lt;li&gt;sync it into the app&lt;/li&gt;
&lt;li&gt;test it live in the storefront&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I think this is a strong Notion MCP use case.&lt;/p&gt;

&lt;p&gt;I did not just connect Notion to an agent.&lt;br&gt;
I used Notion MCP to build the behavior control plane for an AI commerce brain.&lt;/p&gt;

&lt;p&gt;Cart Review Behavior:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd6ekt1ftt564u9nfhms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd6ekt1ftt564u9nfhms.png" alt="cart review behavior"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sale Search Behavior&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdco267lk2s6gll3cybne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdco267lk2s6gll3cybne.png" alt="sale search behavior"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The part I like most is that behavior is no longer buried in prompts or scattered across code. Notion became a real operating surface for the AI, and that made the storefront feel less like a chatbot and more like a system of specialists that can actually be tuned, tested, and improved over time.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>🧠 My 2026 Shopify Stack — I Don’t Build Apps, I Build Business Solutions</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Tue, 24 Feb 2026 20:47:03 +0000</pubDate>
      <link>https://dev.to/uribejr/my-2026-shopify-stack-i-dont-build-apps-i-build-business-solutions-m4p</link>
      <guid>https://dev.to/uribejr/my-2026-shopify-stack-i-dont-build-apps-i-build-business-solutions-m4p</guid>
      <description>&lt;p&gt;I don’t build Shopify apps for fun.&lt;/p&gt;

&lt;p&gt;I build systems that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate operations&lt;/li&gt;
&lt;li&gt;Reduce manual work&lt;/li&gt;
&lt;li&gt;Protect revenue&lt;/li&gt;
&lt;li&gt;Increase AOV&lt;/li&gt;
&lt;li&gt;Clean up messy workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shopify is just the platform.&lt;/p&gt;

&lt;p&gt;The real goal is solving business problems.&lt;/p&gt;

&lt;p&gt;This is the stack I use every day to do that.&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ Shopify CLI — The Operational Backbone
&lt;/h2&gt;

&lt;p&gt;Every solution starts here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;shopify app init&lt;/code&gt;&lt;br&gt;
&lt;code&gt;shopify app dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That one workflow covers the things that usually break first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth&lt;/li&gt;
&lt;li&gt;Local development&lt;/li&gt;
&lt;li&gt;Environment wiring&lt;/li&gt;
&lt;li&gt;Fast iteration loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you’re shipping revenue-impacting logic, fragile setup is a liability.&lt;/p&gt;

&lt;p&gt;CLI keeps the foundation predictable.&lt;/p&gt;


&lt;h2&gt;
  
  
  2️⃣ Shopify MCP — Build Against Reality, Not Assumptions
&lt;/h2&gt;

&lt;p&gt;Most bugs happen because developers assume store structure.&lt;/p&gt;

&lt;p&gt;I don’t assume. I inspect.&lt;/p&gt;

&lt;p&gt;Before building logic around products or metafields, I look at what actually exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query {
  products(first: 3) {
    edges {
      node {
        id
        title
        metafields(first: 10) {
          edges {
            node {
              namespace
              key
              type
            }
          }
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I’m working with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real namespaces
&lt;/li&gt;
&lt;li&gt;Real types
&lt;/li&gt;
&lt;li&gt;Real data patterns
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When business logic depends on structure, guessing gets expensive.&lt;/p&gt;




&lt;h2&gt;
  
  
  3️⃣ Admin GraphQL API — Precision Over Noise
&lt;/h2&gt;

&lt;p&gt;Business solutions need precision.&lt;/p&gt;

&lt;p&gt;Example: auto-tag high-value orders so the ops team can prioritize them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mutation = `
mutation AddOrderTag($id: ID!, $tags: [String!]!) {
  tagsAdd(id: $id, tags: $tags) {
    node { id }
    userErrors { field message }
  }
}
`;

const response = await admin.graphql(mutation, {
  variables: {
    id: orderId,
    tags: ["Priority"]
  }
});

const data = await response.json();

if (data.data.tagsAdd.userErrors.length &amp;gt; 0) {
  console.error(data.data.tagsAdd.userErrors);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small example. Real impact.&lt;/p&gt;

&lt;p&gt;GraphQL lets me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scope tightly&lt;/li&gt;
&lt;li&gt;Avoid overfetching&lt;/li&gt;
&lt;li&gt;Control exactly what the system does&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When thousands of events are flowing through your app, precision matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  4️⃣ Webhooks — Event-Driven Automation
&lt;/h2&gt;

&lt;p&gt;I don’t poll.&lt;/p&gt;

&lt;p&gt;I react.&lt;/p&gt;

&lt;p&gt;Most business automation is event-based:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Product updates&lt;/li&gt;
&lt;li&gt;Customer creation&lt;/li&gt;
&lt;li&gt;App uninstall events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Minimal webhook skeleton:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { authenticate } from "../shopify.server";

export const action = async ({ request }) =&amp;gt; {
  const { topic, payload, shop } = await authenticate.webhook(request);

  if (topic === "ORDERS_CREATE") {
    console.log(`New order from ${shop}`);

    // Example: run automation logic here
    // await handleOrderAutomation(payload);
  }

  return new Response(null, { status: 200 });
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where Shopify turns into a business operating system.&lt;/p&gt;

&lt;p&gt;The store moves.&lt;br&gt;&lt;br&gt;
Your system responds.&lt;/p&gt;




&lt;h2&gt;
  
  
  5️⃣ Tunneling — Clean Install &amp;amp; Testing Cycles
&lt;/h2&gt;

&lt;p&gt;Business solutions often depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth behaving correctly&lt;/li&gt;
&lt;li&gt;Webhooks firing reliably&lt;/li&gt;
&lt;li&gt;Installation flow being clean&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;shopify app dev&lt;/code&gt; handles tunneling seamlessly.&lt;/p&gt;

&lt;p&gt;That means I can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reinstall repeatedly&lt;/li&gt;
&lt;li&gt;Test webhook flows safely&lt;/li&gt;
&lt;li&gt;Simulate real store conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stability during development prevents chaos in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  6️⃣ DevTools — Because Business Owners Notice Speed
&lt;/h2&gt;

&lt;p&gt;If your embedded app slows down the Shopify admin, it won’t survive.&lt;/p&gt;

&lt;p&gt;I constantly inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundle size&lt;/li&gt;
&lt;li&gt;Network waterfalls&lt;/li&gt;
&lt;li&gt;Blocking scripts&lt;/li&gt;
&lt;li&gt;Unnecessary re-renders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Merchants don’t care how elegant your architecture is.&lt;/p&gt;

&lt;p&gt;They care if it feels fast.&lt;/p&gt;

&lt;p&gt;Business impact &amp;gt; technical elegance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Difference
&lt;/h2&gt;

&lt;p&gt;Anyone can build features.&lt;/p&gt;

&lt;p&gt;Not everyone builds solutions.&lt;/p&gt;

&lt;p&gt;The difference is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thinking in workflows, not endpoints&lt;/li&gt;
&lt;li&gt;Designing event-driven systems&lt;/li&gt;
&lt;li&gt;Validating store structure before writing logic&lt;/li&gt;
&lt;li&gt;Treating revenue-impacting code with discipline&lt;/li&gt;
&lt;li&gt;Building for scale from day one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shopify is just the canvas.&lt;/p&gt;

&lt;p&gt;The real work is understanding the business behind it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;In 2026, Shopify development isn’t about who writes the cleanest code.&lt;/p&gt;

&lt;p&gt;It’s about who designs systems that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Survive scale&lt;/li&gt;
&lt;li&gt;Protect revenue&lt;/li&gt;
&lt;li&gt;Reduce operational friction&lt;/li&gt;
&lt;li&gt;Make merchants’ lives easier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the stack I build with.&lt;/p&gt;

&lt;p&gt;If you’re building on Shopify — are you building features, or building systems?&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Using banner bear’s AI image creation to automate designer workflows. Check it out!</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Sun, 15 Feb 2026 00:33:53 +0000</pubDate>
      <link>https://dev.to/uribejr/using-banner-bears-ai-image-creation-to-automate-designer-workflows-check-it-out-1dfk</link>
      <guid>https://dev.to/uribejr/using-banner-bears-ai-image-creation-to-automate-designer-workflows-check-it-out-1dfk</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/uribejr" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" alt="uribejr"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/uribejr/automating-email-image-requests-with-asana-zapier-bannerbear-89d" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;🚀 Automating Email Image Requests with Asana + Zapier + BannerBear&lt;/h2&gt;
      &lt;h3&gt;Enrique Uribe ・ Feb 12&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#automation&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nocode&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#api&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>automation</category>
      <category>nocode</category>
      <category>api</category>
      <category>productivity</category>
    </item>
    <item>
      <title>🚀 Automating Email Image Requests with Asana + Zapier + BannerBear</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Thu, 12 Feb 2026 17:37:31 +0000</pubDate>
      <link>https://dev.to/uribejr/automating-email-image-requests-with-asana-zapier-bannerbear-89d</link>
      <guid>https://dev.to/uribejr/automating-email-image-requests-with-asana-zapier-bannerbear-89d</guid>
      <description>&lt;p&gt;Some companies operate multiple sub-brands under one marketing department.&lt;/p&gt;

&lt;p&gt;Each brand has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own identity
&lt;/li&gt;
&lt;li&gt;Its own templates
&lt;/li&gt;
&lt;li&gt;Its own dedicated graphic designers
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But most email campaigns follow repeatable patterns — flash sales, limited-time offers, announcements.&lt;/p&gt;

&lt;p&gt;Instead of having designers rebuild the same promo layouts every time, this system automates the repetitive production work while preserving brand control.&lt;/p&gt;

&lt;p&gt;Built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asana&lt;/strong&gt; → request form intake
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zapier&lt;/strong&gt; → routing + logic
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BannerBear&lt;/strong&gt; → image rendering
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firjzz93oxcor0skk1y1v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firjzz93oxcor0skk1y1v.jpg" alt="flow-sketch" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 The Goal
&lt;/h2&gt;

&lt;p&gt;This wasn’t about replacing designers.&lt;/p&gt;

&lt;p&gt;It was about removing repetitive promo production so designers can focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Campaign concepts
&lt;/li&gt;
&lt;li&gt;Brand evolution
&lt;/li&gt;
&lt;li&gt;High-impact creative
&lt;/li&gt;
&lt;li&gt;Strategic visual storytelling
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Designers build the master templates once.&lt;br&gt;&lt;br&gt;
Automation handles recurring production.&lt;/p&gt;

&lt;p&gt;That shift alone makes a huge difference.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏗 How It Works
&lt;/h2&gt;

&lt;p&gt;Here’s the full workflow from request to finished asset:&lt;/p&gt;
&lt;h3&gt;
  
  
  1️⃣ Marketing Submits a Form
&lt;/h3&gt;

&lt;p&gt;The marketing team fills out a structured form inside Asana.&lt;/p&gt;

&lt;p&gt;That form automatically creates a task in the &lt;strong&gt;Email Image Requests&lt;/strong&gt; project with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Division
&lt;/li&gt;
&lt;li&gt;Template type
&lt;/li&gt;
&lt;li&gt;Promo details
&lt;/li&gt;
&lt;li&gt;Copy
&lt;/li&gt;
&lt;li&gt;Any needed assets
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything starts from one intake system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbla71prebqcysjno9s2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbla71prebqcysjno9s2.png" alt="form-example" width="741" height="908"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  2️⃣ Designer Reviews the Request
&lt;/h3&gt;

&lt;p&gt;Each brand has a dedicated designer.&lt;/p&gt;

&lt;p&gt;The assigned designer reviews the task and ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy is correct
&lt;/li&gt;
&lt;li&gt;Template selection makes sense
&lt;/li&gt;
&lt;li&gt;Assets are attached
&lt;/li&gt;
&lt;li&gt;Everything is ready
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it’s approved, the designer moves the task to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Ready to Render”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tb6ocyn70p43si5nsrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tb6ocyn70p43si5nsrx.png" alt="ready-render-example" width="697" height="629"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  3️⃣ Moving to “Ready to Render” Triggers the Zap
&lt;/h3&gt;

&lt;p&gt;That section change triggers the automation.&lt;/p&gt;

&lt;p&gt;Zapier then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulls all task data
&lt;/li&gt;
&lt;li&gt;Checks the &lt;strong&gt;Division&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Checks the &lt;strong&gt;Template type&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Maps the task data to the correct BannerBear template &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j3mugdyxe0nifz7atji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j3mugdyxe0nifz7atji.png" alt="zap-example" width="800" height="735"&gt;&lt;/a&gt; &lt;/p&gt;


&lt;h3&gt;
  
  
  4️⃣ BannerBear Generates the Image
&lt;/h3&gt;

&lt;p&gt;Zapier sends the mapped data to BannerBear.&lt;/p&gt;

&lt;p&gt;BannerBear renders the image based on the correct brand template.&lt;/p&gt;

&lt;p&gt;No manual design work required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "template": "V32jY9bBM6l0DBGWrl",
  "modifications": [
    {
      "name": "BG_COLOR",
      "color": null
    },
    {
      "name": "TXT_HEADLINE",
      "text": "You can change this text",
      "color": null,
      "background": null
    },
    {
      "name": "CTA_COLOR",
      "color": null
    },
    {
      "name": "TXT_CTA",
      "text": "You can change this text",
      "color": null,
      "background": null
    },
    {
      "name": "TXT_BODY",
      "text": "You can change this text",
      "color": null,
      "background": null
    },
    {
      "name": "IMG_FEATURED",
      "image_url": "https://cdn.bannerbear.com/sample_images/welcome_bear_photo.jpg"
    },
    {
      "name": "TXT_SUBHEAD",
      "text": "You can change this text",
      "color": null,
      "background": null
    },
    {
      "name": "TXT_LEGAL",
      "text": "You can change this text",
      "color": null,
      "background": null
    }
  ],
  "webhook_url": null,
  "transparent": false,
  "metadata": null
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5️⃣ Zapier Updates the Task
&lt;/h3&gt;

&lt;p&gt;Once the image is created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zapier grabs the generated asset URL
&lt;/li&gt;
&lt;li&gt;Updates a custom field called &lt;strong&gt;“Created Asset”&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The image is ready to download directly from the task
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfb57jb47ozqh0ij7qck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfb57jb47ozqh0ij7qck.png" alt="image-assett-delivered-example" width="694" height="219"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;One intake system.&lt;br&gt;&lt;br&gt;
Multiple brand outputs.&lt;br&gt;&lt;br&gt;
Fully automated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vzbdtpz3htfay4ykfyo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vzbdtpz3htfay4ykfyo.png" alt="email-image-example" width="800" height="1266"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>nocode</category>
      <category>api</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Create your own avatar! 👾
I just added a Pixel Avatar Maker to this Windows 98–style portfolio — feel free to share yours in the comments.</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Sun, 25 Jan 2026 01:28:24 +0000</pubDate>
      <link>https://dev.to/uribejr/create-your-own-avatar-i-just-added-a-pixel-avatar-maker-to-this-windows-98-style-portfolio--1jm9</link>
      <guid>https://dev.to/uribejr/create-your-own-avatar-i-just-added-a-pixel-avatar-maker-to-this-windows-98-style-portfolio--1jm9</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-story__hidden-navigation-link"&gt;I Built a Windows 98–Style Desktop Portfolio with AI 💾&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;New Year, New You Portfolio Challenge Submission&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/uribejr" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" alt="uribejr profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/uribejr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Enrique Uribe
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Enrique Uribe
                
              
              &lt;div id="story-author-preview-content-3183607" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/uribejr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Enrique Uribe&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jan 19&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" id="article-link-3183607"&gt;
          I Built a Windows 98–Style Desktop Portfolio with AI 💾
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/googleaichallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;googleaichallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/portfolio"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;portfolio&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gemini"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gemini&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;21&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              6&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>devchallenge</category>
      <category>googleaichallenge</category>
      <category>portfolio</category>
      <category>gemini</category>
    </item>
    <item>
      <title>What was your first operating system?</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Tue, 20 Jan 2026 17:56:28 +0000</pubDate>
      <link>https://dev.to/uribejr/-16c3</link>
      <guid>https://dev.to/uribejr/-16c3</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/uribejr" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" alt="uribejr"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I Built a Windows 98–Style Desktop Portfolio with AI 💾&lt;/h2&gt;
      &lt;h3&gt;Enrique Uribe ・ Jan 19&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#devchallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#googleaichallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#portfolio&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#gemini&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>googleaichallenge</category>
      <category>portfolio</category>
      <category>gemini</category>
    </item>
    <item>
      <title>What was your first operating system?</title>
      <dc:creator>Enrique Uribe</dc:creator>
      <pubDate>Tue, 20 Jan 2026 17:55:03 +0000</pubDate>
      <link>https://dev.to/uribejr/what-was-your-first-operating-system-5f8a</link>
      <guid>https://dev.to/uribejr/what-was-your-first-operating-system-5f8a</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-story__hidden-navigation-link"&gt;I Built a Windows 98–Style Desktop Portfolio with AI 💾&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;New Year, New You Portfolio Challenge Submission&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/uribejr" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" alt="uribejr profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/uribejr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Enrique Uribe
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Enrique Uribe
                
              
              &lt;div id="story-author-preview-content-3183607" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/uribejr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993731%2F9b1659cd-644a-4612-900d-99c815fd152c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Enrique Uribe&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jan 19&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" id="article-link-3183607"&gt;
          I Built a Windows 98–Style Desktop Portfolio with AI 💾
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/googleaichallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;googleaichallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/portfolio"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;portfolio&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gemini"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gemini&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;21&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/uribejr/lets-build-with-ai-like-its-1998-41kl#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              6&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>devchallenge</category>
      <category>googleaichallenge</category>
      <category>portfolio</category>
      <category>gemini</category>
    </item>
  </channel>
</rss>
