<?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: Youngjun Yoon</title>
    <description>The latest articles on DEV Community by Youngjun Yoon (@cpcai_studio).</description>
    <link>https://dev.to/cpcai_studio</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%2F4243752%2Fd249ecb8-892e-488d-9b0d-16d21efdc8c0.jpg</url>
      <title>DEV Community: Youngjun Yoon</title>
      <link>https://dev.to/cpcai_studio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cpcai_studio"/>
    <language>en</language>
    <item>
      <title>The last 20% of an AI-built app: nine things that actually block launch</title>
      <dc:creator>Youngjun Yoon</dc:creator>
      <pubDate>Sat, 14 Nov 2026 06:56:51 +0000</pubDate>
      <link>https://dev.to/cpcai_studio/the-last-20-of-an-ai-built-app-nine-things-that-actually-block-launch-2806</link>
      <guid>https://dev.to/cpcai_studio/the-last-20-of-an-ai-built-app-nine-things-that-actually-block-launch-2806</guid>
      <description>&lt;p&gt;I keep hearing the same story from people who built something real with Cursor, Lovable, Bolt, v0 or Replit: the app exists, the demo works, and it has been "almost ready" for two months.&lt;/p&gt;

&lt;p&gt;The 20% they are missing is rarely a feature. It is a set of integration seams that a generator will happily write code around and never solve. Here are the nine that block launches most often, in the order I check them, with the symptom and the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Sessions that only exist in memory
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; you can log in, and a refresh logs you out. Or you can log in on desktop and not on the deployed domain, but it works on localhost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; the auth code stores a token in a module-level variable, or sets a cookie without &lt;code&gt;Secure&lt;/code&gt;/&lt;code&gt;SameSite&lt;/code&gt;, or signs with a secret that is regenerated on each cold start because it is generated at boot instead of read from the environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; persist the session in an &lt;code&gt;HttpOnly&lt;/code&gt;, &lt;code&gt;Secure&lt;/code&gt;, &lt;code&gt;SameSite=Lax&lt;/code&gt; cookie (or a refresh token in storage for mobile), keep &lt;code&gt;SESSION_SECRET&lt;/code&gt; in the environment, and test after a hard reload, a tab restore, and a cold start. If your serverless function signs the token, it must use a stored secret — the same one, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Auth that trusts the client
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; everything works, and any user can read another user's records by changing an id.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; the generated code checks &lt;code&gt;if (user)&lt;/code&gt; in the UI and never checks ownership on the server. With a hosted database, the row-level rules were never written at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; every read and write on the server (or in the database rules) must be scoped to the authenticated subject. Not &lt;code&gt;WHERE id = $1&lt;/code&gt; but &lt;code&gt;WHERE id = $1 AND user_id = $subject&lt;/code&gt;. On Postgres with row-level security, the policy is the product; test it with two accounts, not one.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Payments that only pass in test mode
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Stripe checkout redirects and returns, but the order never appears, or appears twice, or appears in test mode and not in live mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; the "success" page is treated as proof of payment. No webhook, no signature verification, no idempotency key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; the redirect page is for the user, not for the database. Grant access in a webhook handler that verifies the signature against the raw body, and write the event id before doing anything so a retried delivery cannot double-credit. Test with the Stripe CLI, then in live mode with a real card and a real refund.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Environment variables that only exist on your machine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; the deploy builds, then the first request that touches a third-party API returns 401 or &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; &lt;code&gt;.env&lt;/code&gt; is not committed (good), the platform's environment is empty (bad), and the code fails at the first network call instead of at boot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; validate the environment at startup and crash loudly if something is missing. Keep a &lt;code&gt;.env.example&lt;/code&gt; in the repo. Treat "works locally" as unverified until it works on the deployed URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Database schema without migrations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; you changed a column two weeks ago and the deployed database does not have it. Nobody knows what the live schema is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; the schema was applied by hand, or by a generator that ran once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; one source of truth, applied by a migration tool, run automatically in deploy. Seed data separate from schema. This is also the step that makes a staging environment possible at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. CORS, cookies and the domain triangle
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; the API works from &lt;code&gt;localhost:3000&lt;/code&gt; and fails from &lt;code&gt;https://app.yourdomain.com&lt;/code&gt; with a CORS error or a dropped cookie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; the API allows &lt;code&gt;*&lt;/code&gt;, and a wildcard origin cannot carry credentials. Or the cookie was set on the API's host and the front end lives on another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; allow-list exact origins, &lt;code&gt;credentials: include&lt;/code&gt; on the client, &lt;code&gt;Access-Control-Allow-Credentials: true&lt;/code&gt; plus an explicit origin on the server, and put front end and API on the same registrable domain when you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Deploy that is not a deploy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; the preview URL works, the custom domain shows an old build, &lt;code&gt;www&lt;/code&gt; and the apex behave differently, and half the traffic is HTTP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; DNS records were added once and never verified, the platform is serving a cached build, and SSL was issued for one hostname only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; decide the canonical host, redirect everything else to it, force HTTPS, verify both apex and &lt;code&gt;www&lt;/code&gt;, and check a hard reload on a phone on cellular data — not just your laptop on the same network.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. No error visibility
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; a user says "it broke". You have nothing. No stack trace, no request, no timestamp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; generated code has &lt;code&gt;console.log&lt;/code&gt; and no error boundary, no server logging, no alert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; a client error boundary that reports, server logging with request ids, and one alert channel you actually read. You cannot operate what you cannot see, and "operate" is the difference between a demo and a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Mobile reality
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; fine in a desktop browser, broken on a phone: keyboard covers the input, fixed footers overlap the content, 100vh is wrong, taps are 12px tall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt; layout was never checked on a device. &lt;code&gt;100vh&lt;/code&gt; was used instead of dynamic viewport units, and safe areas were ignored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; test on a real phone with the keyboard open. Use &lt;code&gt;dvh&lt;/code&gt;, &lt;code&gt;env(safe-area-inset-*)&lt;/code&gt;, and touch targets of at least 44px. Then test a slow connection, because your users are not on your Wi-Fi.&lt;/p&gt;

