<?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: marsdiscovery</title>
    <description>The latest articles on DEV Community by marsdiscovery (@marsdiscovery_3d15f443c16).</description>
    <link>https://dev.to/marsdiscovery_3d15f443c16</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3967735%2Febcffc8c-37bd-4c4d-a217-71fba4ca3392.jpg</url>
      <title>DEV Community: marsdiscovery</title>
      <link>https://dev.to/marsdiscovery_3d15f443c16</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marsdiscovery_3d15f443c16"/>
    <language>en</language>
    <item>
      <title>App store screenshots are a data problem: three small browser tools from a team that kept resizing</title>
      <dc:creator>marsdiscovery</dc:creator>
      <pubDate>Sun, 09 Aug 2026 02:29:15 +0000</pubDate>
      <link>https://dev.to/marsdiscovery_3d15f443c16/app-store-screenshots-are-a-data-problem-three-small-browser-tools-from-a-team-that-kept-resizing-5fdg</link>
      <guid>https://dev.to/marsdiscovery_3d15f443c16/app-store-screenshots-are-a-data-problem-three-small-browser-tools-from-a-team-that-kept-resizing-5fdg</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc58en24dg30vth3y8653.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc58en24dg30vth3y8653.png" alt=" " width="800" height="343"&gt;&lt;/a&gt;&lt;br&gt;
Every solo app launch ends the same way. The build is green, the tests pass, and then the store asks for something nobody put on the roadmap: a screenshot for every possible device class, an icon in sizes you cannot remember, a 1024x500 feature graphic, and a set of rules that quietly change every time Apple shows a new iPhone.&lt;/p&gt;

&lt;p&gt;We kept doing this by hand for our own releases. Two weeks later we had three browser tools instead of one evening of resizing, and they taught us more about the craft than the actual editing did.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why we started
&lt;/h2&gt;

&lt;p&gt;DevCove started as a plain developer toolbox: format, encode, generate, validate. Then shipping a small app made us face a pile of "pixel chores" that did not fit any of those categories. App Store wants a portrait, a landscape, an alt-display-size portrait. Google Play wants its own screenshots and a separate horizontal feature graphic. The sizes are close enough that you hope one export covers them, and different enough that it never does.&lt;/p&gt;

&lt;p&gt;We are the team that runs the site, so we chose the polite path. No server, no upload, no login: the work happens in the browser or it does not happen at all. That single old constraint shaped every later decision.&lt;/p&gt;
&lt;h2&gt;
  
  
  The first tool: one canvas, many presets
&lt;/h2&gt;

&lt;p&gt;The composer begins with raw screenshots of the real UI. A small canvas puts each screenshot under an optional headline and sub-headline, then renders the same document once per preset: 1290x2796 portrait, 1320x2868, landscape where the store accepts it.&lt;/p&gt;

&lt;p&gt;The layout is not saved as pixels. It is saved as a document: canvas size, text boxes, per-preset transforms. Every render re-measures the text against the target box, shrinks it until it fits on the canvas, and reports when it overflows. The same file reads fine on whatever preset the stores ship next year.&lt;/p&gt;

&lt;p&gt;That is the trade we made: template-based rendering instead of free-form drawing. You lose Photoshop-style art direction, and you gain the fact that the output is always the correct ratio. For us, a correct ratio was worth more than unlimited creative control. People scan the store page for a few seconds; consistency beats variety there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Presets are data, not code
&lt;/h2&gt;

&lt;p&gt;The resizer taught us the next lesson: a store requirement is a row in a table, not a function in your head.&lt;/p&gt;

&lt;p&gt;The presets live as plain data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppMarketPreset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requirementLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optional&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;officialUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;defaultExportFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row carries its official documentation link, so the UI can show "required" or "optional" next to the exact size, and open the platform page for the ones that confuse us. When Apple shipped the 6.9-inch display set, adding the new sizes meant adding rows and a test, not reworking the tool.&lt;/p&gt;

&lt;p&gt;The same data also drives the export checklist. Pick iPhone 6.9 portrait and the tool tells you which official result to check, in which format, with the requirement flag visible. It is the mechanical part of the job, minus the guesswork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Icons without AI generation
&lt;/h2&gt;

