<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shoogar</title>
    <description>The latest articles on DEV Community by Shoogar (@shoogarsoft).</description>
    <link>https://dev.to/shoogarsoft</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3947924%2F6705f95b-12a1-417a-bd45-830e4bb917f2.png</url>
      <title>DEV Community: Shoogar</title>
      <link>https://dev.to/shoogarsoft</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shoogarsoft"/>
    <language>en</language>
    <item>
      <title>devto-devto-generated-20260608_080104-1.md</title>
      <dc:creator>Shoogar</dc:creator>
      <pubDate>Mon, 08 Jun 2026 14:01:06 +0000</pubDate>
      <link>https://dev.to/shoogarsoft/devto-devto-generated-20260608080104-1md-beo</link>
      <guid>https://dev.to/shoogarsoft/devto-devto-generated-20260608080104-1md-beo</guid>
      <description>&lt;p&gt;Not enough arguments following: p&lt;br&gt;
Usage: gemini [options] [command]&lt;/p&gt;

&lt;p&gt;Gemini CLI - Defaults to interactive mode. Use -p/--prompt for non-interactive (headless) mode.&lt;/p&gt;

&lt;p&gt;Commands:&lt;br&gt;
  gemini mcp                   Manage MCP servers&lt;br&gt;
  gemini extensions   Manage Gemini CLI extensions.  [aliases: extension]&lt;br&gt;
  gemini skills       Manage agent skills.  [aliases: skill]&lt;br&gt;
  gemini hooks        Manage Gemini CLI hooks.  [aliases: hook]&lt;br&gt;
  gemini gemma                 Manage local Gemma model routing&lt;br&gt;
  gemini [query..]             Launch Gemini CLI  [default]&lt;/p&gt;

&lt;p&gt;Positionals:&lt;br&gt;
  query  Initial prompt. Runs in interactive mode by default; use -p/--prompt for non-interactive.&lt;/p&gt;