&lt;h2&gt;
  
  
  If the product is a mobile app, add a tenth
&lt;/h2&gt;

&lt;p&gt;Store review is its own integration seam: privacy manifest, permission strings, a real account-deletion path, and a build that does not depend on a debug keystore. Most rejections I see are not about the product at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why generated code stalls exactly here
&lt;/h2&gt;

&lt;p&gt;A generator is excellent at the parts that are visible in a single file and weak at the parts that span files, environments and time: secrets, webhooks, migrations, domains, ownership checks. None of those are hard individually. All of them are invisible until you try to ship, and each one looks like "just one more bug".&lt;/p&gt;

&lt;p&gt;That is why the honest answer to "how long until launch?" is not a feature list. It is a checklist of seams, each verified in the environment it will actually run in.&lt;/p&gt;

&lt;p&gt;I do this for a living now. I take over half-finished AI-generated codebases as they are, run a free review against this list, and quote only the work that is actually blocking launch — then finish it through deploy and store release, usually in three to five days.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checklist-based free review of your repo: &lt;strong&gt;&lt;a href="https://cpcai-studio.netlify.app/en/" rel="noopener noreferrer"&gt;https://cpcai-studio.netlify.app/en/&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;I write about operating a one-person studio at &lt;strong&gt;&lt;a href="https://dev.to/cpcai_studio"&gt;https://dev.to/cpcai_studio&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only take one thing from this: stop asking "what feature is missing?" and start asking "which seam has never been verified outside my laptop?" That question is what a launch is made of.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Turn-based multiplayer that survives bad networks: the state model I use in Flutter</title>
      <dc:creator>Youngjun Yoon</dc:creator>
      <pubDate>Sat, 14 Nov 2026 04:08:43 +0000</pubDate>
      <link>https://dev.to/cpcai_studio/turn-based-multiplayer-that-survives-bad-networks-the-state-model-i-use-in-flutter-8i0</link>
      <guid>https://dev.to/cpcai_studio/turn-based-multiplayer-that-survives-bad-networks-the-state-model-i-use-in-flutter-8i0</guid>
      <description>&lt;p&gt;I ship a turn-based artillery game — one player shoots, the other waits, then they swap. Turn-based sounds like the easy version of multiplayer. It is not; it just moves the difficulty from frame timing to &lt;em&gt;state&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here is the model I ended up with after shipping to Android and iOS, with a small player base on mobile networks that drop constantly. It is not clever. It is boring on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Sync inputs, not state
&lt;/h2&gt;

&lt;p&gt;The temptation is to send the world state after every turn: tank positions, HP, terrain deltas. That works until two clients disagree by a rounding error and then diverge forever.&lt;/p&gt;