&lt;p&gt;The third one is the quietest decision. The icon generator does not generate an icon. It takes the logo the team already owns, lets you crop it or pad it (cover vs contain), sets a background color, and exports the PNG sizes for iOS, Android, and both stores in one ZIP.&lt;/p&gt;

&lt;p&gt;People sometimes ask why we did not wrap it in a "generate icon" model. The reason is practical: the logo is shipped artwork. A model will invent a variation that does not match the product, and the store then front-shows a drawing nobody signed off on. Cropping, padding, and sizing the real master is the boring, correct job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the schema kept us honest about
&lt;/h2&gt;

&lt;p&gt;The three tools use the same canvas primitives, so the shared code is small: load an image, crop it, scale it, export a Blob. The interesting parts are all data and rule: the preset table, the requirement flag, the official link. That is also where the bugs can hide.&lt;/p&gt;

&lt;p&gt;A few things we learned the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Common sizes drift over time.&lt;/strong&gt; Text renders fine in one ratio and wraps wrong in another; the re-measuring step exists because of that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Required" means required.&lt;/strong&gt; If a store really wants a specific display size, shipping the old one is not a smaller mistake, it is the same mistake at a different ratio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Promotional rules are per platform.&lt;/strong&gt; Google discourages "Install now" style wording inside artwork, Apple is strict about safe areas and text density. The copy checker matters as much as the sizes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it leaves the process
&lt;/h2&gt;

&lt;p&gt;We build &lt;a href="https://devcove.dev/en/" rel="noopener noreferrer"&gt;DevCove&lt;/a&gt;, and these three are browser tools we shipped because a small team cannot afford endless export rounds. The &lt;a href="https://devcove.dev/en/tools/app-store-screenshot-composer/" rel="noopener noreferrer"&gt;composer&lt;/a&gt;, the &lt;a href="https://devcove.dev/en/tools/app-market-image-resizer/" rel="noopener noreferrer"&gt;market resizer&lt;/a&gt;, and the &lt;a href="https://devcove.dev/en/tools/app-icon-generator/" rel="noopener noreferrer"&gt;icon generator&lt;/a&gt; are grouped into a single &lt;a href="https://devcove.dev/en/tools/category/app-store-assets/" rel="noopener noreferrer"&gt;app store assets category&lt;/a&gt;, and the category page keeps the platform order so the flow lines up with the actual list the store gives you.&lt;/p&gt;

&lt;p&gt;The heavy lifting is boring on purpose. The tooling does not make art; it makes sure the fields you do send are the fields the store expects, in the files that require them.&lt;/p&gt;

&lt;p&gt;A question for you, since dev.to is where the patterns are shared: when Apple announces a next display size, do you update screenshots as data or open a design tool and start over? We are still investing in presets-as-data; I would be honestly curious what that investment looks like four devices from now.&lt;/p&gt;

</description>
      <category>apps</category>
      <category>appstore</category>
    </item>
    <item>
      <title>Why We Built a Local-First App Store Screenshot Composer</title>
      <dc:creator>marsdiscovery</dc:creator>
      <pubDate>Sun, 02 Aug 2026 11:03:58 +0000</pubDate>
      <link>https://dev.to/marsdiscovery_3d15f443c16/why-we-built-a-local-first-app-store-screenshot-composer-19n0</link>
      <guid>https://dev.to/marsdiscovery_3d15f443c16/why-we-built-a-local-first-app-store-screenshot-composer-19n0</guid>
      <description>&lt;p&gt;We make DevCove, a small developer-toolbox site that runs entirely in the browser. One of the tools we shipped this year is an App Store Screenshot Composer — the thing that turns raw phone captures into store-ready marketing images. This post is the backstory: why a team building a toolbox ended up writing yet another screenshot tool, and the tradeoffs we made along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the idea came from
&lt;/h2&gt;

&lt;p&gt;The toolbox itself started with a simple belief: developers reach for the browser for small jobs, and they usually do not want those jobs uploading their files to a stranger's server. We built format converters, validators, generators — all collected at &lt;a href="https://devcove.dev/" rel="noopener noreferrer"&gt;devcove.dev&lt;/a&gt;, free and no account. Local processing became our default answer to "how should this work?" before anyone asked.&lt;/p&gt;

