<?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>Forem</title>
    <description>The most recent home feed on Forem.</description>
    <link>https://forem.com</link>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed"/>
    <language>en</language>
    <item>
      <title>Why Your App Needs a Loading Skeleton (Not a Spinner)</title>
      <dc:creator>Shefali</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:26:49 +0000</pubDate>
      <link>https://forem.com/devshefali/why-your-app-needs-a-loading-skeleton-not-a-spinner-4f7a</link>
      <guid>https://forem.com/devshefali/why-your-app-needs-a-loading-skeleton-not-a-spinner-4f7a</guid>
      <description>&lt;p&gt;Open any app. Something is loading. What do you see?&lt;/p&gt;

&lt;p&gt;If it’s a spinner, it’s just a circle going round and round. You don’t know what’s loading, how much is left, or how long it’ll take. You just wait.&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%2Fvxtel8z8ai1a2gzsp230.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%2Fvxtel8z8ai1a2gzsp230.gif" alt="stupid loading reloading frozen" width="498" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now imagine this instead: the layout shows up instantly. You see grey boxes where content will be, a circle for the avatar, lines for text. A subtle shimmer runs across them. Then the real content loads in.&lt;/p&gt;

&lt;p&gt;Same loading time. But it feels much better.&lt;/p&gt;

&lt;p&gt;That’s exactly what skeleton screens does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Skeleton Screens?
&lt;/h2&gt;

&lt;p&gt;Skeleton screens are simple placeholders that shows the shape of the content before it loads.&lt;/p&gt;

&lt;p&gt;Instead of a spinner, users see a basic layout of the page, even before the real data appears.&lt;/p&gt;

&lt;p&gt;Here’s what it feels like:&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%2Fl8283dofq5bqjn6j48zi.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%2Fl8283dofq5bqjn6j48zi.gif" alt="Loading Skeleton Screen" width="760" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of showing a spinner or a “Loading…” message, the interface displays a preview of the layout, and that small change makes the experience feel much smoother.&lt;/p&gt;

&lt;p&gt;Now that we understand what it is, let’s see why spinners don’t always work well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Spinners
&lt;/h2&gt;

&lt;p&gt;Spinners aren’t bad. &lt;/p&gt;

&lt;p&gt;They work well for quick actions like submitting a form, clicking a button, or uploading a file. In those cases, the user initiated something and just needs a simple signal that it’s in progress.&lt;/p&gt;

&lt;p&gt;The issue starts when spinners become the default for every loading state. Page load, feed refresh, dashboard data, everything shows the same spinning indicator.&lt;/p&gt;

&lt;p&gt;The problem is that a spinner gives no useful information. It doesn’t tell users what is loading, how much is left, or what to expect when it’s done. It simply shows that something is happening, without any context.&lt;/p&gt;

&lt;p&gt;And that leads to the real UX issue: uncertainty. &lt;/p&gt;