&lt;p&gt;Instead the server keeps an append-only &lt;strong&gt;event log&lt;/strong&gt;: &lt;code&gt;shot(angle, power, itemUsed)&lt;/code&gt;, &lt;code&gt;surrender&lt;/code&gt;, &lt;code&gt;turnTimeout&lt;/code&gt;. Each client replays the log locally and computes the outcome itself.&lt;/p&gt;

&lt;p&gt;Two benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payload is tiny (a shot is a few dozen bytes, not a terrain diff).&lt;/li&gt;
&lt;li&gt;A reconnecting client can be caught up by replaying events, not by rebuilding the world.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Determinism is the contract, so remove everything that isn't
&lt;/h2&gt;

&lt;p&gt;The event log only works if every device computes the same result from the same event. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed timestep physics.&lt;/strong&gt; Never integrate with &lt;code&gt;delta&lt;/code&gt; from the frame clock — a 120Hz phone and a throttled one must step the same number of times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seeded RNG.&lt;/strong&gt; Wind, spread, and any crit roll come from a seed stored in the match, indexed by turn number. No &lt;code&gt;Random()&lt;/code&gt; anywhere in gameplay code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No time-based effects.&lt;/strong&gt; A "3 second fuse" is 180 fixed steps, not &lt;code&gt;now + 3s&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I enforce this with a single &lt;code&gt;deterministic&lt;/code&gt; lint rule and a scripted match replay in CI: same log in, same log out.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Terrain is content plus deltas
&lt;/h2&gt;

&lt;p&gt;Destructible terrain is where determinism breaks quietly, because terrain edits accumulate. I keep the base map as static content (a heightmap per map id) and store destruction as a small list of crater events. Crater radius and falloff are constants, so the resulting heightmap is identical everywhere.&lt;/p&gt;

&lt;p&gt;This also makes replays cheap — a match is a map id, a seed, and a list of events.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reconnect: ask for what you missed
&lt;/h2&gt;

&lt;p&gt;On reconnect the client sends its last applied sequence number. The server replies with the events after it. Because the client can recompute anything from the log, a full world snapshot is only needed when the gap is huge (I use 200 events, which never happens in a 5-minute match).&lt;/p&gt;

&lt;p&gt;No "resync state" code path means no second source of truth to keep in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Shots must be idempotent
&lt;/h2&gt;

&lt;p&gt;Mobile clients time out and retry. Without protection, a retry fires the same shot twice — which players absolutely notice.&lt;/p&gt;

&lt;p&gt;Every action carries a client-generated &lt;code&gt;eventId&lt;/code&gt;. The server rejects a duplicate &lt;code&gt;eventId&lt;/code&gt; and returns the original result. The client then treats "already applied" as success. This one rule removed the entire class of double-fire bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. What I deliberately gave up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No rollback netcode.&lt;/strong&gt; Turn-based does not need it; only one player acts at a time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No host migration.&lt;/strong&gt; The server is the authority from turn zero, so there is no host to lose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No real-time interpolation.&lt;/strong&gt; The shell animation is cosmetic; the result is already decided by the log.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Test it without humans
&lt;/h2&gt;

&lt;p&gt;The most useful thing I built is not the netcode, it is the harness: a bot that plays full matches offline through the same event path as a real client, on a simulated 400ms / 5% packet loss link. It found three determinism leaks that no amount of manual testing found.&lt;/p&gt;




&lt;p&gt;If you are building something turn-based, start with the log. The netcode gets simpler every time you remove a source of truth.&lt;/p&gt;

&lt;p&gt;I build and ship apps, games, and client MVPs from Seoul as a one-person studio: &lt;a href="https://cpcai-studio.netlify.app/en/" rel="noopener noreferrer"&gt;https://cpcai-studio.netlify.app/en/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>gamedev</category>
      <category>architecture</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Shipping a production-ready MVP in 3-5 days as a solo developer: the pipeline I actually use</title>
      <dc:creator>Youngjun Yoon</dc:creator>
      <pubDate>Sat, 14 Nov 2026 01:55:45 +0000</pubDate>
      <link>https://dev.to/cpcai_studio/shipping-a-production-ready-mvp-in-3-5-days-as-a-solo-developer-the-pipeline-i-actually-use-4i9b</link>
      <guid>https://dev.to/cpcai_studio/shipping-a-production-ready-mvp-in-3-5-days-as-a-solo-developer-the-pipeline-i-actually-use-4i9b</guid>
      <description>&lt;p&gt;I run a one-person software company. No team, no agency, no project manager — just me and a terminal. People usually assume that means small scope: a landing page, a prototype, something that "works for now".&lt;/p&gt;