&lt;p&gt;The screenshot composer grew out of our own release routine. When we ship one of our apps, we need App Store screenshots: real captures of the app, at Apple's exact pixel sizes, with a headline that survives a phone thumbnail. The sizes are not a suggestion. App Store Connect rejects an upload that is off by one pixel, and as of this year Apple's primary targets are the 6.9-inch iPhone and 13-inch iPad. Google Play has a flexible range instead, but strict rules about text density and alpha channels, plus a Feature Graphic that must be exactly 1024×500 with no alpha.&lt;/p&gt;

&lt;p&gt;None of that is hard. It is just tedious enough that the tools we found were either subscriptions that wanted our captures uploaded, or templates that fell out of date the moment the spec list moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The obvious route we did not take
&lt;/h2&gt;

&lt;p&gt;The easy path would have been to use an existing SaaS screenshot generator: drop captures in, pick a device frame, export. It works, and plenty of people ship releases that way.&lt;/p&gt;

&lt;p&gt;We did not want to. Two reasons.&lt;/p&gt;

&lt;p&gt;First, our product principle is browser-local. Uploading unreleased screenshots to a service — screenshots that show features nobody has seen yet — felt like the opposite of the toolbox's reason for existing. We could not in good conscience recommend a workflow to visitors that we would not use ourselves.&lt;/p&gt;

&lt;p&gt;Second, deterministic output. When we use Figma or Photoshop templates, "export the right size" is manual and easy to miss a required slot. We wanted the layout engine and the export to agree by construction, so that what you see in the editor is what lands in the store console.&lt;/p&gt;

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

&lt;p&gt;The composer takes up to six real screenshots, lets you paste them with ⌘V, reorder them to match your export sequence, then design one size at a time for either store.&lt;/p&gt;

&lt;p&gt;The piece we are most happy with is the deterministic template set. Thirteen templates, and the same layout code drives both the live preview and the full-resolution Canvas export. There is no "close enough" step between what you see and what you get. That came out of a real annoyance: preview-and-export drift is how screenshots quietly ship with a headline cut off or a screenshot cropped differently than expected.&lt;/p&gt;

&lt;p&gt;Text overflow gets a hard stop rather than a silent badge. If a headline or subtitle cannot fit its area at the minimum readable size, export refuses and tells you which screenshot and which line. For a team that has submitted with copy that wrapped badly, that single check is worth more than the sum of the templates.&lt;/p&gt;

&lt;p&gt;We also embedded the policy rules we kept relearning, as live warnings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Play PNG output must have no alpha channel, so backgrounds get flattened.&lt;/li&gt;
&lt;li&gt;Text area should stay near or under 20% on Google Play; the tool estimates your ratio as you type.&lt;/li&gt;
&lt;li&gt;"Best", "#1", "million downloads" and hard CTAs like "Install now" get flagged, because ranking and superlative claims without context are exactly what gets listings flagged.&lt;/li&gt;
&lt;li&gt;Device frames are allowed but warned against on Google Play promotional surfaces, since they can reduce eligibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that replaces reading the store documentation. The presets were checked against the Apple and Google pages in mid-June, and we still point users to verify again on submission day, because the spec list moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;p&gt;A deterministic template composer is not a design suite. Game key art, custom illustration, and multi-locale brand campaigns still need Figma or Photoshop, and we say so on the page rather than pretending otherwise. Batch export stops at six screenshots, which matches the typical store set but not a big campaign. Those limits are deliberate; the tool is for the repeatable 90%, and being honest about the other 10% is part of the design.&lt;/p&gt;

&lt;p&gt;We have also had to accept that a local-first tool makes some things harder. There is no account, so there is no way to recover work across machines, and no cloud storage means every new browser session starts clean unless you re-upload. That is the price of the privacy guarantee, and we think it is the right trade for our audience, but we did not get to have both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we observed after shipping
&lt;/h2&gt;

&lt;p&gt;The most common reaction is not "nice templates". It is people asking why screenshots were so fiddly in the first place, which suggests the pain was universal and under-served. The policy warnings get more attention than the export button, which we did not predict and found encouraging: teams were worried about rejection, not about clicking.&lt;/p&gt;