&lt;p&gt;Options:&lt;br&gt;
  -d, --debug                     Run in debug mode (open debug console with F12)  [boolean] [default: false]&lt;br&gt;
  -m, --model                     Model  [string]&lt;br&gt;
  -p, --prompt                    Run in non-interactive (headless) mode with the given prompt. Appended to input on stdin (if any).  [string]&lt;br&gt;
  -i, --prompt-interactive        Execute the provided prompt and continue in interactive mode  [string]&lt;br&gt;
      --skip-trust                Trust the current workspace for this session.  [boolean] [default: false]&lt;br&gt;
  -w, --worktree                  Start Gemini in a new git worktree. If no name is provided, one is generated automatically.  [string]&lt;br&gt;
  -s, --sandbox                   Run in sandbox?  [boolean]&lt;br&gt;
  -y, --yolo                      Automatically accept all actions (aka YOLO mode, see &lt;a href="https://www.youtube.com/watch?v=xvFZjo5PgG0" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=xvFZjo5PgG0&lt;/a&gt; for more details)?  [boolean] [default: false]&lt;br&gt;
      --approval-mode             Set the approval mode: default (prompt for approval), auto_edit (auto-approve edit tools), yolo (auto-approve all tools), plan (read-only mode)  [string] [choices: "default", "auto_edit", "yolo", "plan"]&lt;br&gt;
      --policy                    Additional policy files or directories to load (comma-separated or multiple --policy)  [array]&lt;br&gt;
      --admin-policy              Additional admin policy files or directories to load (comma-separated or multiple --admin-policy)  [array]&lt;br&gt;
      --acp                       Starts the agent in ACP mode  [boolean]&lt;br&gt;
      --experimental-acp          Starts the agent in ACP mode (deprecated, use --acp instead)  [boolean]&lt;br&gt;
      --allowed-mcp-server-names  Allowed MCP server names  [array]&lt;br&gt;
      --allowed-tools             [DEPRECATED: Use Policy Engine instead See &lt;a href="https://geminicli.com/docs/core/policy-engine" rel="noopener noreferrer"&gt;https://geminicli.com/docs/core/policy-engine&lt;/a&gt;] Tools that are allowed to run without confirmation  [array]&lt;br&gt;
  -e, --extensions                A list of extensions to use. If not provided, all extensions are used.  [array]&lt;br&gt;
  -l, --list-extensions           List all available extensions and exit.  [boolean]&lt;br&gt;
  -r, --resume                    Resume a previous session. Use "latest" for most recent or index number (e.g. --resume 5)  [string]&lt;br&gt;
      --session-file              Load a session from a JSON file  [string]&lt;br&gt;
      --session-id                Start a new session with a manually provided UUID.  [string]&lt;br&gt;
      --list-sessions             List available sessions for the current project and exit.  [boolean]&lt;br&gt;
      --delete-session            Delete a session by index number (use --list-sessions to see available sessions).  [string]&lt;br&gt;
      --include-directories       Additional directories to include in the workspace (comma-separated or multiple --include-directories)  [array]&lt;br&gt;
      --screen-reader             Enable screen reader mode for accessibility.  [boolean]&lt;br&gt;
  -o, --output-format             The format of the CLI output.  [string] [choices: "text", "json", "stream-json"]&lt;br&gt;
      --raw-output                Disable sanitization of model output (e.g. allow ANSI escape sequences). WARNING: This can be a security risk if the model output is untrusted.  [boolean]&lt;br&gt;
      --accept-raw-output-risk    Suppress the security warning when using --raw-output.  [boolean]&lt;br&gt;
  -v, --version                   Show version number  [boolean]&lt;br&gt;
  -h, --help                      Show help  [boolean]&lt;/p&gt;

</description>
      <category>career</category>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Picking the right resale platform is a routing problem, not a price problem</title>
      <dc:creator>Shoogar</dc:creator>
      <pubDate>Fri, 05 Jun 2026 14:01:42 +0000</pubDate>
      <link>https://dev.to/shoogarsoft/picking-the-right-resale-platform-is-a-routing-problem-not-a-price-problem-34md</link>
      <guid>https://dev.to/shoogarsoft/picking-the-right-resale-platform-is-a-routing-problem-not-a-price-problem-34md</guid>
      <description>&lt;h1&gt;
  
  
  Picking the right resale platform is a routing problem, not a price problem
&lt;/h1&gt;

&lt;p&gt;A friend of mine buys vintage Levi's at estate sales. She's good at it. But twice now I've watched her list a $90 jacket on Facebook Marketplace because that's the app she had open, when the same jacket clears $140 on Depop in two days. The item was never the problem. The destination was.&lt;/p&gt;

&lt;p&gt;I've been building a thrift-pricing tool, and for a while I treated "where should this sell" as an afterthought — a string I appended to the price estimate. eBay for most things, Depop for streetwear, done. That was wrong, and the way it was wrong is interesting enough to write up.&lt;/p&gt;

&lt;p&gt;The real question isn't "which platform has the highest price." It's "which platform gives the best expected outcome for &lt;em&gt;this&lt;/em&gt; item, accounting for how fast it sells, what the fees take, and how much the seller has to do." Those four variables pull in different directions, and the naive version optimizes only the first one.&lt;/p&gt;

&lt;p&gt;Here's the concrete failure. eBay almost always shows the highest sold prices for collectibles and electronics, so a top-price heuristic routes nearly everything there. But eBay's final value fee is ~13%, listings can sit for weeks, and for a $25 pair of jeans the effort-to-payout ratio is terrible. Depop and Poshmark skew toward fashion buyers who'll pay more for the same garment and buy faster, even though their fees are similar or worse. Facebook Marketplace has no selling fee and instant local cash, but only for things people will drive to pick up — furniture, appliances, bikes. A single "highest price wins" rule gets the furniture case exactly backwards.&lt;/p&gt;

&lt;p&gt;So I reframed it as a routing problem. Each platform is a candidate destination with a score, and the score is a function of the item's attributes, not a fixed ranking.&lt;/p&gt;

&lt;p&gt;The inputs that actually matter, roughly in order of signal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Category and subcategory.&lt;/strong&gt; Fashion vs electronics vs furniture vs collectibles is the single biggest splitter. Get this right and you've made 70% of the decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price band.&lt;/strong&gt; Sub-$30 items punish slow, high-fee platforms because the absolute fee is small but the time cost dominates. High-value items justify the wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shippability.&lt;/strong&gt; A dresser can't go on Poshmark in any practical sense. Weight and dimensions quietly veto whole platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand and "search demand."&lt;/strong&gt; A known brand (Carhartt, Le Creuset, Sony) has buyers actively searching on specific platforms. Generic items don't, and do better with browse-driven local sale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model I landed on isn't machine learning — it's an explicit scoring function, and I want to defend that choice because it's unfashionable. I have a small table of platform profiles (typical fee %, median days-to-sell by category, audience skew, shippability requirement). For a given item I compute an expected-net-per-week-of-effort number per platform and rank them. Something close to:&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="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;estimatedPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feeRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;demandMultiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;medianDaysToSell&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;shippingFriction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's crude. The &lt;code&gt;estimatedPrice&lt;/code&gt; per platform is itself an estimate stacked on an estimate, and &lt;code&gt;medianDaysToSell&lt;/code&gt; is a table I assembled from public data and seller anecdotes, not a live feed. I'm not going to pretend that's rigorous. But the explicit version has one property an ML model wouldn't give me cheaply: when it routes a couch to eBay, I can read the table and see that shippability friction should have vetoed it, and I can fix the rule in one line. A black box that's confidently wrong on furniture is much worse than a transparent rule that's wrong in a way I can inspect.&lt;/p&gt;

&lt;p&gt;The vision model feeds this. Item identification comes from a multimodal model (GPT-4o class) that returns category, probable brand, and condition cues from the photo. That output is the input to the routing function — and it's also where the whole thing is most fragile. If the model calls a Coach bag "generic brown handbag," the demand multiplier collapses and it routes to local sale instead of Poshmark, costing the seller real money. The routing logic is only as good as the identification feeding it, and identification is the part I trust least. It will misidentify things. I surface the detected brand prominently so a human can catch it before listing, rather than hiding the assumption inside a confident-looking recommendation.&lt;/p&gt;

&lt;p&gt;The tradeoff I'm still not satisfied with: the fee and days-to-sell tables are static. Real fee structures change, promoted-listing dynamics shift, and a category that sold in 3 days last year takes 9 now. The honest version of this tool pulls live comparable-sale velocity per platform. I haven't built that — the data access is uneven across platforms, and some of them actively don't want to be queried — so I'm shipping with periodically-updated tables and being upfront that they're approximations. It's the weakest part of the system and I'd rather say so than dress it up.&lt;/p&gt;

&lt;p&gt;What surprised me is how much value sits in just &lt;em&gt;not defaulting to the open app&lt;/em&gt;. Even an imperfect routing score beats "whatever I had open," because the baseline isn't a smart choice — it's habit. My friend doesn't need a perfect model. She needs something to say "this one's a Depop jacket, not a Facebook jacket" before she lists it wrong.&lt;/p&gt;

&lt;p&gt;If you want to see the version that's running, it's at &lt;a href="https://thriftflipper.app" rel="noopener noreferrer"&gt;https://thriftflipper.app&lt;/a&gt; — photo an item, get the value range and the suggested platform. It's rough on identification and the platform tables are approximations, but it works for the decision it's meant to help with.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Running Facial CV in the Browser: What Breaks When You Refuse to Send Pixels to a Server</title>
      <dc:creator>Shoogar</dc:creator>
      <pubDate>Fri, 05 Jun 2026 13:54:43 +0000</pubDate>
      <link>https://dev.to/shoogarsoft/running-facial-cv-in-the-browser-what-breaks-when-you-refuse-to-send-pixels-to-a-server-5h8</link>
      <guid>https://dev.to/shoogarsoft/running-facial-cv-in-the-browser-what-breaks-when-you-refuse-to-send-pixels-to-a-server-5h8</guid>
      <description>&lt;h1&gt;
  
  
  Running Facial CV in the Browser: What Breaks When You Refuse to Send Pixels to a Server
&lt;/h1&gt;

&lt;p&gt;Most try-on demos I've seen cheat. The phone uploads a frame, a GPU somewhere in us-east-1 runs the heavy model, and the result streams back. It looks magical in a controlled demo and falls apart the moment someone's on hotel wifi or actually reads your privacy policy.&lt;/p&gt;

&lt;p&gt;I build Mirrrd, a try-on tool for makeup. Early on I made a constraint that turned out to be the whole engineering problem: no facial frames leave the device. Not "encrypted in transit," not "deleted after 24 hours." They never go anywhere. That sounds like a marketing line, but it forces a pile of real technical decisions, and I want to walk through the ones that actually hurt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive version works for about a second
&lt;/h2&gt;

&lt;p&gt;The obvious approach: grab the webcam with &lt;code&gt;getUserMedia&lt;/code&gt;, run a face landmark model on each frame, composite the makeup, draw to a canvas. On my desktop with a discrete GPU this hits 60fps and feels great. Ship it.&lt;/p&gt;

&lt;p&gt;Then you open it on a three-year-old Android phone and the frame rate drops to single digits, the fans (if it had fans) would spin up, and the battery visibly drains. The desktop demo lied to you because desktops have thermal headroom phones don't. On-device means on &lt;em&gt;that&lt;/em&gt; device, and the device you have to design for is the median phone, not your dev machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the time actually goes
&lt;/h2&gt;

&lt;p&gt;I assumed inference would be the bottleneck. It mostly isn't, once you pick the right model size. The hidden costs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Getting the frame off the camera and into a tensor. The &lt;code&gt;ImageBitmap&lt;/code&gt; → tensor copy is not free, and doing it naively means a round trip through CPU memory every frame.&lt;/li&gt;
&lt;li&gt;The composite pass. Blending product color onto skin while respecting the existing lighting is per-pixel work, and if you do it in JavaScript on a canvas you've already lost.&lt;/li&gt;
&lt;li&gt;Garbage collection. Allocating a new tensor every frame and letting it get collected gives you periodic stalls that read as "jank" even when average FPS looks fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fixes are unglamorous. Reuse tensors instead of allocating per frame. Move the composite into a WebGL fragment shader so it runs on the GPU where the frame already lives. Keep the landmark model and the rendering on the same backend so you're not shuffling data between CPU and GPU twice per frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model size is a product decision, not a config value
&lt;/h2&gt;

&lt;p&gt;You can run a 200-landmark face mesh or a lighter model. The heavy one is more stable around the lips and eyes, which matters for makeup placement. The light one runs on phones that would otherwise be excluded entirely.&lt;/p&gt;

&lt;p&gt;I went back and forth and landed on shipping the lighter mesh and spending the saved budget on temporal smoothing instead. A slightly less precise landmark that's stable across frames looks far better than a precise one that jitters. Users read jitter as "broken." They read a 2px placement error as nothing at all, because they've never seen the ground truth.&lt;/p&gt;

&lt;p&gt;This is the kind of tradeoff that doesn't show up in a benchmark table. Landmark accuracy is measurable; "looks broken to a human" is not, and the second one is what gets you uninstalled.&lt;/p&gt;

&lt;h2&gt;
  
  
  What on-device genuinely costs you
&lt;/h2&gt;

&lt;p&gt;I'll be honest about the downsides, because the privacy framing makes it tempting to pretend there aren't any.&lt;/p&gt;

&lt;p&gt;You ship the model to every user. That's a multi-megabyte download before anything works, and you eat it on first load. Caching helps on repeat visits but the first impression is slower than a server-side competitor whose model the user never downloads.&lt;/p&gt;

&lt;p&gt;You can't quietly improve the model for everyone overnight. Server-side, you swap a checkpoint and every user gets the upgrade. On-device, the old version keeps running until the user reloads and pulls the new bundle. Your model fleet is whatever versions happen to be cached out there.&lt;/p&gt;

&lt;p&gt;And you lose the data flywheel, on purpose. Companies that collect try-on frames can train on real usage. I can't, because I don't have the frames, and I decided that's the trade. It means I improve the model from public and synthetic data and from explicit, opt-in feedback, which is slower. I think it's the right call for a beauty product where the input is literally your face, but it is a real cost and anyone telling you on-device is strictly better is selling something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'm still not happy with
&lt;/h2&gt;

&lt;p&gt;Lighting estimation from a single uncalibrated webcam is hard, and doing it without a server-side model that's seen millions of frames is harder. The current version is conservative: when it can't confidently estimate your lighting, it tells you the preview may be off rather than rendering a confident-looking result that's wrong. I'd rather under-promise on a frame than show someone a foundation shade that looks perfect on screen and wrong in the mirror, which is the exact failure that makes people return products.&lt;/p&gt;

&lt;p&gt;That conservatism is a stopgap, not a solution. Real ambient light estimation on-device is still open enough that I won't pretend I've closed it.&lt;/p&gt;

&lt;p&gt;If you want to see how the on-device version actually performs on your phone, it's at mirrrd.com. It's free during the beta, partly because I genuinely need to know which phones it falls over on, and the only way to find that out without collecting your camera data is to ask you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>What I built after spending 5 minutes per item checking eBay in thrift stores</title>
      <dc:creator>Shoogar</dc:creator>
      <pubDate>Fri, 29 May 2026 06:02:35 +0000</pubDate>
      <link>https://dev.to/shoogarsoft/what-i-built-after-spending-5-minutes-per-item-checking-ebay-in-thrift-stores-18hb</link>
      <guid>https://dev.to/shoogarsoft/what-i-built-after-spending-5-minutes-per-item-checking-ebay-in-thrift-stores-18hb</guid>
      <description>&lt;h1&gt;
  
  
  What I built after spending 5 minutes per item checking eBay in thrift stores
&lt;/h1&gt;

&lt;p&gt;I'm a developer who thrift shops on weekends. For a while I was the person standing in the housewares aisle, phone out, searching eBay sold listings for a ceramic figurine I couldn't even name properly. It took forever. Half the time I'd give up and either buy it blind or leave it.&lt;/p&gt;

&lt;p&gt;So I built a tool that does the lookup in the time it takes to take a photo. Here's what that actually required technically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core pipeline
&lt;/h2&gt;

&lt;p&gt;The basic flow sounds simple: photo goes in, structured valuation comes out. In practice, getting this to run in under 4 seconds across a range of item categories required more iteration than I expected.&lt;/p&gt;

&lt;p&gt;The first step is identification. I'm using a vision model (GPT-4o) to identify what the item is — not just "ceramic mug" but ideally something specific enough to search against sold listings: brand, approximate era, notable markings, condition. The prompt engineering here took a while to get right. Too vague and the search returns garbage. Too specific and you get zero results for anything that isn't a well-known collectible.&lt;/p&gt;

&lt;p&gt;The second step is the sold listings lookup. This is where most of the latency risk lives. I'm querying eBay's Browse API for completed listings filtered to sold items, then pulling a sample of recent results to build a price range. The API is well-documented and the response times are acceptable, but "acceptable" and "fast" aren't the same thing. I run the identification and the search query in parallel where possible, using the first pass of the vision model output to kick off a search before the full identification is complete.&lt;/p&gt;

&lt;p&gt;The third piece is platform recommendation. This is the least sophisticated part of the system right now. I'm using category signals from the identification step combined with price range to route toward eBay, Depop, Facebook Marketplace, or Poshmark. Clothing and vintage fashion skew toward Depop. High-value collectibles skew toward eBay. Bulky or local-pickup items skew toward Facebook. It's a heuristic, not a learned model. I'll probably revisit this once I have enough scan data to see where things actually sell.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "category agnostic" costs you
&lt;/h2&gt;

&lt;p&gt;The hardest part of building this wasn't any one technical piece. It was making the identification step reliable across completely different categories of items: vintage clothing, old electronics, pottery, board games, tools, sneakers.&lt;/p&gt;

&lt;p&gt;A model that's good at identifying sneaker brands by colorway is not automatically good at reading hallmarks on silver flatware. The prompt structure I landed on asks the model to reason about the item in layers — what category it probably belongs to, what identifying features are visible, what searches would be likely to find comparable sold listings. That layered approach helped, but it also added tokens and latency. There's a tradeoff.&lt;/p&gt;

&lt;p&gt;I also had to accept that the system will misidentify things. An unlabeled piece of pottery from a regional studio will come back with low confidence and a wide price range. That's honest — it's what eBay would return too if you searched vaguely. I'd rather surface that uncertainty than fake precision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The latency problem
&lt;/h2&gt;

&lt;p&gt;Four seconds sounds fast for a multi-step pipeline that involves a vision model call, an external API call, and a final synthesis step. It's achievable but not comfortable.&lt;/p&gt;

&lt;p&gt;The two things that moved the needle most were parallelizing the eBay query with the tail end of the identification step, and caching category-level context so the synthesis prompt doesn't need to re-derive it from scratch. For items with high-confidence identification, I can sometimes shave another half-second by skipping a confirmation pass.&lt;/p&gt;

&lt;p&gt;The mobile web experience matters here too. Most people will use this standing in a store on a cell connection. I'm not doing any client-side image processing — the photo goes up, the response comes back, done. Keeping the payload small and the server-side processing tight matters more than any clever frontend optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't build
&lt;/h2&gt;

&lt;p&gt;I spent time early on thinking about building a database of sold listings that I'd populate and maintain myself, so I could query it faster and have more control over the data. I scrapped that. eBay's sold listings data is current in a way I could never replicate at this stage, and building a scraping pipeline to maintain freshness would be a significant ongoing maintenance burden for marginal latency gains. External API dependency with rate limits is a better problem to have than stale data.&lt;/p&gt;

&lt;p&gt;I also didn't build account-required signup for the first few scans. The friction of creating an account before someone can see if the thing works at all seemed like it would kill adoption before it started. The first five scans work without an account; after that you need to create one to keep going. This required a little extra work on the session/credit-tracking side but it was worth doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it lives now
&lt;/h2&gt;

&lt;p&gt;The app is at &lt;a href="https://thriftflipper.app" rel="noopener noreferrer"&gt;thriftflipper.app&lt;/a&gt;. It's a web app, no download required, works on mobile. Ten free scans to start. I built it primarily for my own use case, but the feedback from other thrift shoppers has been useful for figuring out what the rough edges are.&lt;/p&gt;

&lt;p&gt;The thing I didn't anticipate was how much the speed matters not just for convenience but for behavior change. When a lookup takes 30 seconds, you do it for items you're already confident about. When it takes 4 seconds, you check things you'd otherwise skip — which is where the interesting finds are.&lt;/p&gt;

&lt;p&gt;If you're building anything in this space (real-time lookup, vision-to-search pipelines, pay-as-you-go credit systems on a small SaaS), happy to compare notes in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Why Shade Matching Online Keeps Failing (It's a Lighting Problem, Not a Photo Problem)</title>
      <dc:creator>Shoogar</dc:creator>
      <pubDate>Fri, 29 May 2026 05:59:42 +0000</pubDate>
      <link>https://dev.to/shoogarsoft/why-shade-matching-online-keeps-failing-its-a-lighting-problem-not-a-photo-problem-fdm</link>
      <guid>https://dev.to/shoogarsoft/why-shade-matching-online-keeps-failing-its-a-lighting-problem-not-a-photo-problem-fdm</guid>
      <description>&lt;h1&gt;
  
  
  Why Shade Matching Online Keeps Failing (It's a Lighting Problem, Not a Photo Problem)
&lt;/h1&gt;

&lt;p&gt;When I started building Mirrrd, I assumed the hard part would be the AR rendering — getting a lipstick to follow lip geometry in real time. That turned out to be the second hardest thing. The first was understanding why the same foundation shade looks completely different on three different phones showing the same product image.&lt;/p&gt;

&lt;p&gt;It's a lighting problem disguised as a color problem, and once you see it you can't unsee it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual problem with online shade matching
&lt;/h2&gt;

&lt;p&gt;Every product photo you see in a beauty store is lit under controlled studio conditions, usually 5500K daylight-balanced strobes. Your skin, right now, is lit by whatever's in your environment — a warm 2700K LED, fluorescent overheads, a window with afternoon sun, a monitor's blue glow. Color is not an intrinsic property of an object. It's a relational property between a surface, a light source, and a detector (your eye, or a camera sensor).&lt;/p&gt;

&lt;p&gt;Foundation shade "Warm Beige 230" photographed under studio lighting has a specific spectral reflectance curve. That curve will look different under your bathroom lighting, different again on your phone screen (which has its own white point calibration), and different still against your actual skin under your actual light. This is called metamerism, and it's why you can buy what looks like a perfect match online and receive something that reads completely orange under fluorescent light.&lt;/p&gt;

&lt;p&gt;Most beauty e-commerce tries to solve this with better product photos or "shade finder" quizzes. Both help at the margins. Neither addresses the core issue: the color rendering environment is completely outside their control.&lt;/p&gt;

&lt;h2&gt;
  
  
  What on-device processing actually buys you
&lt;/h2&gt;

&lt;p&gt;The approach I took with Mirrrd is to stop trying to predict how a shade will look and instead simulate it directly in your environment, on your device, under your current lighting.&lt;/p&gt;

&lt;p&gt;This requires a few pieces working together:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Facial geometry estimation&lt;/strong&gt; — you need a rough 3D mesh of the face to know where to apply color, how it follows contours, and how shadows fall. MediaPipe's Face Mesh gives you 468 landmarks in real time on device, which is enough to do decent contour-aware blending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skin tone detection and segmentation&lt;/strong&gt; — before you can simulate a product, you need to isolate skin pixels from background, hair, eyes, and lips. This is where most implementations fall apart. A naive color-range mask breaks instantly across different skin tones and lighting. You need a segmentation model that's actually trained on diversity across the full Fitzpatrick scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lighting estimation&lt;/strong&gt; — this is the piece most AR beauty apps skip, and it's the piece that makes results look fake. If you don't account for the dominant light direction and color temperature in the scene, your virtual product will never look like it actually belongs on the face. Even a rough spherical harmonic lighting estimate — something you can compute from the image itself — makes a significant difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Color space math&lt;/strong&gt; — makeup doesn't layer like digital paint. Lipstick has translucency. Foundation has a skin-interaction model. Blush scatters. You can fake most of this with LAB color space blending and adjustable opacity, but it requires careful tuning per product category.&lt;/p&gt;

&lt;p&gt;The key thing about doing all of this on-device: the facial geometry data and skin segmentation never leave the phone. I made this a hard constraint early on, not primarily for marketing reasons but because storing biometric geometry is a liability I didn't want to take on as a solo founder. GDPR, BIPA, and the emerging patchwork of state-level biometric laws are complicated enough without building a central store of face meshes. The on-device approach sidesteps that entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it breaks
&lt;/h2&gt;

&lt;p&gt;I'll be honest about the limitations because they're real.&lt;/p&gt;

&lt;p&gt;Extreme lighting conditions — very low light, direct harsh backlighting — degrade the lighting estimation and the segmentation quality simultaneously. The simulation still runs, but the result is less reliable.&lt;/p&gt;

&lt;p&gt;Glasses are a pain. The face mesh handles them reasonably but any product that overlaps the frame area (eyeshadow, eye liner) clips in ways that look wrong.&lt;/p&gt;

&lt;p&gt;Very dark or very light skin tones at the extremes of the Fitzpatrick scale still need more training data than I have. The segmentation is decent but the product simulation parameters were tuned more heavily on mid-range tones and I'm actively working on this.&lt;/p&gt;

&lt;p&gt;Real-time performance on older or lower-end devices is a genuine concern. The pipeline runs at 24–30fps on a mid-2022 iPhone. On older Android devices you can drop below 20fps, which makes the try-on feel laggy and breaks the illusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shade matching payoff
&lt;/h2&gt;

&lt;p&gt;Despite the limitations, the approach does solve the core problem I set out to fix. When you try a foundation shade in Mirrrd, you're seeing it rendered on your face, under your lighting, against your skin tone. The metamerism issue doesn't go away entirely — your screen still has its own white point — but it collapses from a four-variable problem (product photo lighting × product photo camera × your screen × your skin) to a two-variable one (your screen × your skin).&lt;/p&gt;

&lt;p&gt;Return rates for wrong shades are one of the dirtier secrets in beauty e-commerce. I've seen estimates from 15–25% for foundation and concealer specifically. Not all of those are shade mismatches — fit, formula, texture matter too — but shade is consistently cited as the top reason. That's a fixable problem with sufficient rendering fidelity, and getting there without requiring biometric data storage seems like the right path.&lt;/p&gt;

&lt;p&gt;If you're building in this space or have questions about the on-device CV pipeline, I'm happy to get into specifics in the comments. Mirrrd is currently in beta at &lt;a href="https://mirrrd.com" rel="noopener noreferrer"&gt;mirrrd.com&lt;/a&gt; — free to try if you want to see the simulation in practice.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I automated my job search pipeline — here's what the architecture looks like</title>
      <dc:creator>Shoogar</dc:creator>
      <pubDate>Fri, 29 May 2026 05:48:14 +0000</pubDate>
      <link>https://dev.to/shoogarsoft/i-automated-my-job-search-pipeline-heres-what-the-architecture-looks-like-3jbk</link>
      <guid>https://dev.to/shoogarsoft/i-automated-my-job-search-pipeline-heres-what-the-architecture-looks-like-3jbk</guid>
      <description>&lt;h1&gt;
  
  
  I automated my job search pipeline — here's what the architecture looks like
&lt;/h1&gt;

&lt;p&gt;A few months ago I was spending about an hour every morning doing the same thing: opening Glassdoor, LinkedIn, Indeed, a couple of niche boards, filtering by role and location, skimming postings to see if they were actually relevant, then closing 80% of them because they weren't. That's before writing a single word of a cover letter.&lt;/p&gt;

&lt;p&gt;I'm a developer. I automate repetitive tasks. So I started pulling on that thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core problem is signal-to-noise
&lt;/h2&gt;

&lt;p&gt;The job board aggregation problem looks simple from the outside: fetch postings, deduplicate, display. But the part that actually wastes your time isn't the volume — it's that every posting looks plausible until you read it. A "senior fullstack developer" role might require 10 years of Salesforce experience buried in paragraph four. You only find out after reading it.&lt;/p&gt;

&lt;p&gt;The fix I landed on was using the job description text itself as an input to an AI relevance score before surfacing the result to the user. You upload your resume once. Every posting gets scored against it before you see it. Jobs scoring below a threshold get filtered out entirely.&lt;/p&gt;

&lt;p&gt;The scoring prompt matters a lot here. You can't just ask "is this relevant?" and get useful output. I structure it as: role alignment (does the job title and core responsibilities match the candidate's experience), skills gap (are there hard requirements the resume clearly doesn't meet), seniority fit, and location/remote compatibility. Each dimension contributes to a composite score, and the model returns a short explanation alongside the number so you can sanity-check its reasoning.&lt;/p&gt;

&lt;p&gt;That alone cuts the time spent reading irrelevant postings by a significant margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-source scraping without getting rate-limited
&lt;/h2&gt;

&lt;p&gt;The second piece is source aggregation. Different job boards serve different audiences: Adzuna works well for broad discovery, Job Bank Canada for government and public sector, We Work Remotely and Himalayas for remote engineering roles. Running searches across all of them on demand would be too slow, so the approach is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect country from IP and profile settings at search time&lt;/li&gt;
&lt;li&gt;Select the relevant source subset automatically&lt;/li&gt;
&lt;li&gt;Fan out requests with per-domain rate limiting and caching&lt;/li&gt;
&lt;li&gt;Normalize the response format before AI scoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The normalization step is tedious but necessary. Every board returns slightly different field names, date formats, and description structures. A unified schema (title, company, location, posted_at, description, source_url) makes downstream processing consistent.&lt;/p&gt;

&lt;p&gt;One thing I got wrong initially: I tried to scrape employer career pages directly via a generic crawler. That's a maintenance nightmare. Every site has different markup, anti-bot measures, and update cadence. I pulled back from that for most sources and kept it selective.&lt;/p&gt;

&lt;h2&gt;
  
  
  The resume generation problem
&lt;/h2&gt;

&lt;p&gt;This is where I spent most of my time, and honestly where the interesting technical work is.&lt;/p&gt;

&lt;p&gt;The problem with a generic resume isn't that it's bad. ATS systems do keyword matching before a human ever reads the document. A strong candidate applying with a resume that uses "built" where the job description says "developed" might score lower than a weaker candidate who happened to use the right synonyms. That's the system working as designed, which is frustrating but real.&lt;/p&gt;

&lt;p&gt;The approach I use is full job description ingestion: not just the title, but the complete text including requirements, responsibilities, and preferred qualifications. I extract keyword patterns from it (hard skills, tools, methodologies, verb patterns) and use that as a constraint layer when generating the resume and cover letter. The model isn't rewriting your experience; it's selecting which aspects of your real background to emphasize based on what this specific role asks for.&lt;/p&gt;

&lt;p&gt;The implementation is roughly: parse JD → extract weighted keywords → retrieve candidate experience bullets from profile → rank bullets by keyword overlap → regenerate summary and skills sections with mirrored language → generate cover letter grounded in the same keyword set.&lt;/p&gt;

&lt;p&gt;You can validate this works by pasting the output back into a resume scoring tool and checking the keyword match rate. It's not gaming the system; it's speaking the system's language.&lt;/p&gt;

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

&lt;p&gt;This became &lt;a href="https://scourr.app" rel="noopener noreferrer"&gt;scourr&lt;/a&gt;. It handles the multi-board discovery, AI relevance scoring, and ATS-optimized generation end-to-end. Free tier gives you 3 application generations a day with no credit card. Paid tiers are pay-as-you-go credits that don't expire.&lt;/p&gt;

&lt;p&gt;It's not going to replace every part of your job search. Networking still matters. Referrals still get you further. But the mechanical part (scanning boards, reading irrelevant postings, spending 45 minutes tailoring a resume for a job you're not sure about) can be automated.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I were building this again, I'd spend more time on the relevance scoring calibration earlier. The first version had too coarse a threshold and filtered out some genuinely good matches. The model also occasionally hallucinates seniority mismatches when job descriptions are poorly written, which produces confusing explanations. I added a "why this was flagged" display so users can override the score when the reasoning is clearly wrong.&lt;/p&gt;

&lt;p&gt;The cover letter generation is still the weakest part. It's good enough to use, but cover letters that sound generated are easy to spot. I've been iterating on prompts that produce more specific, less formulaic output, but it's harder than the resume problem.&lt;/p&gt;

&lt;p&gt;The other thing I underestimated was how much the source selection matters by region. The "just use LinkedIn" approach misses a huge portion of the market outside the US, particularly for government, non-profit, and regional employers. Building the source routing properly was worth the extra complexity.&lt;/p&gt;

&lt;p&gt;If you've built anything similar or have opinions on the ATS parsing side, I'd be interested to hear it.&lt;/p&gt;

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