&lt;p&gt;In practice the last few months shipped a crypto market-analysis web app, a cross-platform turn-based artillery game (Android + iOS + a Toss mini-app), and client MVPs for small businesses. Client projects go from kickoff to a deployed URL in 3–5 days.&lt;/p&gt;

&lt;p&gt;This is the pipeline that makes that possible — the parts that actually matter, and the parts people get wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Define the one loop the product must close
&lt;/h2&gt;

&lt;p&gt;Before touching code I write one sentence: "A user can do X, get Y, and come back." Everything else is out of scope.&lt;/p&gt;

&lt;p&gt;For a client MVP the first loop is usually: sign up → do the one valuable action → see the result saved. If a feature does not touch that loop, it goes on the v2 list. Being able to say no to scope on day one is the biggest speed factor — bigger than any framework choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The stack I reuse instead of rebuilding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js (App Router) + Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; FastAPI (Python) when there is data, scraping or AI work; Next.js route handlers when there is not&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DB + auth:&lt;/strong&gt; Supabase (Postgres, row-level security, email and OAuth auth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile:&lt;/strong&gt; Flutter for cross-platform builds, or a Toss mini-app when the user will never install an app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background work:&lt;/strong&gt; one Playwright / ffmpeg worker process, not a queue cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every choice here exists to remove a decision. I do not want to spend day two comparing auth providers; I want to spend it on the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Deploy on day one, not day five
&lt;/h2&gt;

&lt;p&gt;The first deploy happens before the second feature: empty page, real domain, environment variables wired. Deploying early turns integration risk into a non-event.&lt;/p&gt;

&lt;p&gt;I also keep a fixed layout shell per project type — dashboard, marketing page plus app, admin table. The client clicks a real URL on day one, and the conversation moves from "when will it be done" to "what should this screen say".&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Automate the work a one-person team cannot afford to repeat
&lt;/h2&gt;

&lt;p&gt;A solo team cannot absorb meetings and manual repetition, so the boring parts get scripted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data collection:&lt;/strong&gt; Playwright scripts with retries and conservative throttling, writing into a typed schema. Respecting rate limits is cheaper than fighting them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content pipeline:&lt;/strong&gt; for the crypto app, a confirmed headline triggers an HTML template render to a 27-second vertical video through ffmpeg — unattended, from headline to final file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release:&lt;/strong&gt; push, build, deploy to Netlify or Vercel. One command, same for me and the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule: if I click the same thing twice in a week, it becomes a script.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Where AI agents actually help, and where they do not
&lt;/h2&gt;

&lt;p&gt;I use an agent for the well-specified, parallel work: writing the CRUD layer for a schema I designed, turning an API response into typed models, drafting a migration, generating form UI, and a first pass at release notes. That is real time saved.&lt;/p&gt;

&lt;p&gt;Agents are weak exactly where the product is decided — the data model, what the first loop is, which edge case will hurt the client's business. Handing those off is how you get a fast, wrong product.&lt;/p&gt;

&lt;p&gt;Two rules I keep:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give the agent the schema and the exact file, not the whole repo and a wish.&lt;/li&gt;
&lt;li&gt;Never merge output you cannot explain line by line to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. The last 10% is the whole product
&lt;/h2&gt;

&lt;p&gt;A deployable MVP is not a shippable one. The 3–5 day budget always keeps a fixed block for: error states, empty states, a real 404, loading skeletons, one scheduled database backup, password reset, and a smoke test right after deploy. Clients never notice when these exist. They notice within a minute when they do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;It is not "AI builds your app". It is a small set of boring decisions reused until they are fast, with automation around the parts a human should not repeat. The speed comes from refusing to re-decide things that are already decided.&lt;/p&gt;

&lt;p&gt;I run this as a one-person studio in Seoul — case studies and what I build for clients are at &lt;a href="https://cpcai-studio.netlify.app/en/" rel="noopener noreferrer"&gt;https://cpcai-studio.netlify.app/en/&lt;/a&gt;. If you are sitting on a half-built repo and a deadline, ask in the comments; I am happy to talk specifics about scope, stack or estimates.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