&lt;p&gt;We also learned that local-first needs to be louder than a tagline. Visitors assume online tools upload by default. Making the local-processing notice prominent, and linking it to the actual reason (unreleased features staying on your machine), mattered more for trust than any feature did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying the workflow ourselves
&lt;/h2&gt;

&lt;p&gt;The composer is one tool inside DevCove, which we build and maintain. It is free, needs no account, and never uploads your captures — &lt;a href="https://devcove.dev/en/tools/app-store-screenshot-composer/" rel="noopener noreferrer"&gt;the App Store Screenshot Composer&lt;/a&gt; lives at a URL you can bookmark for your next release.&lt;/p&gt;

&lt;p&gt;If you ship mobile apps, how do you currently produce store screenshots — local templates, a subscription generator, or a designer? And if you have been bitten by a reject-after-upload, was it the pixels or the copy that got you? We are curious how your release workflow handles the spec drift between store updates.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>app</category>
      <category>appstore</category>
      <category>screenshot</category>
    </item>
    <item>
      <title>The webhook signature matched in Postman. Staging still returned 401.</title>
      <dc:creator>marsdiscovery</dc:creator>
      <pubDate>Tue, 23 Jun 2026 02:06:45 +0000</pubDate>
      <link>https://dev.to/marsdiscovery_3d15f443c16/anthropics-friday-kill-switch-changed-how-i-split-ai-from-deterministic-work-1cnc</link>
      <guid>https://dev.to/marsdiscovery_3d15f443c16/anthropics-friday-kill-switch-changed-how-i-split-ai-from-deterministic-work-1cnc</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcks4kbtc9lrtj5ag5uob.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcks4kbtc9lrtj5ag5uob.jpg" alt=" " width="800" height="343"&gt;&lt;/a&gt;&lt;br&gt;
We wired up a new billing webhook last sprint. Local tests passed. Postman collection green. Merged on Friday.&lt;/p&gt;

&lt;p&gt;Monday morning staging logged hundreds of &lt;code&gt;401 invalid signature&lt;/code&gt; from the provider's retry queue. The handler was maybe fifty lines. The secret lived in an env var copied straight from the vendor dashboard.&lt;/p&gt;

&lt;p&gt;I replayed the same payload in Postman with the same &lt;code&gt;X-Signature&lt;/code&gt; header. Verification passed. Staging failed on requests that looked identical.&lt;/p&gt;

&lt;p&gt;The gap wasn't the HMAC math on paper. It was &lt;strong&gt;which bytes&lt;/strong&gt; we hashed.&lt;/p&gt;

&lt;p&gt;Our API gateway parsed JSON before the route handler ran. By the time we read &lt;code&gt;req.body&lt;/code&gt;, it was a JavaScript object serialized again — keys in a different order, sometimes a trailing newline gone. The provider had signed the &lt;strong&gt;raw request body&lt;/strong&gt; from the wire. We were hashing a re-exported string.&lt;/p&gt;

&lt;p&gt;Everything in logs looked fine: one-line JSON, correct algorithm in the header, secret without obvious typos. That's what made it annoying. I spent an hour convinced the vendor had rotated keys without telling us.&lt;/p&gt;

&lt;p&gt;What I check now on webhook PRs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw body first.&lt;/strong&gt; If the framework touches JSON before your verifier runs, find where to hook the unparsed buffer. Signature schemes don't care about your ORM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret encoding.&lt;/strong&gt; Dashboards often show base64-looking strings. Is the env var the decoded key bytes, or the base64 text itself? Wrong layer and HMAC still "works" in a scratch script — with the wrong input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One failing payload, viewed cleanly.&lt;/strong&gt; I copy the exact body and signature into separate tabs so I'm not squinting at escaped quotes wrapped in a narrow terminal.&lt;/p&gt;

&lt;p&gt;Browser pages I keep for these checks — one job each, local processing, no account for a two-minute look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/json-formatter/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/json-formatter/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/base64/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/base64/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/hash-generator/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/hash-generator/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I maintain a small browser toolkit called DevCove for exactly these chores when I'm already in DevTools on another tab. &lt;code&gt;curl --data-binary&lt;/code&gt; and a server-side replay fixture still win for the real fix; this is for ruling out "wrong bytes" vs "wrong secret" before you open a ticket with the vendor.&lt;/p&gt;