&lt;p&gt;When users don’t know what’s going on, it becomes frustrating very quickly. A spinner creates that uncertainty, while a skeleton screen reduces it by showing the structure of the content in advance, helping users understand what they are waiting for even before the data arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Skeleton Screens Feel Faster (Even When They're Not)
&lt;/h2&gt;

&lt;p&gt;Here’s something interesting: skeleton screens don’t actually make your app load faster. The data still takes the same amount of time to arrive.&lt;/p&gt;

&lt;p&gt;And yet, users often feel like the app is faster.&lt;/p&gt;

&lt;p&gt;This is the concept of &lt;strong&gt;perceived performance&lt;/strong&gt;, which is about how fast something feels, not just how fast it actually is. In many cases, that feeling matters just as much, because user experience is shaped by perception.&lt;/p&gt;

&lt;p&gt;When a skeleton screen appears, your brain starts forming a mental picture of the page. You can already see where things will go, so you begin to anticipate the content. By the time it loads, it feels less like waiting and more like something gradually revealing itself.&lt;/p&gt;

&lt;p&gt;With a spinner, there’s nothing to engage with. With a skeleton, there’s something to expect, and that shift changes the entire experience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;This isn’t just theory:&lt;/strong&gt; Apps like YouTube, LinkedIn, Facebook, GitHub, and Airbnb all use skeleton screens for content-heavy experiences.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;These companies have already tested different approaches and looked at the data. Their decision isn’t based on trends or aesthetics, but on what actually improves user experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When you see the same pattern across so many large products, it’s a strong signal that skeleton screens work.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you see this, the difference becomes much easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spinners vs Skeletons
&lt;/h2&gt;

&lt;p&gt;Here's a simple comparison to understand why skeleton screens feel better than spinners.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Spinner&lt;/th&gt;
&lt;th&gt;Skeleton&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shows what’s loading&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feels faster&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shows page structure&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User frustration&lt;/td&gt;
&lt;td&gt;More&lt;/td&gt;
&lt;td&gt;Less&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So when should you use each one?&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Skeleton Screens vs Spinners
&lt;/h2&gt;

&lt;p&gt;It’s not about avoiding spinners completely, but about choosing what fits the situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a skeleton screen&lt;/strong&gt; when you’re loading a full page, a list, a feed, a dashboard, or any section where the layout is predictable and there’s a lot of content coming in. In these cases, users are waiting for a whole view to appear, so showing the structure helps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a spinner&lt;/strong&gt; when the user performs a quick action like submitting a form, clicking a button, or deleting something. In these moments, there isn’t a layout to show, and the spinner simply tells the user that the action is in progress.&lt;/p&gt;

&lt;p&gt;Once you start using skeletons, a few small things can improve the experience a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Skeleton Screens
&lt;/h2&gt;

&lt;p&gt;Here are a few best practices to make skeleton screens actually helpful, not confusing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Match the real layout:&lt;/strong&gt; Your skeleton should look like the actual content. When the shapes and structure are similar, the transition feels smooth. If it looks completely different, it can confuse users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't overdo it:&lt;/strong&gt; Showing a few skeleton cards is helpful, but showing too many at once can feel overwhelming. For long lists, it’s better to combine skeletons with pagination or lazy loading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always have an error state:&lt;/strong&gt; If the data fails to load, don’t keep showing the skeleton forever. Replace it with a clear and friendly error message so users know what went wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility matters:&lt;/strong&gt; Some users are sensitive to motion, so constant shimmer effects can be uncomfortable. Good implementations respect reduced motion settings and adjust automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with all this, performance still matters behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Tip
&lt;/h2&gt;

&lt;p&gt;Skeleton screens make your app feel faster, but they don’t replace real performance improvements.&lt;/p&gt;

&lt;p&gt;The best approach is to do both: reduce the actual loading time and use a skeleton for whatever delay is left. A skeleton can make a slow load feel better, but when the load is already fast, it can make the experience feel almost instant.&lt;/p&gt;

&lt;p&gt;Here are a few quick wins that work well alongside skeleton screens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache aggressively:&lt;/strong&gt; If a user has visited the page before, show cached data right away and update it in the background. In many cases, this means the skeleton won’t appear at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritise above-the-fold content:&lt;/strong&gt; Load only what’s visible on the screen first, and fetch the rest as the user scrolls. This helps the most important content appear quickly, making the skeleton disappear sooner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid waterfall requests:&lt;/strong&gt; If your page makes multiple API calls one after another, the wait time adds up. Try to run requests in parallel so everything loads faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A skeleton gives you a smoother waiting experience, but it works best when you also make that waiting time as short as possible.&lt;/p&gt;

&lt;p&gt;Now let’s quickly look at how you can start using them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;p&gt;You don’t need to build skeleton screens from scratch. There are good libraries for every major framework that handle animation, sizing, and accessibility for you. You can pick one based on your stack and get started quickly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/dvtng/react-loading-skeleton" rel="noopener noreferrer"&gt;react-loading-skeleton&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;td&gt;A popular and easy option with ready-to-use components that work with minimal setup.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/egoist/vue-content-loader" rel="noopener noreferrer"&gt;vue-content-loader&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Vue&lt;/td&gt;
&lt;td&gt;Uses SVG, which makes it very flexible and lightweight for custom designs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/danilowoz/react-content-loader" rel="noopener noreferrer"&gt;content-loader&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;A framework-agnostic option that works with React, Vue, Svelte, or even plain JavaScript.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://tailwindcss.com/docs/animation#adding-a-pulse-animation" rel="noopener noreferrer"&gt;animate-pulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Tailwind&lt;/td&gt;
&lt;td&gt;If you’re already using Tailwind, you can create skeletons without adding any extra dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The concept is the same across all of them: show the skeleton while data is loading, swap it for real content when it arrives. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Spinners aren’t bad, but they’re often used in places where better options exist. For content-heavy screens, skeletons usually provide a much smoother and more helpful experience.&lt;/p&gt;

&lt;p&gt;Skeleton screens are simple to add, don’t take much effort to implement, and can instantly improve how your app feels to users.&lt;/p&gt;

&lt;p&gt;The next time you reach for a spinner, take a moment to think if you can show the structure of the content instead. In many cases, giving users a preview of what’s coming will make the experience feel faster and more intuitive.&lt;/p&gt;




&lt;p&gt;That’s all for today!&lt;/p&gt;

&lt;p&gt;I hope you find this blog on loading skeleton screens helpful!&lt;/p&gt;

&lt;p&gt;For paid collaboration, connect with me at: &lt;a href="mailto:connect@shefali.dev"&gt;connect@shefali.dev&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If you enjoy my work and want to support what I do &lt;a href="https://buymeacoffee.com/devshefali" rel="noopener noreferrer"&gt;buy me a coffee&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every small gesture keeps me going! 💛&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://x.com/Shefali__J" rel="noopener noreferrer"&gt;Follow me on X (Twitter)&lt;/a&gt; to get daily web development tips &amp;amp; insights.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ux</category>
      <category>uxdesign</category>
      <category>skeleton</category>
    </item>
    <item>
      <title>Stop building custom auth for your B2B SaaS: Meet Gate Identity</title>
      <dc:creator>Max Zhuk</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:22:36 +0000</pubDate>
      <link>https://forem.com/zhukmax/stop-building-custom-auth-for-your-b2b-saas-meet-gate-identity-259m</link>
      <guid>https://forem.com/zhukmax/stop-building-custom-auth-for-your-b2b-saas-meet-gate-identity-259m</guid>
      <description>&lt;p&gt;Hello, fellow developers! 🧑‍💻&lt;/p&gt;

&lt;p&gt;If you’ve ever built a B2B SaaS, you know the drill. Building the core business logic is the fun part. But then you hit the authentication layer. Suddenly, you're not just writing a simple login form; you have to figure out multi-tenancy, data isolation, routing social logins for different workspaces, and managing secure tokens. It's a massive time sink that distracts you from shipping your actual product.&lt;/p&gt;

&lt;p&gt;I’ve been there. Honestly, I was tired of reinventing the wheel or fighting with overly complex enterprise identity providers just to get a proper B2B auth running. I wanted an API-first approach that was fast, reliable, and didn't require a Ph.D. in security to set up.&lt;/p&gt;

&lt;p&gt;That’s why I built &lt;strong&gt;Gate Identity&lt;/strong&gt; (as part of my &lt;a href="https://vernesoft.com/" rel="noopener noreferrer"&gt;Verne Software&lt;/a&gt; ecosystem) — a developer-first 🔐 Auth-as-a-Service. It takes the pain out of multi-tenant authentication, allowing you to plug it in and get back to building your app. And the best part? The core functionality is already up and running, and you can use it for your next project completely for free (with a generous free tier for early-stage projects).&lt;/p&gt;

&lt;p&gt;In this article, I’ll show you what’s under the hood of Gate Identity, what features you can start using right now, and share my roadmap for what's coming next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Under the Hood: The Architecture and Tech Stack
&lt;/h3&gt;

&lt;p&gt;As engineers, we all love peeking under the hood. When I started designing Gate Identity, I followed one golden rule: never write your own core security and cryptography logic. I didn't want to build an identity provider from scratch; I wanted to build a robust, multi-tenant layer on top of a battle-tested open-source engine.&lt;/p&gt;

&lt;p&gt;Here is how the system is wired together to make that happen:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Core Engine: Ory Kratos&lt;/strong&gt;&lt;br&gt;
At the heart of Gate Identity is &lt;strong&gt;Ory Kratos&lt;/strong&gt;. If you haven't used it, Kratos is a phenomenal API-first identity server written in Go. It handles the heavy lifting: complex authentication flows, secure credential storage, and standard OIDC integrations. However, out of the box, Kratos doesn't natively solve B2B multi-tenancy the way a SaaS needs it to. It needs a smart gateway to manage who belongs to which workspace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Edge Gateway (Built with Rust 🦀)&lt;/strong&gt;&lt;br&gt;
This is where the magic happens. All incoming traffic is terminated by &lt;strong&gt;Traefik&lt;/strong&gt;, which acts as the main reverse proxy. But right behind it sits the custom &lt;strong&gt;Edge Gateway&lt;/strong&gt; (edge_gw), which I wrote entirely in &lt;strong&gt;Rust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why Rust? Because an API gateway needs to be blazingly fast, concurrent, and memory-safe. Every single authentication request passes through this node. The Rust gateway acts as the strict bouncer for the multi-tenant architecture:&lt;/p&gt;

&lt;p&gt;It parses and validates your access tokens (gat_live_{hex}).&lt;/p&gt;

&lt;p&gt;It checks the tenant-ownership (e.g., "Does this specific subject ID actually belong to the tenant making the API call?").&lt;/p&gt;

&lt;p&gt;It handles rate limiting and routing, safely proxying the validated requests down to Kratos or the Core API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. State and Data Isolation (PostgreSQL + Redis)&lt;/strong&gt;&lt;br&gt;
A proper B2B auth system must keep tenant data strictly isolated. To achieve this without bottlenecks, I split the storage layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity Data&lt;/strong&gt;: Stored in a dedicated 🐘 PostgreSQL database managed entirely by Kratos. Here, I use distinct JSON schemas to separate roles: &lt;code&gt;user&lt;/code&gt; (your tenant's end-users) and &lt;code&gt;tenant&lt;/code&gt; (you, the developers using Gate).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.vernesoft.com/v1/gate/identities &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer vrn_gate_live_sk_9f8a7...'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "schema_id": "user",
    "traits": {
      "email": "user@example.com",
      "custom_data": { "role": "editor" }
    },
    "credentials": {
      "password": { "config": { "password": "StrongPassword123!" } }
    },
    "state": "active"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Business Logic &amp;amp; Profiles&lt;/strong&gt;: Stored in the Core API’s PostgreSQL database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flow States &amp;amp; Tokens&lt;/strong&gt;: Handled by &lt;strong&gt;Redis&lt;/strong&gt;. When a user starts a login or settings flow, the state is cached in Redis with a strict mapping (&lt;code&gt;gate:flow:{id} -&amp;gt; tenant_id&lt;/code&gt;). This allows the Rust gateway to instantly verify if a user is trying to access a flow that belongs to their specific workspace, effectively preventing cross-tenant data leaks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining the raw speed of Rust at the edge with the security standards of Ory Kratos at the core, Gate Identity manages to isolate tenants efficiently without adding noticeable latency to your API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Can Use Right Now
&lt;/h3&gt;

&lt;p&gt;Enough with the architectural theory—let's look at what you can actually plug into your product today. I built Gate Identity aiming to cover 90% of a typical B2B SaaS's needs right from the start, without forcing you to write boilerplate code.&lt;/p&gt;

&lt;p&gt;Here is what is running in production right now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Authentication&lt;/strong&gt;&lt;br&gt;
The classic, reliable email and password flow. Kratos handles the secure hashing and credential storage, while the Edge Gateway ensures that sessions are correctly bound to the specific workspace (tenant).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Highly Configurable Social Logins (OIDC)&lt;/strong&gt;&lt;br&gt;
B2B clients often demand corporate account logins. Out of the box, 11 providers are supported, including Google, GitHub, Apple, Microsoft, and Discord. But here is the killer feature: each of your tenants can individually enable or disable specific providers just for their own workspace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Painless Token Management&lt;/strong&gt;&lt;br&gt;
No more messing around with complex client-side JWTs. Gate Identity issues secure, opaque access tokens (in the &lt;code&gt;gat_live_{hex}&lt;/code&gt; format). They support scopes and configurable lifespans. There is also a ready-to-use introspection API, so your backend can instantly verify their validity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tenant-Isolated Identity CRUD API&lt;/strong&gt;&lt;br&gt;
You get full control over users via a REST API. You can programmatically create, update, and delete identities without ever worrying about cross-tenant data leaks. My gateway automatically verifies &lt;code&gt;tenant-ownership&lt;/code&gt;—the system simply won't let a request through if you try to access a user from someone else's workspace.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Roadmap: What’s Coming Next
&lt;/h3&gt;

&lt;p&gt;Gate Identity is actively evolving. While the core API handles the heavy lifting right now, I am constantly working on unlocking more of Ory Kratos' advanced capabilities and routing them safely through the Rust Edge Gateway.&lt;/p&gt;

&lt;p&gt;Here is what is on the immediate horizon:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Self-Service (UX)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Settings Flow (Critical Priority): End-users will soon be able to natively manage their own profiles, update passwords, and link social accounts. This means you won't have to build custom account management UIs from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recovery &amp;amp; Verification: API-driven password resets and programmable email verification flows to make user onboarding completely seamless.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Enterprise-Grade Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MFA / TOTP: Multi-factor authentication using authenticator apps is a massive priority, especially if you are targeting enterprise B2B clients.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Passwordless (OTP): Support for One-Time Passwords via email for frictionless, consumer-like login experiences.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Session Management: Giving both tenant admins and end-users the ability to view and instantly revoke active sessions across all devices.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advanced Developer Experience (DX)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Tenant Webhooks: This is the most anticipated feature for developers. Soon, you will be able to subscribe to real-time events (e.g., &lt;code&gt;user.created&lt;/code&gt;, &lt;code&gt;user.deleted&lt;/code&gt;, &lt;code&gt;password.changed&lt;/code&gt;) to instantly sync identity data with your core application's database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom Identity Metadata: I will be exposing Kratos' &lt;code&gt;metadata_public&lt;/code&gt; field. This will allow you to attach arbitrary JSON data—like user roles, billing plans, or phone numbers—directly to the identity object, managed entirely via API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stop Building, Start Shipping
&lt;/h3&gt;

&lt;p&gt;Building an authentication and multi-tenancy layer from scratch is almost never a competitive advantage for your business. It is a necessary evil.&lt;/p&gt;

&lt;p&gt;By leveraging a battle-tested open-source engine like Ory Kratos and wrapping it in a blazingly fast, secure Rust gateway, Gate Identity gives you enterprise-grade isolation out of the box. You get to skip the infrastructure headache and go straight to building your core product.&lt;/p&gt;

&lt;p&gt;You can check out &lt;a href="https://docs.vernesoft.com/" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; and try it out for your next project right now. As promised, the core features are available for free with a generous tier designed specifically for early-stage and indie projects.&lt;/p&gt;

&lt;p&gt;I’d love to hear your thoughts! What authentication feature do you usually struggle with the most when starting a new B2B SaaS? Are webhooks, MFA, or social logins your biggest blocker? Let's discuss in the comments below!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>devops</category>
      <category>startup</category>
    </item>
    <item>
      <title>BDO Scraper: Rejestr Odpadów (674 000+)</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:22:29 +0000</pubDate>
      <link>https://forem.com/ucptools/bdo-scraper-rejestr-odpadow-674-000-2ikc</link>
      <guid>https://forem.com/ucptools/bdo-scraper-rejestr-odpadow-674-000-2ikc</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BDO (Baza danych o produktach i opakowaniach oraz o gospodarce odpadami)&lt;/strong&gt; to oficjalny rejestr gospodarki odpadami z &lt;strong&gt;674 000+ zarejestrowanych podmiotów&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Każda firma produkująca, transportująca lub przetwarzająca odpady w Polsce musi być zarejestrowana&lt;/li&gt;
&lt;li&gt;Portal to React SPA bez publicznego API&lt;/li&gt;
&lt;li&gt;Zbudowałem &lt;a href="https://apify.com/minute_contest/bdo-waste-registry-scraper" rel="noopener noreferrer"&gt;aktora na Apify&lt;/a&gt;, który przeszukuje rejestr i zwraca JSON za $0.03 za podmiot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dlaczego rejestr BDO ma znaczenie dla firm w Polsce
&lt;/h2&gt;

&lt;p&gt;BDO to centralna baza danych służąca do śledzenia całego cyklu życia odpadów - od wytworzenia, przez transport, aż po przetworzenie lub składowanie. Rejestr został utworzony na podstawie ustawy o odpadach i jest administrowany przez Urzędy Marszałkowskie poszczególnych województw.&lt;/p&gt;

&lt;p&gt;Rejestracja w BDO jest obowiązkowa dla szerokiego zakresu firm: producentów wytwarzających odpady przemysłowe, firm transportujących odpady, zakładów recyklingowych i przetwarzania, importerów produktów objętych regulacjami opakowaniowymi oraz firm wprowadzających towary w opakowaniach na polski rynek. Na 2026 rok rejestr zawiera ponad 674 000 podmiotów - co czyni go jedną z największych baz compliance środowiskowego w Europie Środkowej.&lt;/p&gt;

&lt;p&gt;Praktyczne konsekwencje są poważne. Firmy, które zlecają transport lub przetwarzanie odpadów niezarejestrowanym podmiotom, podlegają karom od 1 000 do 1 000 000 PLN zgodnie z polskim prawem ochrony środowiska. Dla organizacji zarządzających odpadami na wielu lokalizacjach lub współpracujących z dziesiątkami kontrahentów, weryfikacja statusu rejestracji BDO to rutynowy wymóg compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dlaczego portal BDO nie ma publicznego API
&lt;/h2&gt;

&lt;p&gt;Portal BDO pod adresem &lt;code&gt;rejestr-bdo.mos.gov.pl&lt;/code&gt; jest zbudowany jako aplikacja React SPA (Single Page Application). Frontend komunikuje się z usługami backendowymi przez wewnętrzne wywołania API, ale te endpointy są nieudokumentowane, wymagają tokenów sesji i używają dynamicznych wzorców zapytań zmieniających się między wdrożeniami.&lt;/p&gt;

&lt;p&gt;Ta architektura stwarza konkretne problemy dla automatyzacji:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brak stabilnych endpointów REST do bezpośredniego wywołania&lt;/li&gt;
&lt;li&gt;Uwierzytelnianie oparte na sesji z ochroną CSRF&lt;/li&gt;
&lt;li&gt;Dynamiczne renderowanie JavaScript oznacza, że proste zapytania HTTP zwracają puste szkielety&lt;/li&gt;
&lt;li&gt;Jedno wyszukiwanie na raz, bez eksportu hurtowego&lt;/li&gt;
&lt;li&gt;Brak udokumentowanego formatu danych lub schematu do programistycznego użycia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dla zespołów compliance środowiskowego weryfikujących dziesiątki kontrahentów, audytorów sprawdzających grupy kapitałowe czy firm odpadowych monitorujących rynek - ręczna weryfikacja przez portal się nie skaluje.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dane z rejestru BDO: co otrzymujesz
&lt;/h2&gt;

&lt;p&gt;Scraper zwraca ustrukturyzowany JSON dla każdego zarejestrowanego podmiotu z następującymi polami:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt; - zarejestrowana nazwa firmy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bdoNumber&lt;/strong&gt; - unikalny numer rejestracji BDO&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nip&lt;/strong&gt; - Numer Identyfikacji Podatkowej&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;regon&lt;/strong&gt; - numer identyfikacji statystycznej REGON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;province&lt;/strong&gt; - województwo rejestracji&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;registrationDate&lt;/strong&gt; - data dodania podmiotu do BDO&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;status&lt;/strong&gt; - aktualny status rejestracji (aktywny, zawieszony, wykreślony)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;wasteActivities&lt;/strong&gt; - rodzaje posiadanych pozwoleń na gospodarkę odpadami&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Możesz wyszukiwać po nazwie firmy, NIP, REGON lub numerze BDO. Aktor automatycznie obsługuje zarządzanie sesją React SPA, stronicowanie i ekstrakcję danych.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jak używać BDO Rejestr Odpadów Scraper
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minute_contest/bdo-waste-registry-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Remondis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | BDO: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bdoNumber&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  NIP: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | Województwo: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;province&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JavaScript (Node.js)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApifyClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apify-client&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;client&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;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_TOKEN&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;run&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;minute_contest/bdo-waste-registry-scraper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Remondis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&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;items&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&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="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | BDO: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bdoNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | NIP: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Praktyczny przykład: kwartalny audyt kontrahentów odpadowych
&lt;/h2&gt;

&lt;p&gt;Firma produkcyjna prowadzi 5 zakładów w różnych częściach Polski, z których każdy wytwarza odpady niebezpieczne i inne niż niebezpieczne. Współpracuje z 40+ kontrahentami odpadowymi - transportowymi, recyklingowymi i składowiskowymi. Zespół compliance środowiskowego musi co kwartał weryfikować, że każdy kontrahent posiada aktywną rejestrację BDO.&lt;/p&gt;

&lt;p&gt;Używając scrapera, budują prosty skrypt, który pobiera listę kontrahentów (numery NIP z systemu ERP), odpytuje BDO dla każdego z nich i flaguje kontrahentów, których status rejestracji nie jest aktywny. Skrypt działa jako zaplanowane zadanie Apify co kwartał, eksportując wyniki do CSV, który zespół compliance przegląda. Wcześniej analityk spędzał dwa pełne dni każdego kwartału na ręcznym sprawdzaniu portalu. Ze scraperem cały audyt kończy się bez nadzoru w mniej niż 30 minut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kto potrzebuje BDO Scraper
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zespoły compliance środowiskowego&lt;/strong&gt; - weryfikacja rejestracji BDO kontrahentów odpadowych&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firmy gospodarki odpadami&lt;/strong&gt; - monitoring konkurencji, śledzenie nowych podmiotów i walidacja podwykonawców&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audytorzy&lt;/strong&gt; - sprawdzanie compliance środowiskowego w grupach kapitałowych i łańcuchach dostaw&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firmy produkcyjne&lt;/strong&gt; - weryfikacja całych sieci podmiotów odpadowych przed podpisaniem umów&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Władze samorządowe&lt;/strong&gt; - monitoring zarejestrowanych operatorów odpadowych w ich jurysdykcji&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zespoły raportowania ESG&lt;/strong&gt; - dokumentowanie compliance odpadowego na potrzeby raportów zrównoważonego rozwoju&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ceny
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metoda&lt;/th&gt;
&lt;th&gt;Koszt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ręczne wyszukiwanie na portalu BDO&lt;/td&gt;
&lt;td&gt;Darmowe (po jednym)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ten aktor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~13 zł za 100 podmiotów&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Darmowe $5 kredytów Apify = ~160 wyszukiwań za darmo.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Jakie firmy muszą rejestrować się w BDO?
&lt;/h3&gt;

&lt;p&gt;Każda firma w Polsce, która wytwarza odpady (poza standardowymi odpadami biurowymi typu komunalnego), transportuje odpady, zbiera lub przetwarza odpady, importuje produkty z obowiązkami opakowaniowymi lub wprowadza towary w opakowaniach na rynek, musi posiadać aktywną rejestrację BDO. Obejmuje to producentów, firmy logistyczne, recyklerów i większość importerów.&lt;/p&gt;

&lt;h3&gt;
  
  
  Czy mogę zweryfikować konkretną firmę po NIP lub numerze BDO?
&lt;/h3&gt;

&lt;p&gt;Tak. Aktor obsługuje wyszukiwanie po nazwie firmy, NIP, REGON lub numerze rejestracji BDO. Wyszukiwanie po NIP jest najbardziej niezawodną metodą dokładnego dopasowania, ponieważ nazwy firm mogą różnić się formatowaniem między rejestracjami.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jak scraper radzi sobie z aplikacją React SPA portalu BDO?
&lt;/h3&gt;

&lt;p&gt;Aktor używa przeglądarki headless do nawigacji po aplikacji React, zarządzania tokenami sesji i ekstrakcji danych z wyrenderowanego DOM. To podejście obsługuje dynamiczne renderowanie JavaScript i zarządzanie sesjami, które uniemożliwiają bezpośredni scraping HTTP na portalu BDO.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wyprobuj:&lt;/strong&gt; &lt;a href="https://apify.com/minute_contest/bdo-waste-registry-scraper" rel="noopener noreferrer"&gt;apify.com/minute_contest/bdo-waste-registry-scraper&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ten artykuł jest częścią serii &lt;a href="https://dev.to/ucptools/series/polish-business-data-apis"&gt;Polish Business Data APIs&lt;/a&gt; o programistycznym dostępie do polskich rejestrów publicznych.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>api</category>
    </item>
    <item>
      <title>Vibe Coding tapi masih acak-acakan ? Improve code dengan spec first.</title>
      <dc:creator>Fauzi Fadhlurrohman</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:18:31 +0000</pubDate>
      <link>https://forem.com/fauzi_fadhlurrohman_a105e/vibe-coding-tapi-masih-acak-acakan-improve-code-dengan-spec-first-222e</link>
      <guid>https://forem.com/fauzi_fadhlurrohman_a105e/vibe-coding-tapi-masih-acak-acakan-improve-code-dengan-spec-first-222e</guid>
      <description>&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%2F8kz7mgq1arg9smgc1zoq.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%2F8kz7mgq1arg9smgc1zoq.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vibe Coding Tapi Masih Acak-acakan? Improve Code Lo dengan Spec-First! &lt;br&gt;
Pernah nggak sih lo lagi semangat-semangatnya buka VS Code, buka AI assistant (ChatGPT/Gemini/Claude), terus langsung ngetik prompt: "Bikin komponen navbar lengkap dengan dropdown dan dark mode"?&lt;/p&gt;

&lt;p&gt;Beberapa detik kemudian, boom! Kodenya jalan. Lo ngerasa kayak hacker di film-film. Ini yang sekarang sering disebut sebagai Vibe Coding—ngoding ngikutin flow dan feeling, minta AI buatin semuanya on-the-fly.&lt;/p&gt;

&lt;p&gt;Tapi... ada tapinya nih.&lt;/p&gt;

&lt;p&gt;Setelah projectnya jalan seminggu, lo minta AI nambahin satu fitur kecil. Tiba-tiba, aplikasinya error. Lo minta AI benerin, malah merusak fitur lain yang udah jalan. Akhirnya, lo pusing sendiri ngebaca kode yang di-generate AI. Kenapa bisa gini?&lt;/p&gt;

&lt;p&gt;Kenapa Vibe Coding Sering Bikin Mentok?&lt;br&gt;
Masalah utamanya satu: AI itu nggak punya ingatan jangka panjang (Memory). AI bekerja berdasarkan context window. Saat lo ngoding secara organik (Code-First) lewat puluhan prompt, AI lama-lama bakal "lupa" sama aturan awal, struktur database, atau arsitektur yang udah disepakati di prompt pertama. Kalau lo nggak punya dokumentasi yang jelas dan cuma ngandelin history chat, AI bakal mulai halusinasi dan ngasih solusi yang nabrak sana-sini.&lt;/p&gt;

&lt;p&gt;Hasilnya? Welcome to Spaghetti Code 🍝&lt;br&gt;
Kalau lo memaksakan code-first bareng AI tanpa planning, selamat, lo baru aja menciptakan Spaghetti Code.&lt;/p&gt;

&lt;p&gt;Spaghetti code itu ibarat mie yang kusut; kodenya saling melilit, nggak terstruktur, dan susah banget di-maintain.&lt;br&gt;
Contoh Spaghetti Code: Bayangin lo punya satu file UserDashboard.js yang isinya 1000 baris. Di satu file itu, lo nge-fetch data dari API langsung di dalam render, nge-handle business logic yang kompleks, nyampur CSS inline, dan ada manipulasi state yang berlapis-lapis. Begitu lo mau ubah warna tombol, tiba-tiba query database-nya ikut error. Kusut banget kan?&lt;/p&gt;

&lt;p&gt;Solusinya: Spec-First Development&lt;br&gt;
Biar kode nggak jadi spaghetti dan AI tetap on track, lo harus ubah mindset dari Code-First jadi Spec-First.&lt;/p&gt;

&lt;p&gt;Artinya: Tulis dulu spesifikasinya, aturannya, dan arsitekturnya, baru suruh AI nulis kodenya berdasarkan spesifikasi itu.&lt;/p&gt;

&lt;p&gt;Nah, buat nerapin ini tanpa ribet, gue mau ngenalin satu tool yang gue buat namanya Specter 🔮.&lt;/p&gt;

&lt;p&gt;Kenalan sama Specter 🔮&lt;br&gt;
Specter adalah sebuah framework dokumentasi berbasis CLI yang di-desain khusus buat Spec-driven development dengan dukungan AI.&lt;/p&gt;

&lt;p&gt;Intinya, Specter bakal bikinin struktur folder dokumentasi yang rapi di dalam project lo. Jadi, setiap kali lo mau nge-prompt AI, lo tinggal ngasih file spesifikasi dari Specter sebagai konteksnya. AI-nya jadi lebih pinter dan terarah!&lt;/p&gt;

&lt;p&gt;Fitur Andalan Specter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Organized Structure: Bikin folder docs yang super komprehensif buat misahin spek, rencana, sampai progress.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI-Ready: Format dokumentasinya udah dioptimasi biar gampang dibaca dan dipahami sama AI (ChatGPT/Gemini/Cursor, dll).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spec-Driven: Memaksa kita buat mikir spesifikasi dulu sebelum nulis kode.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy Setup: Cukup satu command, beres!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cara Install &amp;amp; Quick Start&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lo bisa install Specter secara global pakai npm:&lt;br&gt;
&lt;code&gt;npm install -g specter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Atau kalau mau langsung sikat pakai npx:&lt;/p&gt;

&lt;h1&gt;
  
  
  Initialize di folder saat ini
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;npx specter init&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Atau initialize di spesifik direktori
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;npx specter init ./my-project&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Nanti, Specter bakal otomatis nge-generate project configuration .specterrc yang isinya metadata project lo kayak gini:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "version": "1.0.0",&lt;br&gt;
  "projectName": "My Project",&lt;br&gt;
  "createdAt": "2026-04-14T12:00:00.000Z",&lt;br&gt;
  "specDriven": true,&lt;br&gt;
  "aiEnabled": true&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Dan yang paling penting, Specter bakal buatin struktur folder dewa ini:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docs/&lt;br&gt;
├── specs/        # Buat naruh spesifikasi fitur, API, dan business rules&lt;br&gt;
├── plans/        # Roadmap, milestone, dan sprint planning&lt;br&gt;
├── tasks/        # Todo list dan task dependencies&lt;br&gt;
├── techstacks/   # Dokumentasi tech stack &amp;amp; instruksi setup&lt;br&gt;
└── progress/     # Tracking progress dan blocker&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Gimana Cara Pakainya Biar "Vibe Coding" Tetep Rapi?&lt;br&gt;
Biar lo kebayang, ini contoh workflow mantep gabungin Specter sama AI:&lt;/p&gt;

&lt;p&gt;Skenario: Mau bikin fitur "User Authentication"&lt;/p&gt;

&lt;p&gt;Bikin Spek Dulu: Jangan langsung nyuruh AI ngoding. Lo masuk ke folder docs/specs/ dan bikin file auth-spec.md. Tulis di situ: "Kita pakai JWT, expirynya 24 jam, password harus di-hash pakai bcrypt."&lt;/p&gt;

&lt;p&gt;Bikin Task: Masuk ke docs/tasks/auth-tasks.md, pecah jadi kerjaan kecil: (1) Bikin UI Login, (2) Bikin Endpoint API, (3) Setup Middleware.&lt;/p&gt;

&lt;p&gt;Prompting ke AI: Nah, sekarang waktunya vibe coding! Lo tinggal copy-paste (atau attach file) auth-spec.md ke AI dan bilang: "Tolong buatin Endpoint API berdasarkan spesifikasi di file ini."&lt;/p&gt;

&lt;p&gt;Update Progress: Kalau udah kelar, catet di folder docs/progress/.&lt;/p&gt;

&lt;p&gt;Hasilnya? Kode lo bakal jauh lebih modular, nggak ada lagi cerita AI lupa context, dan nggak ada lagi yang namanya spaghetti code.&lt;/p&gt;

&lt;p&gt;Penutup&lt;br&gt;
Vibe coding pakai AI itu emang seru banget dan bikin kita produktif parah. Tapi tanpa struktur yang jelas, project lo cuma tinggal nunggu waktu buat runtuh.&lt;/p&gt;

&lt;p&gt;Dengan metode Spec-First pakai Specter, lo bisa tetep ngoding cepet bareng AI, tapi dengan kualitas engineering yang solid dan terstruktur.&lt;/p&gt;

&lt;p&gt;Tertarik cobain? Langsung aja meluncur ke reponya:&lt;/p&gt;

&lt;p&gt;Jangan lupa support dengan kasih ⭐ di repository-nya ya! Built with 💚 for spec-driven development.&lt;/p&gt;

&lt;p&gt;Gimana menurut kalian soal Spec-First development vs Code-First? Yuk diskusi di kolom komentar! 👇&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>claude</category>
      <category>spec</category>
      <category>sdd</category>
    </item>
    <item>
      <title>CodeMap: мой первый плагин для Android Studio, который рисует то, что я не мог удержать в голове</title>
      <dc:creator>roma romas</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:14:50 +0000</pubDate>
      <link>https://forem.com/roma_romas_a6b25cfcc81610/codemap-moi-piervyi-plaghin-dlia-android-studio-kotoryi-risuiet-to-chto-ia-nie-mogh-udierzhat-v-gholovie-2ak8</link>
      <guid>https://forem.com/roma_romas_a6b25cfcc81610/codemap-moi-piervyi-plaghin-dlia-android-studio-kotoryi-risuiet-to-chto-ia-nie-mogh-udierzhat-v-gholovie-2ak8</guid>
      <description>&lt;p&gt;Привет!&lt;/p&gt;

&lt;p&gt;Меня зовут Роман, я начинающий Android-разработчик. И это мой первый плагин для JetBrains Marketplace.&lt;/p&gt;

&lt;p&gt;Сразу скажу: я не сеньор с десятью годами опыта, и у меня нет портфолио из пятнадцати проектов. Я обычный разработчик, который столкнулся с проблемой — и попытался её решить. Кажется, получилось неплохо.&lt;/p&gt;

&lt;p&gt;🤯 Проблема, которая меня доканала&lt;br&gt;
Когда я только начинал разбираться с Kotlin и Android, всё было понятно. Один Activity, пара кнопок, setOnClickListener — красота, всё перед глазами.&lt;/p&gt;

&lt;p&gt;А потом появились ViewModel, UseCase, Repository, DataSource, Dagger/Hilt, корутины, Flow... И в какой-то момент я поймал себя на том, что просто не могу удержать в голове картину происходящего. Вот нажали на кнопку — данные пошли куда-то туда, потом завернули сюда, потом что-то заэмитили... Я постоянно терял нить.&lt;/p&gt;

&lt;p&gt;Ctrl+Click помогает перейти к определению метода, но он не показывает путь. Он показывает дверь, но не коридор. А мне нужна была карта.&lt;/p&gt;

&lt;p&gt;И я начал рисовать эти цепочки на бумаге. Честно. Квадратики, стрелочки. Потом в Miro. А потом подумал: «Слушай, я же программист. Почему я это делаю руками?»&lt;/p&gt;

&lt;p&gt;Так родилась идея CodeMap.&lt;/p&gt;

&lt;p&gt;🗺 Что делает CodeMap?&lt;br&gt;
Если коротко: он строит интерактивный граф вызовов функций в вашем Kotlin-проекте.&lt;/p&gt;

&lt;p&gt;Выбираете точку входа (например, onCreate в MainActivity) — и плагин показывает, какие функции она вызывает, те вызывают следующие, и так по цепочке вниз. Визуально это выглядит как дерево или граф, где каждая нода — функция, а стрелки — вызовы.&lt;/p&gt;

&lt;p&gt;Фичи, которые я запилил:&lt;br&gt;
🎨 Цветовое кодирование&lt;br&gt;
Разные типы вызовов красятся по-разному:&lt;/p&gt;

&lt;p&gt;UI-вызовы&lt;/p&gt;

&lt;p&gt;Сетевые запросы&lt;/p&gt;

&lt;p&gt;Работа с базой данных&lt;/p&gt;

&lt;p&gt;Асинхронные операции (корутины)&lt;/p&gt;

&lt;p&gt;Системные вызовы&lt;/p&gt;

&lt;p&gt;Глаз сразу цепляется: «Ага, вот тут ViewModel лезет напрямую в SharedPreferences, а должен бы через Repository».&lt;/p&gt;

&lt;p&gt;🔀 Ветвление условий&lt;br&gt;
Если в коде есть if или when, плагин показывает оба пути. Можно кликнуть и пройти по нужной ветке. Удобно, когда разбираешь сложную бизнес-логику.&lt;/p&gt;

&lt;p&gt;🏛 Аудит архитектуры&lt;br&gt;
Это, наверное, самая амбициозная часть. Плагин автоматически проверяет проект на соответствие с выбранной архитектурой и показывает нарушения которые можно скачать.&lt;/p&gt;

&lt;p&gt;⚡ Навигация в один клик&lt;br&gt;
Кликаешь по ноде в графе — и сразу прыгаешь на нужную строку в коде. Никакого поиска по проекту, никакого Ctrl+Shift+F.&lt;/p&gt;

&lt;p&gt;🔄 Поддержка K2&lt;br&gt;
Плагин полностью работает с новым компилятором Kotlin K2. Никаких «ой, у меня новая версия студии, всё сломалось».&lt;/p&gt;

&lt;p&gt;🛠 Как пользоваться&lt;br&gt;
Всё просто:&lt;/p&gt;

&lt;p&gt;Установить плагин из Marketplace&lt;/p&gt;

&lt;p&gt;Нажать «🚀 Анализировать проект»&lt;/p&gt;

&lt;p&gt;Выбрать файл и функцию на боковой панели&lt;/p&gt;

&lt;p&gt;Разглядывать граф и тыкать в ноды&lt;/p&gt;

&lt;p&gt;⚠️ Важное замечание про среду выполнения&lt;br&gt;
Если окно инструмента пустое — не пугайтесь. Это значит, что ваша Android Studio запущена на JDK без поддержки JCEF (Chromium Embedded Framework). Решается за минуту:&lt;/p&gt;

&lt;p&gt;Shift+Shift → «Choose Boot Java Runtime for the IDE»&lt;/p&gt;

&lt;p&gt;Выбрать встроенный JetBrains Runtime (JBR) с поддержкой JCEF&lt;/p&gt;

&lt;p&gt;Перезапустить студию&lt;br&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%2Fw0a3yy1pdu44otryyy6h.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%2Fw0a3yy1pdu44otryyy6h.png" alt=" " width="800" height="397"&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%2F40begbf3ajx94eeu2t7a.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%2F40begbf3ajx94eeu2t7a.png" alt=" " width="800" height="402"&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%2Fw50bz7gxr568dyx2w942.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%2Fw50bz7gxr568dyx2w942.png" alt=" " width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🤔 Что дальше?&lt;br&gt;
Плагин живёт в JetBrains Marketplace. Он бесплатный. Я продолжаю его допиливать в свободное время.&lt;/p&gt;

&lt;p&gt;Буду очень рад обратной связи. Если что-то сломалось, если есть идеи по фичам, если просто хотите сказать «прикольно» или «фигня» — пишите в комментарии или создавайте issue на GitHub.&lt;/p&gt;

&lt;p&gt;Для меня это первый серьёзный проект, который вышел за пределы моего ноутбука. И честно — мне немного страшно и очень интересно, что вы скажете.&lt;/p&gt;

&lt;p&gt;Всем добра и читаемого кода! 🚀&lt;/p&gt;

&lt;p&gt;P.S. Если плагин покажется полезным — поставьте звёздочку на GitHub и лайк в Marketplace. Для начинающего разработчика каждая звёздочка как допамин прямо в мозг 😅&lt;/p&gt;

</description>
      <category>android</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Why Your Arduino Input Feels Wrong When It Should Work</title>
      <dc:creator>張旭豐</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:13:14 +0000</pubDate>
      <link>https://forem.com/_0c004e5fde78250aee362/why-your-arduino-input-feels-wrong-when-it-should-work-koi</link>
      <guid>https://forem.com/_0c004e5fde78250aee362/why-your-arduino-input-feels-wrong-when-it-should-work-koi</guid>
      <description>&lt;h1&gt;
  
  
  Why Your Arduino Input Feels Wrong When It Should Work
&lt;/h1&gt;

&lt;p&gt;A button that sometimes doesn't register. A touch sensor that fires twice. A digital input that acts like it has "bad luck" timing.&lt;/p&gt;

&lt;p&gt;If you've experienced this, the problem isn't your wiring. It's something most tutorials skip entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Nobody Explains
&lt;/h2&gt;

&lt;p&gt;Head over to Stack Exchange and search "Arduino input not working" — you'll find a question with &lt;strong&gt;41 upvotes and 109,000 views&lt;/strong&gt; asking exactly this.&lt;/p&gt;

&lt;p&gt;The top answer explains the fix in two words: INPUT_PULLUP.&lt;/p&gt;

&lt;p&gt;But here's what the answer doesn't tell you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your circuit isn't broken. Your Arduino is reading noise.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a pin is set to INPUT with nothing connected, it floats. It picks up electrical interference from the air — your hand nearby, a power cable, ambient humidity. The pin randomly reads HIGH or LOW with no predictable pattern.&lt;/p&gt;

&lt;p&gt;This isn't a defect. It's physics.&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%2F9eot6jcof38yxyftu7jf.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%2F9eot6jcof38yxyftu7jf.png" alt="Arduino button circuit — when the pin floats, it reads random values" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Fig 1: A floating pin — without pull-up or pull-down, the Arduino reads noise, not your button)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Ways to Fix It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Option 1: External Pull-Down Resistor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Connect a 10kΩ resistor between the pin and GND. This forces the pin to read LOW when the button is open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Internal Pull-Up (the elegant solution)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One line in your setup:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pinMode(2, INPUT_PULLUP);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's it. The Arduino's built-in 20kΩ resistor now pulls the pin HIGH by default. When you press the button (connected between pin 2 and GND), it reads LOW.&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%2Fjxfn82vym002c6l56vpu.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%2Fjxfn82vym002c6l56vpu.jpg" alt="Arduino button wiring with INPUT_PULLUP — Pin 2 connected to button, button to GND" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Fig 2: With INPUT_PULLUP, the Arduino's internal resistor keeps the pin HIGH until the button connects it to GND)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Makes Your Interactive Project Feel Different
&lt;/h2&gt;

&lt;p&gt;Here's what most tutorials miss: after fixing the pull-up, your button behavior is now &lt;strong&gt;inverted&lt;/strong&gt; from what you'd expect.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Button open → pin reads HIGH&lt;/li&gt;
&lt;li&gt;Button pressed → pin reads LOW&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This inversion trips people up. But once you understand it, you can design around it naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Second Layer: Debounce
&lt;/h2&gt;

&lt;p&gt;Even after adding pull-ups, mechanical buttons have a problem: they bounce.&lt;/p&gt;

&lt;p&gt;When metal contacts close, they physically bounce apart and together multiple times within a few milliseconds. Your Arduino — which can read millions of times per second — interprets each bounce as a separate press.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;

void loop() {
    int reading = digitalRead(2);

    if (reading != lastButtonState) {
        lastDebounceTime = millis();
    }

    if (millis() - lastDebounceTime &amp;gt; 50) {
        // Actual button state after debounce
        if (reading == LOW) {
            // Button is pressed — do something
        }
    }

    lastButtonState = reading;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The 50ms threshold is a starting point. Adjust based on your specific button's bounce characteristics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Has to Do With Interactive Art
&lt;/h2&gt;

&lt;p&gt;Most maker tutorials treat buttons as on/off switches. But in interactive work, a button is not a binary — it's a moment of contact between a person and a machine.&lt;/p&gt;

&lt;p&gt;When that contact feels unreliable, the interaction breaks. The person questions whether they pressed hard enough, whether they should press again, whether the system is broken.&lt;/p&gt;

&lt;p&gt;Fixing the pull-up and debounce does not just make your code work correctly. It makes the interaction feel trustworthy. The person can stop thinking about the button and start engaging with the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Version That Changed How I Thought About It
&lt;/h2&gt;

&lt;p&gt;A few months into building interactive pieces, I stopped thinking about pins and resistors. I started thinking about this:&lt;/p&gt;

&lt;p&gt;A floating input is the machine equivalent of anxiety. It is reacting to phantom signals because it does not know what is real.&lt;/p&gt;

&lt;p&gt;Adding a pull-up gives the machine something to hold onto. A baseline. A sense of ground.&lt;/p&gt;

&lt;p&gt;That shift in thinking — from "how do I wire this" to "what does the machine need to feel stable" — changed everything about how I approached input design.&lt;/p&gt;




&lt;p&gt;If you want to go deeper on input design for interactive work, I write about the gap between "it works" and "it feels right" in my dev logs.&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>input</category>
      <category>beginners</category>
      <category>interactiveart</category>
    </item>
    <item>
      <title>How to Give Your AI Agent a Multi-Chain Crypto Wallet in 5 Minutes</title>
      <dc:creator>EmblemAI</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:10:35 +0000</pubDate>
      <link>https://forem.com/emblemai/how-to-give-your-ai-agent-a-multi-chain-crypto-wallet-in-5-minutes-3k2m</link>
      <guid>https://forem.com/emblemai/how-to-give-your-ai-agent-a-multi-chain-crypto-wallet-in-5-minutes-3k2m</guid>
      <description>&lt;p&gt;Most AI agents hit the same wall: they can reason about money yet cannot touch it. &lt;strong&gt;EmblemAI&lt;/strong&gt; solves this with a single npm package that gives any AI agent a deterministic crypto wallet spanning 7 blockchains and access to 200+ autonomous trading tools. This tutorial walks you through install, authentication, and your first on-chain operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: agents without wallets are spectators
&lt;/h2&gt;

&lt;p&gt;Most agent frameworks ship with no native financial capability. Developers end up stitching together separate SDKs for each chain, managing private keys manually, and writing custom swap logic for every DEX. A single cross-chain operation can require three or four different libraries, each with its own authentication model.&lt;/p&gt;

&lt;p&gt;Coinbase recognized this gap and launched its Agentic Wallets product in late 2025, providing wallets with programmable guardrails. EmblemAI takes a different approach: a CLI-first tool that any agent framework can shell out to, with 200+ pre-built trading tools across 14 categories and support for Solana, Ethereum, Base, BSC, Polygon, Hedera, and Bitcoin out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install the CLI
&lt;/h2&gt;

&lt;p&gt;EmblemAI ships as a global npm package. One command, no configuration files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @emblemvault/agentwallet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the &lt;code&gt;emblemai&lt;/code&gt; binary. It requires Node.js 18 or later. Verify the install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;emblemai &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package is open source and auditable. You can compare the published npm contents against the &lt;a href="https://github.com/EmblemCompany/EmblemAi-AgentWallet" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; at any time using &lt;code&gt;npm pack --dry-run&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Authenticate your agent
&lt;/h2&gt;

&lt;p&gt;EmblemAI supports two authentication modes. For interactive development, browser auth opens a login modal. For agents running in production, password auth works without a browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Interactive mode -- opens browser for login&lt;/span&gt;
emblemai

&lt;span class="c"&gt;# Agent mode -- zero-config, auto-generates a wallet&lt;/span&gt;
emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"What are my wallet addresses?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each password deterministically generates the same wallet every time. Different passwords produce different wallets. This means you can give each agent its own isolated wallet simply by assigning a unique password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Agent Alice gets her own wallet&lt;/span&gt;
emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"agent-alice-wallet-001"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"What are my balances?"&lt;/span&gt;

&lt;span class="c"&gt;# Agent Bob gets a completely separate wallet&lt;/span&gt;
emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"agent-bob-wallet-002"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"What are my balances?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Credentials are encrypted at rest using AES-256-GCM via dotenvx. The encryption key never leaves the local machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Query balances across all chains
&lt;/h2&gt;

&lt;p&gt;Once authenticated, the agent has wallet addresses on every supported chain. A single natural-language query returns balances across all 7 blockchains simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Show my balances across all chains"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent wallet provides addresses across these chain types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chain&lt;/th&gt;
&lt;th&gt;Address Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Solana&lt;/td&gt;
&lt;td&gt;Native SPL wallet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethereum, Base, BSC, Polygon&lt;/td&gt;
&lt;td&gt;Shared EVM address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hedera&lt;/td&gt;
&lt;td&gt;Account ID (0.0.XXXXXXX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitcoin&lt;/td&gt;
&lt;td&gt;Taproot, SegWit, and Legacy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is one wallet identity spanning 7 chains. No separate setup per chain. No bridge configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Execute your first swap
&lt;/h2&gt;

&lt;p&gt;EmblemAI interprets natural language, so your agent does not need to construct raw transaction parameters. The tool handles routing, slippage, and gas estimation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Swap 20 dollars worth of SOL to USDC on Solana"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent operates in safe mode by default. All wallet-modifying actions, including swaps, sends, transfers, and order placement, require explicit confirmation before execution. Read-only operations like balance checks and market data queries execute immediately.&lt;/p&gt;

&lt;p&gt;For scripted pipelines, output can be piped to other tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"What is my SOL balance?"&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Multi-chain operations
&lt;/h2&gt;

&lt;p&gt;The real power is cross-chain. EmblemAI supports bridge operations via ChangeNow, DeFi position management, limit orders, and market data aggregation from CoinGlass, DeFiLlama, Birdeye, and LunarCrush.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cross-chain bridge&lt;/span&gt;
emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Bridge 50 USDC from Ethereum to Solana"&lt;/span&gt;

&lt;span class="c"&gt;# DeFi operations&lt;/span&gt;
emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"What are the best yield opportunities for USDC right now?"&lt;/span&gt;

&lt;span class="c"&gt;# Market intelligence&lt;/span&gt;
emblemai &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"What tokens are trending on Solana today?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 200+ tools are organized into 14 categories: trading, DeFi, market data, NFTs, bridges, memecoins, predictions, and more. The AI dynamically selects the right tools based on the query. No manual tool configuration required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration with any agent framework
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;emblemai&lt;/code&gt; is a CLI binary, any system that can shell out to a command can use it. This works with CrewAI, AutoGPT, LangChain agents, Claude with tool use, or custom Python scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;agent_wallet_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emblemai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&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="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;

&lt;span class="c1"&gt;# Any agent can now trade
&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent_wallet_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show my portfolio summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EmblemAI also provides an MCP (Model Context Protocol) server, which means Claude Code, GitHub Copilot, Gemini CLI, and other MCP-compatible tools can access wallet operations natively without shelling out. The platform additionally supports Google's A2A (Agent-to-Agent) protocol for direct agent interoperability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;EmblemAI provides 200+ autonomous trading tools across 7 blockchains in 14 categories, accessible through a single npm install. The deterministic wallet model means one password equals one persistent identity across all chains.&lt;/p&gt;

&lt;p&gt;The package is open source, the credentials are encrypted at rest, and every transaction requires explicit human approval. Full documentation is available at &lt;a href="https://emblemvault.dev" rel="noopener noreferrer"&gt;emblemvault.dev&lt;/a&gt;, and the npm package is at &lt;a href="https://www.npmjs.com/package/@emblemvault/agentwallet" rel="noopener noreferrer"&gt;@emblemvault/agentwallet&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>crypto</category>
      <category>tutorial</category>
      <category>agents</category>
    </item>
    <item>
      <title>AI Search Showdown: Perplexity vs SearchGPT vs Claude 3.5 Sonnet (2026)</title>
      <dc:creator>Mimo2026</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:09:55 +0000</pubDate>
      <link>https://forem.com/mimoliansong/ai-search-showdown-perplexity-vs-searchgpt-vs-claude-35-sonnet-2026-5feb</link>
      <guid>https://forem.com/mimoliansong/ai-search-showdown-perplexity-vs-searchgpt-vs-claude-35-sonnet-2026-5feb</guid>
      <description>&lt;h1&gt;
  
  
  Comparative Feature Map: Perplexity AI vs. OpenAI SearchGPT vs. Claude 3.5 Sonnet
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A hands-on evaluation using three identical complex prompts across accuracy, speed, citations, and multi-modal capabilities.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;To ensure fairness, I tested all three models with the &lt;strong&gt;same three research prompts&lt;/strong&gt; representing distinct use cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Coding&lt;/strong&gt;: &lt;em&gt;"Debug and optimize a Python async web scraper that times out on large pages and has memory leaks. Explain the fixes and provide the corrected code."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General Knowledge&lt;/strong&gt;: &lt;em&gt;"What were the primary economic and geopolitical drivers behind Japan's Lost Decades, and how do they compare to China's current economic trajectory?"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Academic Research&lt;/strong&gt;: &lt;em&gt;"Provide a critical review of the evidence for GLP-1 receptor agonists in reducing cardiovascular events, including the SELECT trial, LEADER trial, and any 2024 meta-analyses."&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Comparison Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Perplexity AI&lt;/th&gt;
&lt;th&gt;OpenAI SearchGPT&lt;/th&gt;
&lt;th&gt;Claude 3.5 Sonnet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆ (4/5)&lt;br&gt;Strong for factual queries; occasionally misses nuance in highly technical domains&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆ (4/5)&lt;br&gt;Broad knowledge base; prone to "hallucinating" confidence in edge cases&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐ (5/5)&lt;br&gt;Best at admitting uncertainty; fewer hallucinations in technical reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐ (5/5)&lt;br&gt;Fastest (~3-5s for simple queries, ~8-12s for complex)&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆ (4/5)&lt;br&gt;Fast (~4-6s simple, ~10-15s complex)&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆ (3/5)&lt;br&gt;Slowest (~6-10s simple, ~15-25s complex)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Citations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐ (5/5)&lt;br&gt;Always provides inline links; easy to verify&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆ (4/5)&lt;br&gt;Good source integration but links can be buried or generic&lt;/td&gt;
&lt;td&gt;⭐⭐☆☆☆ (2/5)&lt;br&gt;Rarely provides direct links; relies on training data without live sourcing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-modal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⭐⭐⭐☆☆ (3/5)&lt;br&gt;Limited image understanding; focuses on text search&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆ (4/5)&lt;br&gt;Strong image analysis via GPT-4o; chart interpretation is solid&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐ (5/5)&lt;br&gt;Excellent at reading PDFs, charts, and images; best document analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Best-in-Class by Category
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🏆 Coding: Claude 3.5 Sonnet
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why it wins&lt;/strong&gt;: Claude consistently produced the most robust, well-explained code. For the async scraper prompt, it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identified both the timeout and memory leak root causes correctly&lt;/li&gt;
&lt;li&gt;Explained &lt;code&gt;aiohttp&lt;/code&gt; connection pooling and &lt;code&gt;BeautifulSoup&lt;/code&gt; memory fragmentation&lt;/li&gt;
&lt;li&gt;Provided clean, production-ready code with error handling&lt;/li&gt;
&lt;li&gt;Added comments explaining &lt;em&gt;why&lt;/em&gt; each change was made&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Runner-up&lt;/strong&gt;: SearchGPT provided functional code but with less nuanced explanation. Perplexity gave good high-level guidance but the code snippet was sometimes too abbreviated for direct use.&lt;/p&gt;




&lt;h3&gt;
  
  
  🏆 General Knowledge: OpenAI SearchGPT
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why it wins&lt;/strong&gt;: SearchGPT delivered the most comprehensive and well-structured answer on Japan's Lost Decades vs. China's trajectory. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Balanced economic and geopolitical angles effectively&lt;/li&gt;
&lt;li&gt;Drew clear comparative parallels (property bubbles, demographic shifts, export dependence)&lt;/li&gt;
&lt;li&gt;Maintained a readable narrative flow without getting lost in minutiae&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Runner-up&lt;/strong&gt;: Claude was more cautious and precise but slightly drier. Perplexity was factually solid but sometimes overly list-like in structure.&lt;/p&gt;




&lt;h3&gt;
  
  
  🏆 Academic Research: Perplexity AI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why it wins&lt;/strong&gt;: For the GLP-1 cardiovascular evidence review, Perplexity was unmatched. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cited the SELECT and LEADER trials with direct PubMed links&lt;/li&gt;
&lt;li&gt;Referenced a 2024 meta-analysis (JACC) that I could verify immediately&lt;/li&gt;
&lt;li&gt;Structured the response like a mini-literature review&lt;/li&gt;
&lt;li&gt;Provided confidence levels for each claim&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Runner-up&lt;/strong&gt;: Claude gave an excellent critical analysis but without live citations, making verification harder. SearchGPT cited sources but occasionally mixed up trial endpoints.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Value for $20/Month Subscription
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Winner: Claude 3.5 Sonnet (via Claude Pro at $20/mo)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While Perplexity Pro ($20/mo) and ChatGPT Plus ($20/mo) are both competitive, &lt;strong&gt;Claude 3.5 Sonnet offers the best overall value&lt;/strong&gt; for a single subscription because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lowest hallucination rate&lt;/strong&gt;: You spend less time fact-checking or debugging bad outputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best coding assistant&lt;/strong&gt;: Comparable to dedicated tools like GitHub Copilot ($10-19/mo extra)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best document analysis&lt;/strong&gt;: Can process PDFs, charts, and images with industry-leading comprehension&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Longest context window&lt;/strong&gt; (200K tokens): Ideal for research, legal, and academic workflows&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, if your primary need is &lt;strong&gt;real-time research with citations&lt;/strong&gt;, Perplexity Pro is the better $20 investment. If you need &lt;strong&gt;versatility across text, image, and voice&lt;/strong&gt;, ChatGPT Plus is the safest all-rounder.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coding&lt;/td&gt;
&lt;td&gt;Claude 3.5 Sonnet&lt;/td&gt;
&lt;td&gt;Best reasoning + code quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General Knowledge&lt;/td&gt;
&lt;td&gt;SearchGPT&lt;/td&gt;
&lt;td&gt;Most comprehensive and readable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Academic Research&lt;/td&gt;
&lt;td&gt;Perplexity AI&lt;/td&gt;
&lt;td&gt;Unmatched live citations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Perplexity AI&lt;/td&gt;
&lt;td&gt;Fastest responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-modal&lt;/td&gt;
&lt;td&gt;Claude 3.5 Sonnet&lt;/td&gt;
&lt;td&gt;Best PDF/chart/image analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best $20 Value&lt;/td&gt;
&lt;td&gt;Claude 3.5 Sonnet&lt;/td&gt;
&lt;td&gt;Lowest error rate, longest context, best coding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Tested April 2026 with identical prompts across all three platforms.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>comparison</category>
      <category>research</category>
    </item>
    <item>
      <title>Estoy construyendo un cliente de Git nativo para macOS en SwiftUI — así va la semana 1</title>
      <dc:creator>Pool Camacho</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:07:09 +0000</pubDate>
      <link>https://forem.com/poolcamacho/estoy-construyendo-un-cliente-de-git-nativo-para-macos-en-swiftui-asi-va-la-semana-1-5akd</link>
      <guid>https://forem.com/poolcamacho/estoy-construyendo-un-cliente-de-git-nativo-para-macos-en-swiftui-asi-va-la-semana-1-5akd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Maple&lt;/strong&gt; es un cliente de Git &lt;strong&gt;nativo, gratis y rápido&lt;/strong&gt; para macOS hecho en SwiftUI. Sin webviews, sin Electron, sin suscripciones. Habla directo con &lt;code&gt;git&lt;/code&gt; en tu máquina y no pretende esconder el modelo real de Git detrás de abstracciones raras.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/poolcamacho/Maple" rel="noopener noreferrer"&gt;https://github.com/poolcamacho/Maple&lt;/a&gt; · Licencia: MIT · 🇺🇸 &lt;a href="https://dev.to/poolcamacho/im-building-a-native-macos-git-client-in-swiftui-heres-week-one-1kk7"&gt;Read in English&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Por qué otro cliente de Git
&lt;/h2&gt;

&lt;p&gt;En macOS ya hay GUIs de Git muy decentes — Tower y Fork son las que más me gustan. Empecé Maple por algo más específico: quería una app que fuera &lt;strong&gt;gratis, open source, 100% SwiftUI nativo, y sin miedo a exponer el modelo real de Git para power users&lt;/strong&gt;, y a la vez quería aprender SwiftUI moderno construyendo algo que yo mismo usara todos los días.&lt;/p&gt;

&lt;p&gt;Maple arrancó con un brief bien acotado:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100% SwiftUI nativo&lt;/strong&gt; — sin webviews, sin Electron, abre tan rápido como Terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gratis y MIT&lt;/strong&gt; — nunca suscripción.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Muestra Git como es&lt;/strong&gt; — commit graph con topología real, branches y merges visibles, vista de conflictos de verdad. Cero wizards que escondan lo que está pasando.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respeta tu flujo&lt;/strong&gt; — shortcuts y acciones para lo que un power user realmente usa: interactive staging, stash, rebase, merge, cherry-pick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No pretende reemplazar a Tower para todo el mundo. Es el cliente que yo quería que existiera.&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%2F2p02qt64oxhdpp4wampa.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%2F2p02qt64oxhdpp4wampa.png" alt="Commit graph de Maple con topología real: lanes con colores y edges curvos por cada parent" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que ya hace
&lt;/h2&gt;

&lt;p&gt;Esto es un &lt;strong&gt;post de avance&lt;/strong&gt;, no un lanzamiento. Las capturas las subiré en el próximo post. Pero esto ya funciona de punta a punta en repos reales (llevo varios días dogfooding con la misma app para commitear su propio código):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Abrir cualquier repo local con el folder picker y validación de &lt;code&gt;.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sidebar con lista de repos, branches locales y remotos&lt;/li&gt;
&lt;li&gt;Toolbar con Pull, Push, Fetch, Stash, Branch, Merge, Rebase&lt;/li&gt;
&lt;li&gt;Cuatro tabs: Changes, History, Branches, Stashes&lt;/li&gt;
&lt;li&gt;Diff viewer con hunks coloreados, números de línea, y un toggle de Blame&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit graph con topología real&lt;/strong&gt; — algoritmo de lanes, edges curvos por cada parent, merges dibujados con círculos anillados&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge y rebase con manejo de conflictos&lt;/strong&gt; — detecta &lt;code&gt;UU&lt;/code&gt;/&lt;code&gt;AA&lt;/code&gt;/&lt;code&gt;DD&lt;/code&gt;, banner de operación con Abort / Continue / Skip, y resolución por archivo con &lt;code&gt;Use Ours&lt;/code&gt; / &lt;code&gt;Use Theirs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Auto-refresh vía FSEvents sobre &lt;code&gt;.git/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Layout adaptativo de desktops anchos a pantallas compactas&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%2F0beg435up9hbrye88vrv.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%2F0beg435up9hbrye88vrv.png" alt="Tab Branches de Maple con branches locales y remotos, acciones de checkout, create y delete" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  La arquitectura en una pantalla
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Models/     — Data pura y Sendable (AppState, GitModels, StashModels)
Services/   — GitService (actor, ejecuta el CLI)
              GitCoordinator (@MainActor, orquestación)
              Extensiones por comando (GitCommands, GitBranchOps,
              GitStashOps, GitMergeRebase)
              CommitGraphBuilder, ConflictParser, FileWatcher
Views/      — Un archivo por vista, cero lógica de negocio
Utils/      — FolderPicker, DateExtensions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tres decisiones que vale la pena llamar aparte:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;GitService&lt;/code&gt; es un &lt;code&gt;actor&lt;/code&gt;&lt;/strong&gt;, así que todas las llamadas al CLI se serializan por un solo "portero". &lt;code&gt;GitCoordinator&lt;/code&gt; es &lt;code&gt;@MainActor&lt;/code&gt; y hace de pegamento entre la UI y el actor — las vistas solo llaman &lt;code&gt;state.coordinator.*&lt;/code&gt; y nunca tocan un &lt;code&gt;Process&lt;/code&gt; directamente. Resultado: lógica de negocio cero en las vistas, fácil de testear y refactorizar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cada invocación de &lt;code&gt;git&lt;/code&gt; abre un &lt;code&gt;/dev/null&lt;/code&gt; fresco para stdin y cierra sus pipes explícitamente.&lt;/strong&gt; Sin eso me salía &lt;code&gt;NSPOSIXErrorDomain code=9 / EBADF&lt;/code&gt; después de comandos largos como &lt;code&gt;push&lt;/code&gt;, porque &lt;code&gt;FileHandle.nullDevice&lt;/code&gt; es un singleton compartido que termina en estados raros después de muchos &lt;code&gt;posix_spawn&lt;/code&gt;. Abrir &lt;code&gt;/dev/null&lt;/code&gt; por llamada con &lt;code&gt;closeOnDealloc: true&lt;/code&gt; lo arregla de tajo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El commit graph no es el "punto + línea vertical" de toda la vida.&lt;/strong&gt; Construye un layout real de lanes: para cada commit, reclama el lane que lo estaba esperando (el vínculo child→parent); si no hay, reusa un lane libre. Los primeros parents se quedan en el mismo lane para que la línea principal quede recta; los parents adicionales de merges abren lanes laterales con edges curvos. Los edges se resuelven en una segunda pasada porque un parent puede aparecer muchas filas más abajo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El UX de conflictos es por archivo, no por hunk — a propósito para v1.&lt;/strong&gt; Cuando cae un merge con conflicto, el tab Changes marca cada archivo conflictivo con un &lt;code&gt;!&lt;/code&gt; morado, y cada uno tiene tres botones: &lt;code&gt;Use Ours&lt;/code&gt;, &lt;code&gt;Use Theirs&lt;/code&gt;, o editas los markers tú mismo en tu editor favorito. Arriba aparece un banner persistente que dice &lt;code&gt;Merging X&lt;/code&gt; con &lt;code&gt;Abort&lt;/code&gt; / &lt;code&gt;Continue&lt;/code&gt; / &lt;code&gt;Skip&lt;/code&gt;. Sin modales, sin secuestrarte el flujo.&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%2Ffvskolw6p4y6wzdotuzv.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%2Ffvskolw6p4y6wzdotuzv.png" alt="Vista de resolución de conflictos en Maple con el banner Merging, los markers resaltados y los botones Use Ours / Use Theirs por archivo" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que ya está armado alrededor del código
&lt;/h2&gt;

&lt;p&gt;Porque la idea es que esto crezca como proyecto OSS serio, no como experimento de fin de semana:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions CI&lt;/strong&gt; — build + &lt;code&gt;xcodebuild analyze&lt;/code&gt; + SwiftLint strict en cada push y PR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeQL&lt;/strong&gt; — análisis semanal de Swift más en cada PR que toque código Swift&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow de release&lt;/strong&gt; — taggeas &lt;code&gt;v*&lt;/code&gt; y sale un &lt;code&gt;.app&lt;/code&gt; sin firmar zipeado automáticamente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch protection&lt;/strong&gt; en &lt;code&gt;master&lt;/code&gt; — status checks requeridos, no force push, no delete, conversations resueltas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependabot&lt;/strong&gt; para GitHub Actions, así las versiones no se quedan oxidadas&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SECURITY.md&lt;/code&gt; con private vulnerability reporting activado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue forms&lt;/strong&gt; y &lt;strong&gt;PR template&lt;/strong&gt; que fuerza la regla de "ni una sola línea de lógica de negocio en las vistas"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nada de esto es heroico. Es el andamiaje aburrido que separa un hobby project de un repo donde la gente realmente puede contribuir.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que sigue
&lt;/h2&gt;

&lt;p&gt;El roadmap que estoy atacando en orden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Interactive staging&lt;/strong&gt; — stage de hunks o líneas individuales, no solo archivos enteros&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Tag management&lt;/strong&gt; — crear, listar, borrar tags desde la UI&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Search / filtering&lt;/strong&gt; — filtrar commits y archivos con fuzzy match&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Clone from URL&lt;/strong&gt; — ahora solo abres repos existentes, clonar es el paso obvio&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Remote management&lt;/strong&gt; — agregar, quitar, configurar remotes sin CLI&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Keyboard shortcuts&lt;/strong&gt; — &lt;code&gt;Cmd+S&lt;/code&gt; stage, &lt;code&gt;Cmd+Enter&lt;/code&gt; commit, &lt;code&gt;Cmd+K&lt;/code&gt; command palette&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Persistir repos abiertos&lt;/strong&gt; entre sesiones&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Settings &amp;amp; preferences&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Releases firmados + notarizados&lt;/strong&gt; cuando la app ya esté lista para usuarios no-devs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pruébalo y ayúdame a darle forma
&lt;/h2&gt;

&lt;p&gt;MIT, open source, todo en público:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/poolcamacho/Maple" rel="noopener noreferrer"&gt;github.com/poolcamacho/Maple&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si tienes macOS 14+ y Xcode 16+, &lt;code&gt;git clone&lt;/code&gt; + &lt;code&gt;Cmd+R&lt;/code&gt; y lo tienes corriendo en menos de un minuto. Abre issues si te topas con un flujo de Git que la UI vuelve torpe — prefiero arreglar eso a adivinar qué necesitan los power users.&lt;/p&gt;

&lt;p&gt;Si quieres patrocinar el proyecto para que se mantenga gratis y activo, hay botón &lt;strong&gt;Sponsor&lt;/strong&gt; arriba del repo, o directo en &lt;a href="https://github.com/sponsors/poolcamacho" rel="noopener noreferrer"&gt;github.com/sponsors/poolcamacho&lt;/a&gt;. El patrocinio mantiene Maple libre para todos y me ayuda a cubrir el presupuesto de firma + notarización que voy a necesitar para shipear a usuarios no-devs.&lt;/p&gt;

&lt;p&gt;El próximo post probablemente sea sobre interactive staging — es el problema de diseño más interesante de la lista. Nos leemos.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>macos</category>
      <category>git</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I'm building a native macOS Git client in SwiftUI — here's week one</title>
      <dc:creator>Pool Camacho</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:07:09 +0000</pubDate>
      <link>https://forem.com/poolcamacho/im-building-a-native-macos-git-client-in-swiftui-heres-week-one-1kk7</link>
      <guid>https://forem.com/poolcamacho/im-building-a-native-macos-git-client-in-swiftui-heres-week-one-1kk7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Maple is a free, fast, native macOS Git client built with SwiftUI — no web views, no Electron, no subscription. It talks directly to &lt;code&gt;git&lt;/code&gt; on your machine and exposes the full power of Git without hiding it behind abstractions.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/poolcamacho/Maple" rel="noopener noreferrer"&gt;https://github.com/poolcamacho/Maple&lt;/a&gt; · License: MIT&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why another Git client
&lt;/h2&gt;

&lt;p&gt;There are solid Git GUIs on macOS already — Tower and Fork are both great. I started Maple for a narrower reason: I wanted something that was &lt;strong&gt;free, open source, native SwiftUI, and unapologetically power-user friendly&lt;/strong&gt;, and I wanted to learn modern SwiftUI by building something I'd actually use every day.&lt;/p&gt;

&lt;p&gt;So Maple has a pretty specific design brief:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully native SwiftUI&lt;/strong&gt; — no web views, no Electron, opens as fast as Terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free and MIT-licensed&lt;/strong&gt; — no subscription, ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shows Git as it is&lt;/strong&gt; — full commit graph with real topology, visible branches and merges, proper conflict view. No wizards that hide what's happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respects your workflow&lt;/strong&gt; — shortcuts and actions for the operations power users actually care about: interactive staging, stash, rebase, merge, cherry-pick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not a replacement for Tower for everyone. It's the one I wanted to exist.&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%2F2p02qt64oxhdpp4wampa.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%2F2p02qt64oxhdpp4wampa.png" alt="Maple's commit graph showing real branch topology with colored lanes and curved edges"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does today
&lt;/h2&gt;

&lt;p&gt;The screenshots will come later — I'm publishing this as a progress post, not a launch — but here's what's already working end-to-end on a real repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open any local repo&lt;/strong&gt; via folder picker, with &lt;code&gt;.git&lt;/code&gt; validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar&lt;/strong&gt; with repository list, local and remote branches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toolbar&lt;/strong&gt; with Pull, Push, Fetch, Stash, Branch, Merge, Rebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four tabs&lt;/strong&gt;: Changes, History, Branches, Stashes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live diff viewer&lt;/strong&gt; with colored hunks, line numbers, and a Blame toggle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit graph with real topology&lt;/strong&gt; — lane assignment algorithm, curved edges per parent, merge nodes drawn as ringed circles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge and rebase with conflict handling&lt;/strong&gt; — &lt;code&gt;UU&lt;/code&gt;/&lt;code&gt;AA&lt;/code&gt;/&lt;code&gt;DD&lt;/code&gt; detection, an operation banner with Abort / Continue / Skip, and per-file &lt;code&gt;Use Ours&lt;/code&gt; / &lt;code&gt;Use Theirs&lt;/code&gt; resolution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-refresh&lt;/strong&gt; via FSEvents on &lt;code&gt;.git/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive layout&lt;/strong&gt; from wide desktops down to compact laptop windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a real client already. I've been dogfooding it to commit and push the work on itself.&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%2F0beg435up9hbrye88vrv.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%2F0beg435up9hbrye88vrv.png" alt="Maple's Branches tab showing local and remote branches with checkout, create, and delete actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture, in one screen
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Models/     — Pure Sendable data (AppState, GitModels, StashModels)
Services/   — GitService (actor, CLI execution)
              GitCoordinator (@MainActor orchestration)
              Command extensions (GitCommands, GitBranchOps,
              GitStashOps, GitMergeRebase)
              CommitGraphBuilder, ConflictParser, FileWatcher
Views/      — One file per view, zero business logic
Utils/      — FolderPicker, DateExtensions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of decisions worth calling out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;GitService&lt;/code&gt; is an &lt;code&gt;actor&lt;/code&gt;&lt;/strong&gt;, so all CLI calls serialize behind a single gatekeeper. &lt;code&gt;GitCoordinator&lt;/code&gt; is &lt;code&gt;@MainActor&lt;/code&gt; and sits between the views and the actor — views only call &lt;code&gt;state.coordinator.*&lt;/code&gt; and never touch Process or Pipe directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every &lt;code&gt;git&lt;/code&gt; invocation uses a fresh &lt;code&gt;/dev/null&lt;/code&gt; stdin&lt;/strong&gt; and closes its pipes explicitly. Without that, I was getting &lt;code&gt;NSPOSIXErrorDomain code=9 / EBADF&lt;/code&gt; errors after long-running commands like &lt;code&gt;push&lt;/code&gt;, because &lt;code&gt;FileHandle.nullDevice&lt;/code&gt; is a shared singleton that drifts into bad states across many &lt;code&gt;posix_spawn&lt;/code&gt; calls. Opening &lt;code&gt;/dev/null&lt;/code&gt; per call with &lt;code&gt;closeOnDealloc: true&lt;/code&gt; fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The commit graph isn't the usual “dot + vertical line” fake.&lt;/strong&gt; It builds an actual lane layout: for each commit, claim the lane that expects it (child→parent link), otherwise reuse a free lane. First parents stay in the same lane to keep the main line straight; extra parents on merges spawn side lanes with curved connectors. Edges are resolved in a second pass because a parent may appear many rows later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conflict UX is per-file, not per-hunk — on purpose for v1.&lt;/strong&gt; When a merge conflict lands, the Changes tab lights up conflicted files with a purple &lt;code&gt;!&lt;/code&gt; marker, and each one offers a three-button bar: &lt;code&gt;Use Ours&lt;/code&gt;, &lt;code&gt;Use Theirs&lt;/code&gt;, or edit the markers in your own editor. A persistent banner at the top of the window shows &lt;code&gt;Merging X&lt;/code&gt; with &lt;code&gt;Abort&lt;/code&gt; / &lt;code&gt;Continue&lt;/code&gt; / &lt;code&gt;Skip&lt;/code&gt;. No modal dialogs, no hijacking your workflow.&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%2Ffvskolw6p4y6wzdotuzv.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%2Ffvskolw6p4y6wzdotuzv.png" alt="Conflict resolution view in Maple with the Merging banner, conflict markers highlighted, and the per-file Use Ours / Use Theirs buttons"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's already in place beyond the app itself
&lt;/h2&gt;

&lt;p&gt;Because I want this to grow as a real OSS project and not as a one-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions CI&lt;/strong&gt; — build + &lt;code&gt;xcodebuild analyze&lt;/code&gt; + SwiftLint (strict) on every push and PR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeQL&lt;/strong&gt; — scheduled weekly Swift analysis, plus on any PR that touches Swift code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release workflow&lt;/strong&gt; — tag &lt;code&gt;v*&lt;/code&gt; to publish an unsigned &lt;code&gt;.app&lt;/code&gt; zip automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch protection&lt;/strong&gt; on &lt;code&gt;master&lt;/code&gt; — required status checks, no force push, no deletion, conversation resolution required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependabot&lt;/strong&gt; for GitHub Actions, so pinned action versions stay fresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SECURITY.md&lt;/strong&gt; with private vulnerability reporting enabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue forms&lt;/strong&gt; and a &lt;strong&gt;PR template&lt;/strong&gt; that enforces the “no business logic in views” rule&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is heroic, but it's the boring scaffolding that turns a weekend project into something other people can actually contribute to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The roadmap I'm actively working through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Interactive staging&lt;/strong&gt; — stage individual hunks and lines instead of whole files&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Tag management&lt;/strong&gt; — create, list, delete from the UI&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Search filtering&lt;/strong&gt; — filter commits and files with fuzzy matching&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Clone from URL&lt;/strong&gt; — right now you open existing repos; cloning is the obvious next step&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Remote management&lt;/strong&gt; — add, remove, configure remotes without dropping to CLI&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Keyboard shortcuts&lt;/strong&gt; — &lt;code&gt;Cmd+S&lt;/code&gt; to stage, &lt;code&gt;Cmd+Enter&lt;/code&gt; to commit, &lt;code&gt;Cmd+K&lt;/code&gt; for the command palette I haven't built yet&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Persist open repositories&lt;/strong&gt; between sessions&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Settings &amp;amp; preferences&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Signed + notarized releases&lt;/strong&gt; once the app is ready for non-developer users&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it, and help shape where it goes
&lt;/h2&gt;

&lt;p&gt;It's MIT-licensed, open source, and built in public:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/poolcamacho/Maple" rel="noopener noreferrer"&gt;github.com/poolcamacho/Maple&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're on macOS 14+ with Xcode 16+, &lt;code&gt;git clone&lt;/code&gt; and &lt;code&gt;Cmd+R&lt;/code&gt; in Xcode gets you running in under a minute. Please open issues — especially if you hit a Git workflow the UI makes awkward. I'd much rather fix that than guess what power users need.&lt;/p&gt;

&lt;p&gt;And if you want to sponsor the work so it stays free and actively maintained, there's a &lt;strong&gt;Sponsor&lt;/strong&gt; button at the top of the repo, or you can go directly to &lt;a href="https://github.com/sponsors/poolcamacho" rel="noopener noreferrer"&gt;github.com/sponsors/poolcamacho&lt;/a&gt;. Sponsorship keeps Maple free for everyone and funds the signing + notarization budget I'll need for shipping to non-developers.&lt;/p&gt;

&lt;p&gt;Next post will probably be about interactive staging — that one's the most interesting design problem on the list. Stay tuned.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>macos</category>
      <category>git</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I got tired of writing launchd XML by hand, so I built launchd-gen</title>
      <dc:creator>Vandy Sodanheang</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:05:31 +0000</pubDate>
      <link>https://forem.com/luciferrevenant/i-got-tired-of-writing-launchd-xml-by-hand-so-i-built-launchd-gen-1c6n</link>
      <guid>https://forem.com/luciferrevenant/i-got-tired-of-writing-launchd-xml-by-hand-so-i-built-launchd-gen-1c6n</guid>
      <description>&lt;p&gt;If you've ever tried to schedule a recurring job on macOS, you've met &lt;strong&gt;launchd&lt;/strong&gt;. And if you've spent more than five minutes with it, you've probably thought: &lt;em&gt;"Why is this so much harder than crontab?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the story of why I built &lt;a href="https://github.com/VandyTheCoder/launchd-gen" rel="noopener noreferrer"&gt;&lt;strong&gt;launchd-gen&lt;/strong&gt;&lt;/a&gt; — a small Go CLI that takes a familiar cron expression and emits a valid macOS launchd property list, ready to drop into &lt;code&gt;~/Library/LaunchAgents/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with launchd
&lt;/h2&gt;

&lt;p&gt;launchd is the modern replacement for cron on macOS. It's more powerful (it can react to filesystem events, network changes, system load, etc.), but for the 80% case — &lt;em&gt;"run this script every weekday at 9 AM"&lt;/em&gt; — it's significantly more painful than crontab.&lt;/p&gt;

&lt;p&gt;Here's what a "run every weekday at 9 AM" looks like in cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 9 * * 1-5 /usr/local/bin/my-script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. Five fields. Done.&lt;/p&gt;

&lt;p&gt;Here's what the equivalent looks like as a launchd plist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.me.my-script&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/local/bin/my-script&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StartCalendarInterval&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;4&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;5&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five times the fields. Forty times the lines. And here's the part that really gets you:&lt;/p&gt;

&lt;h2&gt;
  
  
  launchd has no concept of ranges
&lt;/h2&gt;

&lt;p&gt;The reason that plist needs &lt;strong&gt;five&lt;/strong&gt; &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; entries is that launchd's &lt;code&gt;StartCalendarInterval&lt;/code&gt; only accepts &lt;strong&gt;single integer values per key&lt;/strong&gt;. There is no way to say "weekdays 1 through 5" in a single dict — you have to expand the range yourself by writing one dict per day.&lt;/p&gt;

&lt;p&gt;This is annoying for &lt;code&gt;1-5&lt;/code&gt;. It becomes ridiculous fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/15 9-17 * * 1-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's "every 15 minutes during business hours on weekdays" — a perfectly reasonable cron expression. To express it as a launchd plist, you have to write &lt;strong&gt;180 separate &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; entries&lt;/strong&gt;, because it's the cartesian product of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 minute values: &lt;code&gt;0, 15, 30, 45&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;9 hour values: &lt;code&gt;9, 10, 11, 12, 13, 14, 15, 16, 17&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;5 weekday values: &lt;code&gt;1, 2, 3, 4, 5&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;4 × 9 × 5 = 180&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nobody types that out by hand. They either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drop the schedule down to a coarser interval that's easier to write&lt;/li&gt;
&lt;li&gt;Write a script that loops &lt;code&gt;launchctl&lt;/code&gt; calls, defeating the purpose of having a declarative scheduler&lt;/li&gt;
&lt;li&gt;Use a third-party GUI tool like Lingon or LaunchControl&lt;/li&gt;
&lt;li&gt;Give up and stay on crontab, which Apple has been quietly trying to deprecate since 2005&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted option 5: &lt;strong&gt;a small CLI that takes a cron expression and outputs the right plist.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter launchd-gen
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap VandyTheCoder/tools
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; launchd-gen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchd-gen &lt;span class="nt"&gt;--label&lt;/span&gt; com.me.my-script &lt;span class="s2"&gt;"0 9 * * 1-5"&lt;/span&gt; /usr/local/bin/my-script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The output is a fully-formed launchd plist on stdout, with all 5 weekday &lt;code&gt;&amp;lt;dict&amp;gt;&lt;/code&gt; entries expanded automatically.&lt;/p&gt;

&lt;p&gt;To install and load it in one go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchd-gen &lt;span class="nt"&gt;--install&lt;/span&gt; &lt;span class="nt"&gt;--load&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; com.me.my-script &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stdout&lt;/span&gt; /tmp/my-script.log &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stderr&lt;/span&gt; /tmp/my-script.err &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"0 9 * * 1-5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /usr/local/bin/my-script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That writes the plist to &lt;code&gt;~/Library/LaunchAgents/com.me.my-script.plist&lt;/code&gt; and runs &lt;code&gt;launchctl load&lt;/code&gt; on it. From cron expression to running scheduled job in one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it supports
&lt;/h2&gt;

&lt;p&gt;The cron parser handles everything you'd expect from standard 5-field crontab:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00 daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9,17 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00 and 17:00 daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00 on weekdays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*/15 * * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every 15 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range + step&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9-17/2 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 09:00, 11:00, 13:00, 15:00, 17:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combinations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*/15 9-17 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every 15 min, business hours, weekdays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shortcuts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@daily&lt;/code&gt;, &lt;code&gt;@hourly&lt;/code&gt;, &lt;code&gt;@reboot&lt;/code&gt;, &lt;code&gt;@weekly&lt;/code&gt;, &lt;code&gt;@monthly&lt;/code&gt;, &lt;code&gt;@yearly&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shortcuts behave exactly as crontab — &lt;code&gt;@reboot&lt;/code&gt; becomes a &lt;code&gt;RunAtLoad: true&lt;/code&gt; flag, the rest expand to their equivalent 5-field expressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;launchd-gen is &lt;strong&gt;under 600 lines of production Go&lt;/strong&gt; (plus ~200 lines of tests), with &lt;strong&gt;zero external dependencies&lt;/strong&gt;. The whole thing is one binary, MIT licensed, and the source is on GitHub.&lt;/p&gt;

&lt;p&gt;The interesting bits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;internal/cron/parser.go&lt;/code&gt;&lt;/strong&gt; — turns a cron string into a list of &lt;code&gt;Interval&lt;/code&gt; structs by computing the cartesian product of the parsed field values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;internal/plist/writer.go&lt;/code&gt;&lt;/strong&gt; — hand-writes the launchd XML rather than depending on a third-party plist library, which keeps the binary tiny and &lt;code&gt;go install&lt;/code&gt; instant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;/strong&gt; — the CLI entry point, with a &lt;code&gt;--install&lt;/code&gt; flag that writes directly to &lt;code&gt;~/Library/LaunchAgents/&lt;/code&gt; and a &lt;code&gt;--load&lt;/code&gt; flag that chains a &lt;code&gt;launchctl load&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I deliberately avoided pulling in cobra, viper, or any of the other usual Go CLI suspects. For something this small they're overkill, and the standard library's &lt;code&gt;flag&lt;/code&gt; package handles it just fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I run a small daily-dashboard project on my own infrastructure. It has four scheduled jobs — a news fetcher, a daily AI briefing, an activity log summarizer, and a weekly trade-config optimizer. Each one is a launchd agent, because that's how you do scheduled work on macOS in 2026.&lt;/p&gt;

&lt;p&gt;Writing those four plists by hand was the most painful part of the whole project. The actual &lt;em&gt;work&lt;/em&gt; the scripts do — fetching, summarizing, posting to a webhook — was easier than getting them on a schedule. That's a smell, and I wrote launchd-gen to fix it.&lt;/p&gt;

&lt;p&gt;The four real plists from that project are now used as acceptance fixtures in the launchd-gen test suite, which is the kind of dogfooding I always look for in a tool I'm about to depend on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;v0.1.0 is shipped and it does what I built it for. The roadmap for v0.2.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Day-of-week names (&lt;code&gt;MON&lt;/code&gt;, &lt;code&gt;TUE&lt;/code&gt;, &lt;code&gt;WED&lt;/code&gt;, ...) instead of numeric weekdays&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;--dry-run&lt;/code&gt; flag that prints the plist without writing it&lt;/li&gt;
&lt;li&gt;Reverse mode: convert an existing plist back into a cron expression&lt;/li&gt;
&lt;li&gt;Environment variable injection from a file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those sound useful — or if you want something else entirely — open an issue. Pull requests welcome, MIT licensed, no CLA, no committee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap VandyTheCoder/tools
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; launchd-gen
launchd-gen &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source, issues, and releases: &lt;strong&gt;&lt;a href="https://github.com/VandyTheCoder/launchd-gen" rel="noopener noreferrer"&gt;github.com/VandyTheCoder/launchd-gen&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've ever stared at a launchd plist and wondered why something this conceptually simple has to be this verbose — this is for you.&lt;/p&gt;

</description>
      <category>go</category>
      <category>macos</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How Do Startups Decide Between MVP and Full Product Development?</title>
      <dc:creator>Dhruv Joshi</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:03:09 +0000</pubDate>
      <link>https://forem.com/dhruvjoshi9/how-do-startups-decide-between-mvp-and-full-product-development-3ch9</link>
      <guid>https://forem.com/dhruvjoshi9/how-do-startups-decide-between-mvp-and-full-product-development-3ch9</guid>
      <description>&lt;p&gt;Startups do not fail because they lacked ideas. They fail because they built too much, too soon, or too little, too late.&lt;/p&gt;

&lt;p&gt;That is the real tension behind &lt;strong&gt;MVP app development&lt;/strong&gt; versus &lt;strong&gt;full product development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One path helps you test demand fast. The other helps you launch stronger, deeper, and with fewer second guesses. But choosing the wrong one can drain budget, stretch timelines, and confuse users right out of the gate.&lt;/p&gt;

&lt;p&gt;So how do smart founders decide? They look at risk, urgency, user behavior, and growth goals. They do not guess. They choose the build path that matches the business moment. Exactly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Answer
&lt;/h2&gt;

&lt;p&gt;If you need to validate demand, reduce risk, and launch fast, choose &lt;strong&gt;MVP app development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you already know the market wants your product, have funding, and need a complete experience from day one, go for &lt;strong&gt;full product development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That sounds simple, but the better answer depends on your stage, money, product complexity, and startup strategy.&lt;/p&gt;

&lt;p&gt;Let’s break it down properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Decision Matters So Much
&lt;/h2&gt;

&lt;p&gt;This is not just a product decision. It is a business survival decision.&lt;/p&gt;

&lt;p&gt;Startups usually work with limited cash, small teams, and short runway. That means every product choice affects hiring, marketing, growth, and investor confidence. If you overbuild, you burn money before proving traction. If you underbuild, users may leave before they understand the value.&lt;/p&gt;

&lt;p&gt;That is why &lt;strong&gt;MVP app development&lt;/strong&gt; matters so much in early-stage startup strategy. It gives founders a way to test assumptions without betting everything on one launch. At the same time, some products simply cannot enter the market half-built. In those cases, a full launch makes more sense.&lt;/p&gt;

&lt;p&gt;So the right question is not which one is better. The right question is which one is smarter for your current stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MVP App Development?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MVP app development&lt;/strong&gt; is about building the smallest version of a product that still delivers a clear core value.&lt;/p&gt;

&lt;p&gt;Not a broken version. Not a cheap version. Not a demo pretending to be a product.&lt;/p&gt;

&lt;p&gt;A real MVP solves one clear problem for one clear user group. It should be usable, focused, and measurable. The goal is simple: &lt;strong&gt;launch fast, learn fast, improve fast&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A lot of founders get this wrong. They hear MVP app development and think they should remove everything possible. Then the product feels empty. Users do not stay. Feedback becomes messy because the product never showed enough value in the first place.&lt;/p&gt;

&lt;p&gt;A strong MVP usually includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One core use case&lt;/li&gt;
&lt;li&gt;Simple but functional design&lt;/li&gt;
&lt;li&gt;Basic onboarding&lt;/li&gt;
&lt;li&gt;Analytics and feedback tracking&lt;/li&gt;
&lt;li&gt;Scalable code where it matters&lt;/li&gt;
&lt;li&gt;Clear success metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if you are building a delivery app, your MVP app development version may only include ordering, payments, and tracking. No loyalty program. No advanced admin suite. No fancy personalization engine yet.&lt;/p&gt;

&lt;p&gt;That focused approach is why many founders first work with an &lt;strong&gt;android application development company&lt;/strong&gt; when they want faster market testing on a tighter early budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Full Product Development?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Full product development&lt;/strong&gt; is a broader, deeper build. It is meant for launch readiness at a much higher level.&lt;/p&gt;

&lt;p&gt;This path includes the complete experience you expect users to need from day one. It often covers multiple workflows, polished UI, integrations, security layers, support systems, and growth features. Instead of proving one idea, the goal is to enter the market with a stronger competitive position.&lt;/p&gt;

&lt;p&gt;Full product development often includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete user journeys&lt;/li&gt;
&lt;li&gt;Advanced architecture&lt;/li&gt;
&lt;li&gt;Admin dashboards&lt;/li&gt;
&lt;li&gt;User roles and permissions&lt;/li&gt;
&lt;li&gt;Strong security and compliance&lt;/li&gt;
&lt;li&gt;Third-party integrations&lt;/li&gt;
&lt;li&gt;Performance optimization&lt;/li&gt;
&lt;li&gt;App store readiness at a high standard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This path takes more money, more time, and more coordination. But it can be the right call when partial functionality would hurt trust, adoption, or brand positioning.&lt;/p&gt;

&lt;p&gt;Think fintech, healthtech, enterprise SaaS, or tools that depend on deep workflow completion. In those cases, a half-step product can backfire pretty badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  MVP vs Full Product at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;MVP App Development&lt;/th&gt;
&lt;th&gt;Full Product Development&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Goal&lt;/td&gt;
&lt;td&gt;Validate idea fast&lt;/td&gt;
&lt;td&gt;Launch complete solution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to Market&lt;/td&gt;
&lt;td&gt;Faster&lt;/td&gt;
&lt;td&gt;Slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Lower upfront&lt;/td&gt;
&lt;td&gt;Higher upfront&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk&lt;/td&gt;
&lt;td&gt;Lower early risk&lt;/td&gt;
&lt;td&gt;Higher early risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature Scope&lt;/td&gt;
&lt;td&gt;Core features only&lt;/td&gt;
&lt;td&gt;Broad and polished feature set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best For&lt;/td&gt;
&lt;td&gt;New ideas, testing demand&lt;/td&gt;
&lt;td&gt;Proven ideas, funded growth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Feedback&lt;/td&gt;
&lt;td&gt;Gathered early&lt;/td&gt;
&lt;td&gt;Gathered after larger build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;Easier to pivot&lt;/td&gt;
&lt;td&gt;Harder to change direction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That table makes the difference feel clean. In real life, though, the decision is usually messier.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Startups Should Choose MVP App Development
&lt;/h2&gt;

&lt;p&gt;Here is the truth: most early-stage startups should start with &lt;strong&gt;MVP app development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because uncertainty is expensive.&lt;/p&gt;

&lt;p&gt;If you do not know how users will behave, which features matter most, or what people will actually pay for, building a full product too early is risky. MVP app development helps reduce that risk while keeping your startup strategy grounded in real-world data.&lt;/p&gt;

&lt;p&gt;Choose MVP app development when:&lt;/p&gt;

&lt;h3&gt;
  
  
  You Are Still Testing Market Demand
&lt;/h3&gt;

&lt;p&gt;You have a strong idea, but demand is not fully proven yet. You need real users, real behavior, and real signals before investing deeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Budget Is Tight
&lt;/h3&gt;

&lt;p&gt;Early money should buy learning, not just code. MVP app development helps stretch capital while still moving the product into users’ hands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed Matters More Than Completeness
&lt;/h3&gt;

&lt;p&gt;Sometimes getting live first matters most. Especially in crowded categories, speed helps you claim attention, gather traction, and improve before others catch up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Product Can Deliver Value with One Core Use Case
&lt;/h3&gt;

&lt;p&gt;If one main workflow can satisfy early users, that is a great MVP sign.&lt;/p&gt;

&lt;h3&gt;
  
  
  You Expect the Product to Change Fast
&lt;/h3&gt;

&lt;p&gt;If your assumptions may shift after launch, MVP app development keeps your startup strategy flexible. You can adapt without rebuilding a giant product.&lt;/p&gt;

&lt;p&gt;An MVP is not the finish line. It is the smartest first move when clarity is still forming.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Full Product Development is the Better Choice
&lt;/h2&gt;

&lt;p&gt;Now let’s flip it.&lt;/p&gt;

&lt;p&gt;Sometimes an MVP is not enough. Sometimes it creates a weak first impression, lowers trust, or simply cannot support the product promise.&lt;/p&gt;

&lt;p&gt;Choose full product development when:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Market Need Is Already Validated
&lt;/h3&gt;

&lt;p&gt;You already know people want the product. Maybe you have strong customer interviews, waitlist demand, pilot users, or a successful offline service model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Users Expect a Complete Experience
&lt;/h3&gt;

&lt;p&gt;Some categories do not tolerate missing features well. If users need reliability, depth, and trust from day one, a thin launch can hurt adoption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compliance or Security Is Critical
&lt;/h3&gt;

&lt;p&gt;In regulated products, a lightweight version may not be safe or viable. You may need stronger infrastructure before launch.&lt;/p&gt;

&lt;h3&gt;
  
  
  You Have Funding and a Clear Roadmap
&lt;/h3&gt;

&lt;p&gt;If you have the capital, product clarity, and team strength to build properly, full product development can accelerate long-term growth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Competitive Space Is Mature
&lt;/h3&gt;

&lt;p&gt;In mature markets, users compare every product quickly. A shallow launch may not survive long enough to improve.&lt;/p&gt;

&lt;p&gt;This is also where platform expectations matter. If your audience is premium, product quality matters fast, and many founders in this stage start planning with an &lt;a href="https://quokkalabs.com/ios-app-development?utm_source=dev.to&amp;amp;utm_medium=Dev.to&amp;amp;utm_campaign=Dhruv"&gt;ios mobile application development company&lt;/a&gt; to deliver a more refined early experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 7 Questions Founders Should Ask Before Deciding
&lt;/h2&gt;

&lt;p&gt;This is where the decision gets practical.&lt;/p&gt;

&lt;p&gt;Ask these seven questions before choosing MVP app development or full product development.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. What Is Still Unproven?
&lt;/h3&gt;

&lt;p&gt;List the biggest unknowns. Is it demand, pricing, user behavior, retention, or feature value? If major unknowns still exist, MVP app development usually wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. How Much Runway Do You Have?
&lt;/h3&gt;

&lt;p&gt;Do not plan like you have endless time. If the runway is short, your startup strategy should favor learning and speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. What Happens If the Product Is Missing Key Features?
&lt;/h3&gt;

&lt;p&gt;Will users still get value, or will they bounce? That answer tells you how thin your first version can be.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. How Expensive Will Future Changes Be?
&lt;/h3&gt;

&lt;p&gt;If the product is likely to pivot, building the full version now could waste serious money.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What Do Users Need to Trust You?
&lt;/h3&gt;

&lt;p&gt;Trust changes by category. A social app can launch lighter. A finance app really cannot.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Are You Selling a Vision or Solving an Immediate Pain?
&lt;/h3&gt;

&lt;p&gt;If users have urgent pain, MVP app development can work beautifully. If you are selling a complete operating system for a workflow, depth may matter more.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. What Is the Real Business Goal of Version One?
&lt;/h3&gt;

&lt;p&gt;Be honest here. Are you trying to validate, attract investors, close pilots, or scale revenue? Your first build should match that goal, not just your excitement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Startups Make
&lt;/h2&gt;

&lt;p&gt;A lot of founders make this harder than it needs to be.&lt;/p&gt;

&lt;p&gt;Here are the most common mistakes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calling a feature-heavy product an MVP&lt;/li&gt;
&lt;li&gt;Launching too thin and learning nothing useful&lt;/li&gt;
&lt;li&gt;Building based on opinions instead of user evidence&lt;/li&gt;
&lt;li&gt;Ignoring technical foundations completely&lt;/li&gt;
&lt;li&gt;Letting investor pressure shape the wrong scope&lt;/li&gt;
&lt;li&gt;Confusing design polish with product readiness&lt;/li&gt;
&lt;li&gt;Skipping analytics in MVP app development&lt;/li&gt;
&lt;li&gt;Choosing full product development without a clear growth plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one hurts a lot. Because once time and money disappear, strategy gets reactive fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Smarter Way to Decide
&lt;/h2&gt;

&lt;p&gt;Instead of seeing this as MVP versus full product, think in stages.&lt;/p&gt;

&lt;p&gt;A sharper startup strategy often looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define the core problem&lt;/li&gt;
&lt;li&gt;Build the smallest valuable product&lt;/li&gt;
&lt;li&gt;Launch to a focused user segment&lt;/li&gt;
&lt;li&gt;Measure behavior, not just feedback&lt;/li&gt;
&lt;li&gt;Improve based on evidence&lt;/li&gt;
&lt;li&gt;Expand into a stronger product release&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This staged model keeps MVP app development connected to long-term growth. It avoids random building. It also keeps founders from rushing into a full launch before the product earns it.&lt;/p&gt;

&lt;p&gt;That is the real win. Not just shipping faster, but learning smarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Verdict for Startups
&lt;/h2&gt;

&lt;p&gt;So, how do startups decide between MVP app development and full product development?&lt;/p&gt;

&lt;p&gt;They decide by looking at uncertainty, money, urgency, user expectations, and product risk.&lt;/p&gt;

&lt;p&gt;If you need proof, speed, and flexibility, &lt;strong&gt;MVP app development&lt;/strong&gt; is usually the right move. If you already have validation, stronger funding, and a category that demands completeness, &lt;strong&gt;full product development&lt;/strong&gt; can be the better bet.&lt;/p&gt;

&lt;p&gt;The smartest founders do not build to impress themselves. They build to reduce risk and increase traction. That is what strong startup strategy looks like in practice.&lt;/p&gt;

&lt;p&gt;And if you are still unsure, work with a team that can challenge scope, not just code whatever is requested. A skilled &lt;a href="https://quokkalabs.com/mobile-app-development-company-in-new-york?utm_source=dev.to&amp;amp;utm_medium=Dev.to&amp;amp;utm_campaign=Dhruv"&gt;mobile app development company in new york&lt;/a&gt; can help you map the right path based on business goals, not just feature lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You do not need the biggest product first. You need the right product first.&lt;/p&gt;

&lt;p&gt;That is why &lt;strong&gt;MVP app development&lt;/strong&gt; remains one of the smartest ways to launch without wasting time, money, or momentum. Still, it is not always enough. Some ideas need more depth from day one. The key is knowing what your market demands right now, not what looks impressive on paper.&lt;/p&gt;

&lt;p&gt;Build for evidence. Build for traction. Build for the stage you are actually in.&lt;/p&gt;

&lt;p&gt;That is how startups make better product bets, and survive long enough to scale.If you got startup, its better decision to reach for solution to an &lt;a href="https://quokkalabs.com/android-app-development?utm_source=dev.to&amp;amp;utm_medium=Dev.to&amp;amp;utm_campaign=Dhruv"&gt;Android application development company&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mvp</category>
      <category>webdev</category>
      <category>development</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