&lt;p&gt;Half the outcome was code: verify on raw body, add one canned replay test. The other half was stopping the assumption that Postman parity means production parity.&lt;/p&gt;

&lt;p&gt;If you maintain signed webhooks — where do you usually lose the plot? Middleware reordering the body, secret encoding, or something else entirely?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Anthropic's Friday kill switch changed how I split AI from deterministic work</title>
      <dc:creator>marsdiscovery</dc:creator>
      <pubDate>Tue, 16 Jun 2026 06:29:33 +0000</pubDate>
      <link>https://dev.to/marsdiscovery_3d15f443c16/anthropics-friday-kill-switch-changed-how-i-split-ai-from-deterministic-work-i6n</link>
      <guid>https://dev.to/marsdiscovery_3d15f443c16/anthropics-friday-kill-switch-changed-how-i-split-ai-from-deterministic-work-i6n</guid>
      <description>&lt;p&gt;I had a half-finished refactor open in Cursor when a teammate dropped a link in Slack: Anthropic had pulled Fable 5 and Mythos 5 offline. Not a rate limit. Not scheduled maintenance. &lt;strong&gt;Gone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first thought wasn't policy. It was practical. A script we run in CI uses Claude to summarize test failures. Another engineer routes security-triage prompts through the API. We both assumed the model would be there on Monday like electricity.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A letter at 5:21 p.m.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;According to Anthropic and reporting from &lt;a href="https://www.lawfaremedia.org/article/a-kill-switch-for-frontier-ai" rel="noopener noreferrer"&gt;Lawfare&lt;/a&gt; and &lt;a href="https://techcrunch.com/2026/06/15/the-us-governments-anthropic-models-ban-was-never-about-an-ai-jailbreak/" rel="noopener noreferrer"&gt;TechCrunch&lt;/a&gt;, the U.S. Commerce Department sent a letter on Friday, June 12, at 5:21 p.m. Eastern. Citing export-control authorities, it told Anthropic to cut off Fable 5 and Mythos 5 for any foreign national — inside or outside the United States, including foreign employees at Anthropic itself.&lt;/p&gt;

&lt;p&gt;Anthropic can't reliably sort API customers by nationality. So the company did the only thing that guaranteed compliance: it disabled both models for &lt;strong&gt;everyone&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Within hours, one of the most capable model pairs on the market was unavailable worldwide. No court hearing. No public text of the letter. Just a product that existed on Thursday and didn't on the weekend.&lt;/p&gt;

&lt;p&gt;If you build on someone else's API, that sequence should bother you.&lt;/p&gt;

&lt;h2&gt;
  
  
  This wasn't really about a jailbreak — or maybe it was
&lt;/h2&gt;

&lt;p&gt;The official reasoning is still fuzzy. Anthropic believes the letter relates to a guardrail bypass. David Sacks framed it as a serious vulnerability — reportedly demonstrated by a "trusted partner" (widely reported as Amazon security researchers) — and said Anthropic refused to patch or de-deploy when asked.&lt;/p&gt;

&lt;p&gt;Anthropic pushed back: the issue was narrow, partly known before launch, and present in rival frontier models too. Pulling the models globally, they argued, would essentially halt frontier deployments for every lab if this became the standard.&lt;/p&gt;

&lt;p&gt;Then Katie Moussouris — founder of Luta Security — weighed in. Anthropic had shared a private paper describing the alleged bypass. Moussouris wrote that the behavior "should never have triggered an export control." The difference, she explained, was largely between asking a model to &lt;em&gt;review code for security issues&lt;/em&gt; and asking it to &lt;em&gt;fix this code&lt;/em&gt;. Same output class, different phrasing.&lt;/p&gt;

&lt;p&gt;Dozens of security researchers signed an open letter asking the administration to revoke the order. Their argument: yanking advanced models from defenders during an active dispute is worse for U.S. cybersecurity than the risk being debated.&lt;/p&gt;

&lt;p&gt;Axios, citing sources, described personality friction between Anthropic and the administration as a bigger driver than the technical report itself. That part is harder to prove. But the pattern fits: Anthropic is already suing over a Pentagon supply-chain blacklist after refusing unrestricted military use of its models. Whether or not you trust Anthropic's safety posture, it's difficult to read this as a purely technical intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export controls meet SaaS
&lt;/h2&gt;

&lt;p&gt;Lawfare's analysis is worth sitting with even if you don't follow trade policy. The Export Administration Regulations were built for chips and tangible technology. Biden's AI Diffusion Rule briefly put model weights on the control list; Trump rescinded it on retaking office. This Anthropic action may be the first time export controls were used to choke access to a live API model.&lt;/p&gt;

&lt;p&gt;That raises messy questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are the &lt;strong&gt;weights&lt;/strong&gt; being controlled, or the &lt;strong&gt;outputs&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Is foreign access to a U.S. SaaS product an "export" at all?&lt;/li&gt;
&lt;li&gt;If guardrails fail, does that justify a blanket ban on all non-U.S. persons — including allied researchers?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Congress passed nothing resembling a clear licensing regime for frontier AI before this happened. Anthropic's CEO had actually argued days earlier that government should be able to block dangerous deployments — with protections against arbitrary decisions. A standardless letter on a Friday evening is probably not what he meant.&lt;/p&gt;

&lt;p&gt;Foreign capitals will wonder whether American AI infrastructure is reliable for critical systems. Today's favorite U.S. model can become a bargaining chip tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke for developers on the ground
&lt;/h2&gt;

&lt;p&gt;Most of us aren't training frontier models. We paste stack traces into chat windows, wire up API calls, and move on. The kill switch still landed in real workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams mid-sprint on Fable or Mythos had to fail over, rewrite prompts, or pause features.&lt;/li&gt;
&lt;li&gt;Startups without multi-provider abstraction learned an expensive lesson about single-vendor dependency.&lt;/li&gt;
&lt;li&gt;Security engineers who wanted Claude for log triage found the tool missing during an already tense news cycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is catastrophic if you treat LLMs as optional accelerators. It &lt;strong&gt;is&lt;/strong&gt; catastrophic if you've designed your product so the model &lt;em&gt;is&lt;/em&gt; the product — no fallback, no offline path, no deterministic substitute for the boring parts.&lt;/p&gt;

&lt;p&gt;AI is great at judgment-heavy work: explaining an unfamiliar codebase, drafting a migration plan, suggesting why a flaky test might be order-dependent. It's terrible as the only way to decode a JWT, format a JSON error body, or confirm whether a log timestamp is seconds or milliseconds. Those jobs don't need reasoning. They need correctness, speed, and a clear boundary about where your data goes.&lt;/p&gt;

&lt;p&gt;When Anthropic's models went dark, the deterministic chores didn't disappear. Pipelines still emitted malformed JSON. Staging still handed us tokens we shouldn't paste into random websites. Cron expressions in YAML still deserved a second look before deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed — a little
&lt;/h2&gt;

&lt;p&gt;I'm not abandoning AI assistants. They're too useful for the messy middle of engineering. But the weekend reminded me to separate two buckets:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bucket A&lt;/strong&gt; — work where the model adds judgment: design tradeoffs, unfamiliar APIs, long explanations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bucket B&lt;/strong&gt; — pure transformation: format, decode, encode, validate, generate a UUID, parse a cron field.&lt;/p&gt;

&lt;p&gt;Bucket B should never depend on a frontier API being online, priced the same, or politically acceptable in every jurisdiction. It should run locally, finish in seconds, and not send customer payloads to a third party.&lt;/p&gt;

&lt;p&gt;I maintain a small browser workbench called &lt;a href="https://devcove.dev/" rel="noopener noreferrer"&gt;DevCove&lt;/a&gt; for exactly those Bucket B tasks — separate pages, one job each, processing in the browser after load. Not because local tools are morally superior, but because they're boring in the right way. They don't get export-controlled.&lt;/p&gt;

&lt;p&gt;Tabs I kept open even while the Claude API was down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devcove.dev/en/tools/json-formatter/" rel="noopener noreferrer"&gt;JSON formatter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devcove.dev/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;JWT decoder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devcove.dev/en/tools/timestamp-converter/" rel="noopener noreferrer"&gt;Timestamp converter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a longer take on why local-first tooling still matters when AI is everywhere, see &lt;a href="https://devcove.dev/en/articles/local-first-developer-tools-in-the-ai-era/" rel="noopener noreferrer"&gt;Local-first developer tools in the AI era&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jq&lt;/code&gt;, IDE plugins, and one-liners in the terminal all work too. The point isn't which tab you use. The point is having a fallback that doesn't require a model endpoint, an account, or a policy debate in Washington.&lt;/p&gt;

&lt;h2&gt;
  
  
  The precedent outlives the outage
&lt;/h2&gt;

&lt;p&gt;By the time you read this, Anthropic and the administration may have patched things up. Models may be back. That's almost beside the point.&lt;/p&gt;

&lt;p&gt;The precedent is set: a U.S. AI product can be switched off globally on short notice, with limited transparency, while the industry argues about whether the triggering incident even qualifies as a national-security event. If you're an engineering lead, that belongs in your risk register next to cloud region outages and vendor price hikes.&lt;/p&gt;

&lt;p&gt;If you're an individual developer, the lesson is smaller and more actionable: &lt;strong&gt;don't merge Bucket A and Bucket B.&lt;/strong&gt; Let AI help you think. Let local, deterministic tools handle the fragments of your day that shouldn't need a network round trip — especially the fragments that contain secrets.&lt;/p&gt;

&lt;p&gt;How are you handling provider risk now — multi-model routing, self-hosted weights for specific tasks, or fingers crossed and a cached API key in the vault?&lt;/p&gt;

&lt;p&gt;What's in your fallback plan when the API you depend on goes dark on a Friday?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>claude</category>
    </item>
    <item>
      <title>The webhook signature matched in Postman. Staging still returned 401.</title>
      <dc:creator>marsdiscovery</dc:creator>
      <pubDate>Sat, 06 Jun 2026 08:09:55 +0000</pubDate>
      <link>https://dev.to/marsdiscovery_3d15f443c16/the-webhook-signature-matched-in-postman-staging-still-returned-401-1fmj</link>
      <guid>https://dev.to/marsdiscovery_3d15f443c16/the-webhook-signature-matched-in-postman-staging-still-returned-401-1fmj</guid>
      <description>&lt;p&gt;We wired up a new billing webhook last sprint. Local tests passed. Postman collection green. Merged on Friday.&lt;/p&gt;

&lt;p&gt;Monday morning staging logged hundreds of &lt;code&gt;401 invalid signature&lt;/code&gt; from the provider's retry queue. The handler was maybe fifty lines. The secret lived in an env var copied straight from the vendor dashboard.&lt;/p&gt;

&lt;p&gt;I replayed the same payload in Postman with the same &lt;code&gt;X-Signature&lt;/code&gt; header. Verification passed. Staging failed on requests that looked identical.&lt;/p&gt;

&lt;p&gt;The gap wasn't the HMAC math on paper. It was &lt;strong&gt;which bytes&lt;/strong&gt; we hashed.&lt;/p&gt;

&lt;p&gt;Our API gateway parsed JSON before the route handler ran. By the time we read &lt;code&gt;req.body&lt;/code&gt;, it was a JavaScript object serialized again — keys in a different order, sometimes a trailing newline gone. The provider had signed the &lt;strong&gt;raw request body&lt;/strong&gt; from the wire. We were hashing a re-exported string.&lt;/p&gt;

&lt;p&gt;Everything in logs looked fine: one-line JSON, correct algorithm in the header, secret without obvious typos. That's what made it annoying. I spent an hour convinced the vendor had rotated keys without telling us.&lt;/p&gt;

&lt;p&gt;What I check now on webhook PRs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw body first.&lt;/strong&gt; If the framework touches JSON before your verifier runs, find where to hook the unparsed buffer. Signature schemes don't care about your ORM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret encoding.&lt;/strong&gt; Dashboards often show base64-looking strings. Is the env var the decoded key bytes, or the base64 text itself? Wrong layer and HMAC still "works" in a scratch script — with the wrong input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One failing payload, viewed cleanly.&lt;/strong&gt; I copy the exact body and signature into separate tabs so I'm not squinting at escaped quotes wrapped in a narrow terminal.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgc0xjym60rjae6c6axur.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%2Fgc0xjym60rjae6c6axur.png" alt=" " width="799" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Browser pages I keep for these checks — one job each, local processing, no account for a two-minute look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/json-formatter/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/json-formatter/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/base64/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/base64/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/hash-generator/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/hash-generator/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I maintain a small browser toolkit called DevCove for exactly these chores when I'm already in DevTools on another tab. &lt;code&gt;curl --data-binary&lt;/code&gt; and a server-side replay fixture still win for the real fix; this is for ruling out "wrong bytes" vs "wrong secret" before you open a ticket with the vendor.&lt;/p&gt;

&lt;p&gt;Half the outcome was code: verify on raw body, add one canned replay test. The other half was stopping the assumption that Postman parity means production parity.&lt;/p&gt;

&lt;p&gt;If you maintain signed webhooks — where do you usually lose the plot? Middleware reordering the body, secret encoding, or something else entirely?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The redirect_uri matched in the dashboard. OAuth still failed.</title>
      <dc:creator>marsdiscovery</dc:creator>
      <pubDate>Thu, 04 Jun 2026 07:18:26 +0000</pubDate>
      <link>https://dev.to/marsdiscovery_3d15f443c16/the-redirecturi-matched-in-the-dashboard-oauth-still-failed-50hl</link>
      <guid>https://dev.to/marsdiscovery_3d15f443c16/the-redirecturi-matched-in-the-dashboard-oauth-still-failed-50hl</guid>
      <description>&lt;p&gt;We spent an afternoon convinced the identity provider was broken. Login worked locally. Staging failed with the usual vague error — redirect mismatch, invalid request, pick your vendor wording.&lt;/p&gt;

&lt;p&gt;The redirect_uri in our admin console was copied from the ticket. Character for character, it looked identical to what the browser sent. No trailing slash debate. No http vs https surprise.&lt;/p&gt;

&lt;p&gt;Then I dumped the authorize URL from the network tab and compared it to what we had registered.&lt;/p&gt;

&lt;p&gt;One query value had been encoded twice. Another used + for a space in a place our stack later read as a literal plus. The path wasn't wrong. The encoding layers were fighting each other.&lt;/p&gt;

&lt;p&gt;This is the boring class of bug: nothing shows up in a unit test because each function "works" in isolation. The failure only appears when a full URL travels through a form, a framework helper, and a provider that compares strings literally.&lt;/p&gt;

&lt;p&gt;What I watch for now:&lt;/p&gt;

&lt;p&gt;Before blaming the IdP, decode the authorize URL once in a sane viewer. I want to see each query key and value split apart, not a single opaque percent soup.&lt;/p&gt;

&lt;p&gt;Plus vs %20. application/x-www-form-urlencoded treats + as space. Generic URI encoding uses %20. Mix those rules in one redirect chain and you get ghosts.&lt;/p&gt;

&lt;p&gt;Nested URLs. Some flows put a return URL inside a query parameter. Encode the inner URL as a component, not as "the whole string again with extra % on the %."&lt;/p&gt;

&lt;p&gt;One-line error JSON from the token endpoint still shows up in the same incident. Pretty-printing it once saves ten minutes of guessing whether the problem is OAuth or your own handler.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjazxciaz3eehclcrxapo.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%2Fjazxciaz3eehclcrxapo.png" alt=" " width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Browser tabs I keep for these checks — one job per page, local processing, no account for a two-minute look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/url-encoder/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/url-encoder/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/json-formatter/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/url-encoder/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcove.dev/en/tools/base64/" rel="noopener noreferrer"&gt;https://devcove.dev/en/tools/url-encoder/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I maintain a small toolkit called DevCove for exactly these chores (format, encode, decode in the tab I already have open). curl and server logs still win for reproduction; this is for the moment you're staring at a URL in DevTools and need to know if you're looking at double encoding or a real mismatch.&lt;/p&gt;

&lt;p&gt;Not claiming OAuth is easy. Half our fix was process: register the exact bytes the browser will send, document which encoder each service uses, stop "fixing" URLs by encoding until it looks scary.&lt;/p&gt;

&lt;p&gt;If you've shipped OAuth lately — where do you catch encoding bugs first? Provider logs, proxy logs, or a scratch pad in the browser?&lt;/p&gt;

</description>
      <category>api</category>
      <category>devjournal</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
