<?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: Max/Wang</title>
    <description>The latest articles on DEV Community by Max/Wang (@maxslashwang).</description>
    <link>https://dev.to/maxslashwang</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%2F2255922%2Fdd90580a-3288-48c4-8cc2-aa2bf7ab9671.jpg</url>
      <title>DEV Community: Max/Wang</title>
      <link>https://dev.to/maxslashwang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxslashwang"/>
    <language>en</language>
    <item>
      <title>What Is FSR? A Practical Look at FSR Video Upscaling for Browser-Based Tools</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Sun, 13 Sep 2026 14:13:49 +0000</pubDate>
      <link>https://dev.to/maxslashwang/what-is-fsr-a-practical-look-at-fsr-video-upscaling-for-browser-based-tools-2fhk</link>
      <guid>https://dev.to/maxslashwang/what-is-fsr-a-practical-look-at-fsr-video-upscaling-for-browser-based-tools-2fhk</guid>
      <description>&lt;p&gt;If you've ever used a browser-based video upscaler that doesn't require uploading your file to a server, there's a good chance FSR is the tech running under the hood. It powers a lot of lightweight, fast upscaling tools that work on almost any device — no GPU required. Here's what FSR actually is, how the different versions differ, and why its earliest version turns out to be a surprisingly good fit for client-side, WASM-based tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is FSR?
&lt;/h2&gt;

&lt;p&gt;FSR stands for &lt;strong&gt;FidelityFX Super Resolution&lt;/strong&gt;, an upscaling technology built by AMD. It started as a gaming feature: render a game at a lower resolution (say 1080p), then let FSR reconstruct the missing detail to display it at a higher resolution (like 4K) — cutting GPU load while keeping the image close to native quality.&lt;/p&gt;

&lt;p&gt;The same core idea has since been applied outside of gaming, to video and images in general, which is why it now shows up in browser-based upscaling tools too.&lt;/p&gt;

&lt;p&gt;The key difference between FSR and Nvidia's DLSS is that FSR doesn't need dedicated AI hardware like Nvidia's Tensor Cores. The earlier FSR versions run on almost any GPU — Intel graphics, older cards, whatever — which is exactly why it's so easy to port into cross-platform, cross-device apps (including ones compiled to WASM).&lt;/p&gt;

&lt;h2&gt;
  
  
  The FSR generations, briefly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FSR 1 — spatial upscaling, no AI.&lt;/strong&gt; Looks at a single frame and processes it in two steps: EASU (edge-adaptive upscaling that interpolates detail more intelligently around edges) and RCAS (contrast-adaptive sharpening applied afterward). No motion vectors, no previous frames, no model — just an algorithm. That makes it extremely light, and it's the version that actually runs well inside WebAssembly in a browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FSR 2 — temporal upscaling, still no AI.&lt;/strong&gt; Uses data from previous frames plus motion vectors to reconstruct detail more accurately. Better quality, but it needs motion data — fine for a game engine that always has it, much harder for a standalone video file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FSR 3 — adds frame generation.&lt;/strong&gt; Keeps FSR 2's upscaling core and inserts synthetic frames between real ones to boost FPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FSR 4 — a full switch to machine learning.&lt;/strong&gt; Runs on the dedicated AI cores of AMD's RDNA4 GPUs (Radeon RX 9000 series). Quality is close to DLSS, but it only runs on that specific new hardware — it loses the "runs on anything" advantage of the earlier versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AMD is reportedly also working on a next-gen version (codename "Redstone") for a fully neural rendering pipeline, but that's real-time gaming tech, not something you'd deploy in a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  FSR for video vs. FSR for games
&lt;/h2&gt;

&lt;p&gt;This is the part that's easy to mix up. In-game FSR runs in real time, tightly wired into the engine so it can pull motion vectors and depth-buffer data — information only a game engine has on hand.&lt;/p&gt;

&lt;p&gt;Applied to video or still images, a tool doesn't have any of that internal data. All it has is the input file. That's why video/image tools rely on FSR's &lt;strong&gt;spatial branch&lt;/strong&gt; — the FSR 1 approach, processing each frame independently through EASU + RCAS — rather than the temporal versions, which need motion data a plain video file simply doesn't carry.&lt;/p&gt;

&lt;p&gt;AMD also has &lt;strong&gt;RSR (Radeon Super Resolution)&lt;/strong&gt;, which is essentially FSR 1 running at the driver level to upscale anything on screen — including video playing in YouTube or VLC. It's the closest thing to "FSR for video" without needing dedicated app support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why FSR fits browser-based, client-side tools so well
&lt;/h2&gt;

&lt;p&gt;If you're building (or evaluating) a video upscaler that runs entirely client-side, with no file ever touching a server, going with FSR's spatial branch over a heavier AI model (Real-ESRGAN, waifu2x, Topaz Video AI, etc.) makes practical sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No model to ship.&lt;/strong&gt; AI upscalers usually come with deep learning models that are tens to hundreds of MB, need to be downloaded to the browser, and run through inference — resource-heavy. FSR's spatial approach is just math (edge detection + sharpening), so it compiles down to compact WASM that runs fast even on weaker machines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No GPU dependency.&lt;/strong&gt; Since it doesn't touch AI/Tensor cores, it runs fine on an ordinary CPU through WASM — important since not every visitor has a strong GPU, and browsers have limited hardware-acceleration access anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame-independent processing fits a "no upload" architecture.&lt;/strong&gt; No motion vectors, no shared state between frames — everything can be processed locally, which is exactly what a fully client-side tool needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality/speed tradeoff that's usually worth it.&lt;/strong&gt; Detail reconstruction isn't as sharp as a dedicated AI model or FSR 4, but it's enough to meaningfully reduce blur/pixelation, and it runs many times faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Classic interpolation (Bicubic, Lanczos, etc.)&lt;/td&gt;
&lt;td&gt;Computes new pixels via a pure math interpolation formula&lt;/td&gt;
&lt;td&gt;Extremely lightweight, built into most image libraries&lt;/td&gt;
&lt;td&gt;Looks blurry, no real detail reconstruction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FSR (spatial — FSR 1)&lt;/td&gt;
&lt;td&gt;Edge detection + adaptive sharpening&lt;/td&gt;
&lt;td&gt;Lightweight, no AI needed, cross-platform, WASM-friendly&lt;/td&gt;
&lt;td&gt;Lower quality than dedicated AI upscalers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI upscaler (Real-ESRGAN, waifu2x, etc.)&lt;/td&gt;
&lt;td&gt;Deep learning model trained to reconstruct missing detail&lt;/td&gt;
&lt;td&gt;Strong detail reconstruction, especially on low-quality sources&lt;/td&gt;
&lt;td&gt;Heavy models, resource-intensive, slow on weaker machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FSR 4 (AI, new AMD GPUs only)&lt;/td&gt;
&lt;td&gt;ML model on dedicated AI cores&lt;/td&gt;
&lt;td&gt;Quality near top-tier AI upscaling, real-time optimized&lt;/td&gt;
&lt;td&gt;Needs specific GPU hardware — no browser, no plain CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;FSR was built to solve a gaming problem — less GPU load, still-sharp images — but the simple algorithmic core of FSR 1 opened up a genuinely useful side application: fast, lightweight upscaling that runs almost anywhere, including inside a browser via WebAssembly, with the file never leaving the device. That combination of speed and near-universal compatibility is exactly why it keeps showing up in modern client-side video tools.&lt;/p&gt;

&lt;p&gt;If you're working on anything WASM + media processing, curious to hear what upscaling/compression approaches others have landed on for the "no server upload" constraint — drop a comment below.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amd.com/en/products/graphics/technologies/fidelityfx/super-resolution.html" rel="noopener noreferrer"&gt;AMD FSR™ Technologies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gpuopen.com/learn/amd-fsr4-gpuopen-release/" rel="noopener noreferrer"&gt;AMD FSR 4 now available on GPUOpen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gpuopen.com/amd-fsr-upscaling/" rel="noopener noreferrer"&gt;AMD FSR Upscaling — GPUOpen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amd.com/en/developer/resources/technical-articles/2026/upscale-everything-super-resolution-across-amd-hardware-.html" rel="noopener noreferrer"&gt;Upscale Everything: Super-Resolution Across AMD Hardware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amd.com/rsr" rel="noopener noreferrer"&gt;AMD Radeon Super Resolution (RSR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tweaktown.com/news/95373/amd-announces-radeon-fsr-upscaling-for-video-playback-in-youtube-and-vlc/index.html" rel="noopener noreferrer"&gt;AMD announces Radeon FSR upscaling for video playback in YouTube and VLC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.techradar.com/computing/gpu/what-is-amd-fsr" rel="noopener noreferrer"&gt;What is AMD FSR? The upscaling technology explained — TechRadar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aiseesoft.com/tutorial/amd-video-upscaling.html" rel="noopener noreferrer"&gt;AMD Video Upscale: FidelityFX Super Resolution and RSR — Aiseesoft&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How Many Times Can You Compress a Video Before It Looks Bad?</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Sun, 13 Sep 2026 13:37:11 +0000</pubDate>
      <link>https://dev.to/maxslashwang/how-many-times-can-you-compress-a-video-before-it-looks-bad-ojc</link>
      <guid>https://dev.to/maxslashwang/how-many-times-can-you-compress-a-video-before-it-looks-bad-ojc</guid>
      <description>&lt;p&gt;&lt;em&gt;If a compressed video looks worse than you expected, the problem usually isn't the compressor — it's that the file was already compressed once before you touched it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The result looks worse than it should — here's why
&lt;/h2&gt;

&lt;p&gt;You compress a video, and it comes out blockier or softer than you expected, even on a setting that's supposed to keep quality close to the original. Nine times out of ten, the file you fed into the compressor wasn't actually the original — it was already a compressed copy, and you just compressed it a second time without realizing it.&lt;/p&gt;

&lt;p&gt;This happens constantly without anyone noticing, because the first compression pass usually isn't a choice you make on purpose. You record a video, a messaging app or social platform re-compresses it the moment you upload or send it, you download that version back down later, and then run it through a compressor again to hit a size limit somewhere else. By the time you're looking at the final result, it's had two or three rounds of lossy compression stacked on top of each other — and each one throws away more detail than the last, in a way that adds up faster than most people expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why compression loss compounds instead of staying flat
&lt;/h2&gt;

&lt;p&gt;Every time a video is compressed, the encoder makes judgment calls about which detail to keep and which to discard, based on what it can actually see in that specific file at that moment. The first pass discards genuine picture detail — fine texture, subtle gradients, motion blur, small variations in color — because that's the least noticeable place to save space when the source is clean. Second and later passes don't have that untouched detail to work with anymore. The encoder is now looking at a file that already has some blockiness and softness baked into it, and it has to make the same kind of decisions again, except this time there's less real information left to preserve.&lt;/p&gt;

&lt;p&gt;Here's the part that makes it worse than simply "losing a bit more each time": some of what the second pass now treats as real picture detail is actually just artifacts left over from the first compression. Blocky edges around a face, slight color banding in a sky, a faint checkerboard pattern in a dark scene — the encoder doesn't know these came from a previous compression pass rather than the original footage, so it spends bits trying to preserve them as if they mattered, which leaves even less budget for the parts of the image that actually do.&lt;/p&gt;

&lt;p&gt;It's the same idea as photocopying a photocopy. The first copy loses a little sharpness around the edges of the text. Copy that copy, and you're not starting from the clean original — you're compounding the loss that's already there, and the machine has no way of knowing which smudges were in the first copy versus the real page. Video compression works the same way, just with motion and color instead of ink on paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the extra compression passes sneak in
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recording&lt;/strong&gt; — Phones and screen recorders already compress footage as they save it. This is the real original, and it's already been through one encoding pass before you've done anything at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uploading somewhere first&lt;/strong&gt; — Instagram, WhatsApp sent "as video," Discord, and most messaging apps re-encode whatever you send through them, usually at a lower bitrate than you'd choose yourself, to keep their own storage and bandwidth costs down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downloading that version back&lt;/strong&gt; — If you then save the file from the app or platform instead of pulling it from your own camera roll, you're now working from the re-compressed copy, not the source — and there's often no visual clue that this happened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editing and re-exporting&lt;/strong&gt; — Trimming or adding captions in a phone editing app usually means a full re-encode of the whole clip, even for a one-second cut — another pass, whether or not you changed the picture itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compressing again to hit a new limit&lt;/strong&gt; — Squeezing that already-compressed, already-re-exported file down further for a different platform's size limit is the pass where the accumulated loss finally becomes visible to the naked eye.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these individual steps is unreasonable on its own — trimming a clip, sending it to a friend, then needing it smaller for a different app is a completely normal sequence of events. The problem is that each step quietly re-encodes the file, and nothing in the process warns you that it's happening. By the time you notice the quality drop, you're usually several steps removed from the original file and it's not obvious which step actually caused it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to tell if a file has already been compressed
&lt;/h2&gt;

&lt;p&gt;A few practical signs are worth checking before you compress something a second time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the video came from anywhere other than your own camera roll or recording software — downloaded from a chat, saved from a social app, pulled off someone else's share link — assume it's already a compressed copy rather than a source file.&lt;/li&gt;
&lt;li&gt;Zooming into a still frame and looking for faint blocky squares in flat areas like skies or walls, or a slightly smeared look around fast-moving edges, is a reasonably reliable tell that a file has already been through at least one lossy pass, even if it still looks fine at normal viewing size.&lt;/li&gt;
&lt;li&gt;File size relative to length and resolution is another clue: a five-minute 1080p clip sitting at only 15MB has almost certainly already been compressed hard by whatever app touched it last, since an untouched original at that resolution and length is normally many times larger.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to avoid it
&lt;/h2&gt;

&lt;p&gt;The fix is simpler than it sounds: always compress from the earliest, least-processed copy of the file you can find — usually the one still sitting in your camera roll or original recording folder, not a version you re-downloaded from somewhere else. If you know a video is headed to more than one place with different size limits, compress once at the lowest target size you'll actually need and reuse that single file, rather than compressing a fresh copy for each platform starting from an already-compressed one each time.&lt;/p&gt;

&lt;p&gt;It also helps to match the compression level to what the video is actually for. If there's any chance you'll re-edit or re-export a clip later — adding captions, trimming it further, repurposing it for a different platform — keep it at a lighter setting the first time. &lt;a href="https://squishyfile.com" rel="noopener noreferrer"&gt;SquishyFile's "Light" level&lt;/a&gt; trims redundant data without pushing the bitrate down aggressively, leaving more headroom before a second pass becomes visible, compared to jumping straight to the smallest possible file. Save "Max squish" for the last step before sending, once you know there's no next re-encode coming after it.&lt;/p&gt;

&lt;p&gt;If storage allows, it's worth keeping the original, uncompressed recording around for a while even after you've shared a compressed version — a video you can always compress again from scratch is a lot more forgiving than one where the only copy left has already been through two or three rounds of lossy encoding.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Is there a hard number of times I can compress a video?&lt;/strong&gt;&lt;br&gt;
Not a fixed one — it depends on how aggressive each pass is and how much motion or fine detail the footage has to begin with. A few light passes on simple footage can look fine; two or three aggressive passes on anything with texture or motion usually won't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does re-compressing the same file with the same settings make it worse each time?&lt;/strong&gt;&lt;br&gt;
Yes, even with identical settings, because the encoder is working from a file that already lost some detail last time — there's less real information to preserve on each subsequent pass, even though the settings you're choosing haven't changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If a platform re-compresses my upload anyway, is compressing it myself first pointless?&lt;/strong&gt;&lt;br&gt;
No — starting from a smaller, well-compressed file usually means the platform's own re-encode has less work to do and causes noticeably less additional damage than feeding it a huge, high-bitrate original the platform then has to crush down on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does SquishyFile compress the same file twice if I run it through the tool again?&lt;/strong&gt;&lt;br&gt;
Each run compresses whatever file you drop in at that moment — if that file is already a compressed export from a previous run, yes, that's a second pass. Keep your original source file around if you might need to compress it differently later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the safest setting for a video I might need again later?&lt;/strong&gt;&lt;br&gt;
Light compression, and keep the original uncompressed file if you have the storage for it. You can always compress harder later if you need a smaller file; you can't get lost detail back once it's gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I visually tell how many times a video has been compressed?&lt;/strong&gt;&lt;br&gt;
Not precisely, but blockiness in flat areas, smeared fast motion, and a file size that's unusually small for the resolution and length are all signs of at least one prior pass — worth checking before you compress it again.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Need to compress a video right now without losing more detail than necessary? &lt;a href="https://squishyfile.com" rel="noopener noreferrer"&gt;SquishyFile&lt;/a&gt; runs entirely in your browser — no uploads, no server round-trip, just drag, pick a level, and download.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The first shader I ever wrote drew nothing at all</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Sat, 12 Sep 2026 14:36:44 +0000</pubDate>
      <link>https://dev.to/maxslashwang/the-first-shader-i-ever-wrote-drew-nothing-at-all-18ol</link>
      <guid>https://dev.to/maxslashwang/the-first-shader-i-ever-wrote-drew-nothing-at-all-18ol</guid>
      <description>&lt;p&gt;Not an error. Not a crash. Just a blank black canvas, for about two hours, while I quietly assumed WebGL itself was broken. It wasn't — I'd forgotten to bind one uniform, the GPU had every reason in the world to draw nothing, and it did exactly that without complaint. That's usually how the first week with shaders goes: coming from normal programming, where a missing variable throws an exception you can read, and landing in a world where a mistake just... produces silence. No stack trace. No pixel. Nothing to grep for.&lt;/p&gt;

&lt;p&gt;The reason it's disorienting is that a shader isn't really "code" in the sense a JavaScript or Python function is. It's a tiny program, usually a few dozen lines, that the GPU runs an enormous number of times in parallel, and it has almost no memory of anything outside itself. Once that model clicks, a lot of GPU behavior that looks like magic — real-time video effects, live filters, a game rendering a complex scene at 144 frames a second — turns out to be a fairly simple idea, repeated an absurd number of times per second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two programs, two completely different jobs
&lt;/h2&gt;

&lt;p&gt;Every WebGL draw call runs two shader programs back to back, and they're not doing the same kind of work at all.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;vertex shader&lt;/strong&gt; runs once per vertex — a corner point of a triangle, since everything the GPU draws eventually decomposes into triangles. Its only job is geometry: given this point's input data, where does it end up on screen? That's it. It doesn't know about color, lighting, or texture in any deep sense; it just outputs a position, and the vertex shader for a single triangle might run only three times total.&lt;/p&gt;

&lt;p&gt;Once every vertex of a triangle has a screen position, a fixed-function stage called the rasterizer figures out which pixels fall inside that triangle. This part isn't programmable — it's baked into the GPU's silicon — and it hands off every pixel it finds to the second program.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;fragment shader&lt;/strong&gt; (called a "pixel shader" if you came from DirectX) runs once for every one of those pixels, and this is where basically all the interesting work happens: color, lighting, textures, effects. A fragment shader has no idea it's one of two million running this frame. It gets fed whatever data got interpolated across the triangle for its specific location, and its only job is to answer one question — what color is this pixel? — and hand back an answer.&lt;/p&gt;

&lt;p&gt;Here's a trick that still feels a little illegal the first time you see it: if all you need is to process a full-screen image — which is exactly what a video filter or a post-processing effect needs — you don't need a vertex buffer, an index buffer, or even a real triangle mesh. You draw one triangle, deliberately oversized so it covers the entire screen and then some, computed directly from the vertex's built-in ID with no input data at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A single triangle, built from gl_VertexID alone,&lt;/span&gt;
&lt;span class="c1"&gt;// big enough to cover the whole screen. No buffers needed.&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;gl_VertexID&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&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;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;gl_VertexID&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;gl_Position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;vec4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;vUv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// passed to the fragment shader, interpolated per-pixel&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three vertices, zero setup, and every pixel on screen gets covered exactly once. The part that got cut off past the visible viewport just gets clipped away for free by the rasterizer. It's the standard way to run a "process this whole image" shader, and once you've seen it you start noticing it's what almost every real-time video or image effect is quietly built on.&lt;/p&gt;

&lt;h2&gt;
  
  
  How data actually gets into a shader
&lt;/h2&gt;

&lt;p&gt;None of this is useful if a shader can only see itself — it needs a way to read in the image you're processing and whatever settings you want to control. GLSL gives you exactly two doors in, and picking the right one matters.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;uniform&lt;/strong&gt; is a value that's the same for every single pixel in a given draw call — you set it once from JavaScript before drawing, and every invocation of the fragment shader that frame sees the identical number. This is the natural home for anything a slider controls: scanline intensity, grain amount, how strong a chromatic-aberration effect should be. The GPU doesn't recompute it per pixel; it's just a constant sitting there while two million pixels each read the same value and do something different with it based on their own position.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;texture&lt;/strong&gt; is the other door, and it's how the actual image gets in — the current video frame, uploaded to the GPU as a 2D grid of color data. The fragment shader doesn't loop over that grid; it samples a single texel at whatever coordinate corresponds to the pixel it's currently responsible for, using UV coordinates that got interpolated across the triangle from the vertex shader. Want to blur something, or detect an edge? Sample a few neighboring coordinates around your own position instead of just one, and combine them. That's the entire trick behind most of what looks like a complicated visual effect — one texture, a handful of nearby samples, and some arithmetic.&lt;/p&gt;

&lt;p&gt;Between those two — uniforms for "how strong" and a texture for "what image" — you can express almost any real-time filter without ever touching the CPU again once the frame is uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this thing is absurdly fast
&lt;/h2&gt;

&lt;p&gt;A CPU is built to run one instruction stream really well, with a handful of cores doing a handful of different things. A GPU is built for the opposite bet entirely: thousands of much simpler cores, all running the exact same instructions at the exact same time, just on different pieces of data. That architecture is useless for most general-purpose code, which is full of branches and dependencies between steps. It's perfect for a fragment shader, because a fragment shader is designed, on purpose, to never need to know what any other pixel is doing.&lt;/p&gt;

&lt;p&gt;That independence is the entire performance story. A 1920×1080 frame is a little over two million pixels. The fragment shader doesn't run two million times in some patient loop — the GPU dispatches huge batches of pixels across its cores simultaneously, and because no pixel's answer depends on any other pixel's answer, there's nothing stopping it from happening at once. That's what makes a full-frame effect — color grading, a blur, a chromatic aberration pass, a whole filtered look — something a GPU finishes in a fraction of a millisecond, at a resolution and frame rate that would make the same math on a CPU, in a naive per-pixel loop, visibly chug.&lt;/p&gt;

&lt;p&gt;It's also why shader code looks weirdly restrictive if you're used to normal programming — no dynamic memory allocation, loops generally need to know their bounds ahead of time, and branching that diverges wildly between neighboring pixels can quietly tank performance even when it still produces a correct result. Those aren't arbitrary rules. They're the price of admission for running the same tiny program a couple million times a frame, sixty times a second, without the whole thing falling over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this actually shows up: real-time video and image effects
&lt;/h2&gt;

&lt;p&gt;This is the part that made shaders click for me as more than a graphics-programming curiosity. Take any video effect you've seen applied live — scanlines, film grain, an edge-detection sketch look, a thermal false-color pass, chromatic bleed — and the practical way to do it in a browser is exactly the fullscreen-triangle setup above: decode a video frame into a texture, run one fragment shader pass over it, and read the result back out. Every single pixel gets the same treatment independently — sample a color, run some math on it, maybe sample a couple of neighboring pixels for something like an edge or a blur, write the output — and the GPU chews through the whole frame in parallel before the next one is even ready to decode.&lt;/p&gt;

&lt;p&gt;Compare that to doing it the naive way: a JavaScript loop over a canvas's raw pixel array, checking each pixel one at a time on the CPU. It'll produce the same image, technically, but at maybe a few frames per second on anything past a tiny thumbnail, because you've thrown away the one property — total independence between pixels — that makes the parallel version fast in the first place. The math for a lot of these effects isn't even that complicated. A basic edge detector is a handful of neighboring-pixel samples and a threshold. What makes it usable in real time, on a full video, is entirely about running that small piece of math on the right kind of hardware, in the way that hardware was actually built to run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I ended up building a shader-based filter tool
&lt;/h2&gt;

&lt;p&gt;This is more or less the rabbit hole that led to SquishyFile's &lt;a href="https://squishyfile.com/video-filters" rel="noopener noreferrer"&gt;video filter tool&lt;/a&gt;. I wanted a VHS-style effect — scanlines, tape warmth, that slightly degraded camcorder color bleed — plus a few more artistic looks like pencil sketch and thermal vision, with the preview updating live as you tweak it, not "upload, wait, hope it looks right." A CPU-side loop was never going to hit a watchable frame rate. A WebGL2 fragment shader, running one pass per frame with the same fullscreen-triangle setup above, handles it easily — drag a slider, the shader re-runs, the preview updates instantly, because the per-pixel math is cheap and the hardware is built to run all of it at once.&lt;/p&gt;

&lt;p&gt;The side benefit is that nothing ever leaves your browser. The frame becomes a GPU texture locally, the shader runs locally, the render gets encoded back to video locally — no server ever sees your footage. If you want to see the fullscreen-triangle trick above actually doing something, &lt;a href="https://squishyfile.com/video-filters" rel="noopener noreferrer"&gt;the tool&lt;/a&gt; has ten styles — six VHS and camcorder presets plus pencil sketch, cross-hatch, thermal vision and neon — with a live preview so you can judge the effect on your own clip before rendering the final file.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What's Actually Inside a Video File?</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Sat, 12 Sep 2026 14:26:11 +0000</pubDate>
      <link>https://dev.to/maxslashwang/whats-actually-inside-a-video-file-4d92</link>
      <guid>https://dev.to/maxslashwang/whats-actually-inside-a-video-file-4d92</guid>
      <description>&lt;p&gt;If you pause a video, you see a complete, sharp image.&lt;/p&gt;

&lt;p&gt;It’s easy to assume that the video file contains that image somewhere as a complete picture.&lt;/p&gt;

&lt;p&gt;Usually, it doesn't.&lt;/p&gt;

&lt;p&gt;A compressed video is much closer to a collection of instructions for reconstructing frames than it is to a folder full of photographs. Once you understand that, a few annoying video behaviors suddenly make a lot more sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why scrubbing sometimes jumps slightly away from the exact position&lt;/li&gt;
&lt;li&gt;Why an extracted frame can look worse than the frame you saw during playback&lt;/li&gt;
&lt;li&gt;Why cutting one second from a video can require re-encoding the entire thing&lt;/li&gt;
&lt;li&gt;Why some trims are instant and lossless while others take much longer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part is that all of these behaviors come from basically the same mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  A video isn't really a stack of photos
&lt;/h2&gt;

&lt;p&gt;Raw camera footage is, in a sense, a sequence of images.&lt;/p&gt;

&lt;p&gt;But storing every frame as a completely independent image would make compressed video absurdly large.&lt;/p&gt;

&lt;p&gt;Imagine a static shot where almost nothing changes between frames. If the camera records 30 frames per second, there is very little reason to store the entire background 30 times.&lt;/p&gt;

&lt;p&gt;Instead, modern video codecs such as H.264, HEVC, and AV1 take advantage of the similarity between neighboring frames.&lt;/p&gt;

&lt;p&gt;Some frames contain a complete image.&lt;/p&gt;

&lt;p&gt;Others mostly describe how the image has changed.&lt;/p&gt;

&lt;p&gt;And that's where I-frames, P-frames, and B-frames come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three main frame types
&lt;/h2&gt;

&lt;h3&gt;
  
  
  I-frames
&lt;/h3&gt;

&lt;p&gt;An I-frame, or intra-coded frame, is a complete image that can be decoded independently.&lt;/p&gt;

&lt;p&gt;It's the closest thing to a traditional photograph inside a compressed video.&lt;/p&gt;

&lt;p&gt;If a decoder has an I-frame, it has enough information to reconstruct that frame without needing another frame first.&lt;/p&gt;

&lt;p&gt;The downside is that I-frames are relatively expensive to store.&lt;/p&gt;

&lt;p&gt;That's why a video doesn't normally contain a complete I-frame for every single frame.&lt;/p&gt;

&lt;h3&gt;
  
  
  P-frames
&lt;/h3&gt;

&lt;p&gt;P-frames are predicted frames.&lt;/p&gt;

&lt;p&gt;Instead of storing another complete image, a P-frame describes changes relative to previously decoded reference frames.&lt;/p&gt;

&lt;p&gt;For a mostly static scene, that can save a huge amount of data.&lt;/p&gt;

&lt;p&gt;Suppose someone is sitting in front of a stationary background. Between two frames, the wall, desk, and most of the person's body might be almost identical.&lt;/p&gt;

&lt;p&gt;There's no reason to encode all of that information again.&lt;/p&gt;

&lt;p&gt;The encoder can describe the changes instead.&lt;/p&gt;

&lt;p&gt;This is why most frames in a typical compressed video aren't self-contained pictures.&lt;/p&gt;

&lt;h3&gt;
  
  
  B-frames
&lt;/h3&gt;

&lt;p&gt;B-frames take prediction a step further.&lt;/p&gt;

&lt;p&gt;They can use information from frames before and after them.&lt;/p&gt;

&lt;p&gt;The "after" part sounds strange at first. How can a frame refer to something that hasn't happened yet?&lt;/p&gt;

&lt;p&gt;The encoder and decoder don't necessarily process frames in the same order that you watch them.&lt;/p&gt;

&lt;p&gt;The encoder can arrange the necessary reference frames so that the decoder has the information it needs when reconstructing the final playback sequence.&lt;/p&gt;

&lt;p&gt;B-frames can therefore compress very efficiently because they have more information available for prediction.&lt;/p&gt;

&lt;p&gt;You normally never notice any of this while watching a video.&lt;/p&gt;

&lt;p&gt;Your player handles the reconstruction automatically.&lt;/p&gt;

&lt;p&gt;It becomes interesting when you stop doing normal playback.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is where the GOP comes in
&lt;/h2&gt;

&lt;p&gt;These frames aren't just thrown into the file randomly.&lt;/p&gt;

&lt;p&gt;They're organized into groups called &lt;strong&gt;GOPs — Groups of Pictures&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A simplified GOP might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I  B  B  P  B  B  P  B  B  P  I
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact structure varies by codec and encoder, but the important idea is that an I-frame provides a new independent starting point, followed by frames that can depend on other frames.&lt;/p&gt;

&lt;p&gt;GOP length is a trade-off.&lt;/p&gt;

&lt;p&gt;More I-frames generally means more independently stored pictures and therefore more data.&lt;/p&gt;

&lt;p&gt;Longer GOPs can improve compression efficiency because the encoder doesn't have to store as many expensive I-frames.&lt;/p&gt;

&lt;p&gt;But longer GOPs also make certain operations less convenient.&lt;/p&gt;

&lt;p&gt;And that's when things like seeking and editing start getting weird.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does video seeking sometimes feel inaccurate?
&lt;/h2&gt;

&lt;p&gt;Say you drag the timeline to exactly 01:23.450.&lt;/p&gt;

&lt;p&gt;You expect the player to show you precisely that frame.&lt;/p&gt;

&lt;p&gt;But that frame might be a P-frame or B-frame.&lt;/p&gt;

&lt;p&gt;It can't necessarily be decoded by itself.&lt;/p&gt;

&lt;p&gt;The player may need to find an earlier I-frame and then decode all the necessary frames between that keyframe and your requested position.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I → B → B → P → B → P → B → B → [your frame]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The player starts from the I-frame and reconstructs forward until it reaches the requested frame.&lt;/p&gt;

&lt;p&gt;That's computational work.&lt;/p&gt;

&lt;p&gt;Some software prioritizes accuracy and does this reconstruction.&lt;/p&gt;

&lt;p&gt;Other implementations may prioritize speed and seek to a nearby keyframe first, which can make the scrubber appear to land slightly before or after where you expected.&lt;/p&gt;

&lt;p&gt;So when a video player doesn't seem perfectly precise while scrubbing, it isn't necessarily broken.&lt;/p&gt;

&lt;p&gt;It may simply be dealing with the way the video was encoded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can an extracted frame look worse than the video?
&lt;/h2&gt;

&lt;p&gt;This one is particularly unintuitive.&lt;/p&gt;

&lt;p&gt;You pause a video and see a fairly sharp image.&lt;/p&gt;

&lt;p&gt;Then you extract that exact frame as a JPEG or PNG.&lt;/p&gt;

&lt;p&gt;And somehow the exported still looks softer or more blocky.&lt;/p&gt;

&lt;p&gt;What happened?&lt;/p&gt;

&lt;p&gt;The frame you extracted might have been a predicted frame.&lt;/p&gt;

&lt;p&gt;During playback, that frame exists as part of a rapidly changing sequence. Your visual system doesn't spend much time inspecting the individual frame.&lt;/p&gt;

&lt;p&gt;But when you turn it into a standalone image, all of its compression artifacts become much easier to notice.&lt;/p&gt;

&lt;p&gt;Fast motion can make this even more obvious.&lt;/p&gt;

&lt;p&gt;A predicted frame following a large amount of movement has more changes to encode. The encoder can compress those changes aggressively because, during normal playback, the viewer isn't expected to stop and inspect that single frame.&lt;/p&gt;

&lt;p&gt;Once you extract it as a still, that assumption disappears.&lt;/p&gt;

&lt;p&gt;Now you're staring directly at the frame.&lt;/p&gt;

&lt;p&gt;This is one reason why moving the extraction point forward or backward by a few frames can sometimes produce a noticeably better still.&lt;/p&gt;

&lt;p&gt;You're not necessarily finding a "better moment."&lt;/p&gt;

&lt;p&gt;You're simply landing on a frame that happened to survive compression a little better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The surprising part: trimming a video
&lt;/h2&gt;

&lt;p&gt;Here's where this frame structure becomes much more important.&lt;/p&gt;

&lt;p&gt;Suppose you have a ten-minute video and want to remove the first second.&lt;/p&gt;

&lt;p&gt;It sounds like a trivial operation.&lt;/p&gt;

&lt;p&gt;Why should an editor have to do anything complicated? Just remove the first second and keep everything else, right?&lt;/p&gt;

&lt;p&gt;The problem is that your cut might happen in the middle of a GOP.&lt;/p&gt;

&lt;p&gt;Imagine the beginning of the remaining video looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I → B → B → P → B → P → B → ...
        ↑
     cut here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the first frame you're keeping depends on information from a frame you just removed, that frame is no longer valid on its own.&lt;/p&gt;

&lt;p&gt;The editor can't simply throw away the first second and copy the remaining compressed data unchanged.&lt;/p&gt;

&lt;p&gt;It needs to create a new valid sequence.&lt;/p&gt;

&lt;p&gt;That usually means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decode the relevant video data&lt;/li&gt;
&lt;li&gt;Apply the cut&lt;/li&gt;
&lt;li&gt;Encode the resulting frames again&lt;/li&gt;
&lt;li&gt;Create new reference frames and predictions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's why a seemingly tiny edit can trigger a full export.&lt;/p&gt;

&lt;h2&gt;
  
  
  But sometimes trimming is instant
&lt;/h2&gt;

&lt;p&gt;There is an important exception.&lt;/p&gt;

&lt;p&gt;If your cut happens to land exactly on an existing I-frame, the remaining video may already have a valid independent starting point.&lt;/p&gt;

&lt;p&gt;In that case, software can sometimes perform a &lt;strong&gt;stream copy&lt;/strong&gt; or &lt;strong&gt;remux&lt;/strong&gt; instead of re-encoding the video.&lt;/p&gt;

&lt;p&gt;The actual picture data doesn't need to change.&lt;/p&gt;

&lt;p&gt;The software can essentially rearrange the existing encoded streams inside a new container.&lt;/p&gt;

&lt;p&gt;That's extremely fast and doesn't introduce another generation of lossy compression.&lt;/p&gt;

&lt;p&gt;The catch is that you don't get to choose arbitrary frame-accurate cut points when doing this.&lt;/p&gt;

&lt;p&gt;If the cut falls between keyframes, you generally lose that advantage.&lt;/p&gt;

&lt;p&gt;This is why two apparently identical "trim this video" operations can behave completely differently.&lt;/p&gt;

&lt;p&gt;The difference may simply be where the keyframes happen to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  One simple way to think about it
&lt;/h2&gt;

&lt;p&gt;A useful mental model is to imagine a video like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[I] [changes] [changes] [changes] [I] [changes] [changes] [changes] [I]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The I-frames are complete starting points.&lt;/p&gt;

&lt;p&gt;The other frames are instructions that depend on information around them.&lt;/p&gt;

&lt;p&gt;During playback, your video player quietly reconstructs everything.&lt;/p&gt;

&lt;p&gt;During editing, you start interacting with those dependencies.&lt;/p&gt;

&lt;p&gt;That's why:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What's happening&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scrubbing isn't perfectly precise&lt;/td&gt;
&lt;td&gt;The player may need to seek from a nearby keyframe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An extracted frame looks rough&lt;/td&gt;
&lt;td&gt;The frame may be heavily compressed or predicted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A tiny trim takes a long time&lt;/td&gt;
&lt;td&gt;The cut may fall inside a GOP and require re-encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some trims are nearly instant&lt;/td&gt;
&lt;td&gt;The cut may align with existing keyframes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion-heavy frames look worse as stills&lt;/td&gt;
&lt;td&gt;More changes have to be represented in the predicted frame&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you know about GOPs, these behaviors stop looking random.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does a longer GOP mean worse video quality?
&lt;/h2&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

&lt;p&gt;A longer GOP doesn't automatically mean lower visual quality.&lt;/p&gt;

&lt;p&gt;It's primarily a compression and access trade-off.&lt;/p&gt;

&lt;p&gt;Fewer I-frames can reduce the amount of data required, while predicted frames take advantage of similarities between neighboring frames.&lt;/p&gt;

&lt;p&gt;The downside is that random access becomes more complicated, because there are fewer independent starting points.&lt;/p&gt;

&lt;p&gt;This matters for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video editing&lt;/li&gt;
&lt;li&gt;Seeking&lt;/li&gt;
&lt;li&gt;Streaming&lt;/li&gt;
&lt;li&gt;Frame extraction&lt;/li&gt;
&lt;li&gt;Scrubbing&lt;/li&gt;
&lt;li&gt;Fast random access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For ordinary playback, you might never notice the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why codecs are designed this way
&lt;/h2&gt;

&lt;p&gt;It can be tempting to look at all of this and think: wouldn't it be easier if every frame were just a complete image?&lt;/p&gt;

&lt;p&gt;Yes — but the resulting files would be enormous.&lt;/p&gt;

&lt;p&gt;The whole point of inter-frame compression is that consecutive frames are usually highly redundant.&lt;/p&gt;

&lt;p&gt;A stationary background doesn't need to be encoded from scratch 30 times every second.&lt;/p&gt;

&lt;p&gt;A talking head doesn't require the entire scene to be completely re-described for every frame.&lt;/p&gt;

&lt;p&gt;Video compression takes advantage of that redundancy.&lt;/p&gt;

&lt;p&gt;The price is that not every frame is independently useful.&lt;/p&gt;

&lt;p&gt;That's not a flaw.&lt;/p&gt;

&lt;p&gt;It's the fundamental trade-off that makes compressed video practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful takeaway
&lt;/h2&gt;

&lt;p&gt;You don't need to understand GOP structures to watch YouTube or record a video on your phone.&lt;/p&gt;

&lt;p&gt;But once you start extracting frames or editing video, this little bit of codec knowledge becomes surprisingly useful.&lt;/p&gt;

&lt;p&gt;If a frame looks worse than expected, try a few nearby frames.&lt;/p&gt;

&lt;p&gt;If a simple trim takes much longer than expected, the cut probably isn't landing on a convenient keyframe.&lt;/p&gt;

&lt;p&gt;And if one video can be trimmed almost instantly while another takes ages to export, the difference may have nothing to do with the length of the video at all.&lt;/p&gt;

&lt;p&gt;It can come down to something you normally never see:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;where the keyframes are.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's one of the interesting things about compressed video. What looks like a simple sequence of pictures on screen is actually a carefully constructed dependency graph underneath.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building a Browser-Based Video Upscaler with AI-Assisted Coding</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Fri, 11 Sep 2026 15:15:07 +0000</pubDate>
      <link>https://dev.to/maxslashwang/building-a-browser-based-video-upscaler-with-ai-assisted-coding-29ab</link>
      <guid>https://dev.to/maxslashwang/building-a-browser-based-video-upscaler-with-ai-assisted-coding-29ab</guid>
      <description>&lt;p&gt;I finally finished the video upscaler for SquishyFile.&lt;/p&gt;

&lt;p&gt;Honestly, this was probably the most frustrating tool I've built so far.&lt;/p&gt;

&lt;p&gt;I want to start with one thing, though: if you think "vibe coding" means describing an idea in one sentence, letting AI generate everything, and coming back two hours later to a finished product — that's not how it went for me.&lt;/p&gt;

&lt;p&gt;At least not with DeepSeek.&lt;/p&gt;

&lt;p&gt;I used DeepSeek mainly because it's cheap. Claude Code is noticeably better at coding in my experience, but the cost adds up quickly when you're using it for a project like this.&lt;/p&gt;

&lt;p&gt;With DeepSeek, I could usually get something running fairly quickly.&lt;/p&gt;

&lt;p&gt;Then the fun started.&lt;/p&gt;

&lt;p&gt;Fix one bug, another one appeared. Fix that one, and something else broke. Run another test, get a completely different result.&lt;/p&gt;

&lt;p&gt;And video processing makes debugging even more painful because "it runs" doesn't necessarily mean "it works."&lt;/p&gt;

&lt;p&gt;Sometimes the colors were wrong. Sometimes the output video was rotated. Sometimes the aspect ratio was messed up. Sometimes the result looked nothing like the original video.&lt;/p&gt;

&lt;p&gt;A fix that worked perfectly with one video could break another one.&lt;/p&gt;

&lt;p&gt;So the workflow became something like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write code → run it → find something broken → explain it to the AI → change the code → run it again.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over and over.&lt;/p&gt;

&lt;p&gt;For almost a week.&lt;/p&gt;

&lt;p&gt;That's also why I don't really understand the "I built this in two hours with AI" posts I sometimes see. Maybe they're building something simple, or maybe I'm just particularly bad at this. But once you get into browser-based video processing, the first two hours can disappear just trying to figure out why the output is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why run it in the browser?
&lt;/h2&gt;

&lt;p&gt;The main reason is money.&lt;/p&gt;

&lt;p&gt;SquishyFile is free and I'm not charging users for the tool. I also don't want to run expensive GPU infrastructure just to process videos for a free service.&lt;/p&gt;

&lt;p&gt;Video upscaling is especially GPU-heavy.&lt;/p&gt;

&lt;p&gt;If every uploaded video had to go through a cloud GPU on RunPod, AWS, or something similar, the economics would get ugly very quickly. A few dozen concurrent users could generate a GPU bill that makes no sense for a free tool.&lt;/p&gt;

&lt;p&gt;So I decided to take the other approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the model directly on the user's machine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No video processing server on my side.&lt;/p&gt;

&lt;p&gt;That creates a very different set of problems, though.&lt;/p&gt;

&lt;p&gt;Now the tool has to deal with different browsers, different GPUs, different amounts of memory, different video formats, and devices that can range from a powerful desktop to a laptop that starts getting very warm after a few minutes of processing.&lt;/p&gt;

&lt;p&gt;And all of that has to happen inside a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where I almost gave up
&lt;/h2&gt;

&lt;p&gt;There were a few points where I seriously considered abandoning the browser approach and just putting the processing on a server.&lt;/p&gt;

&lt;p&gt;It would have made some things much easier.&lt;/p&gt;

&lt;p&gt;But then I'd have to keep paying for GPU time every month for a free tool.&lt;/p&gt;

&lt;p&gt;That didn't really make sense to me.&lt;/p&gt;

&lt;p&gt;So I kept going back to the code.&lt;/p&gt;

&lt;p&gt;Some bugs were straightforward. Others took a while to understand. Sometimes I found the problem myself and told the AI exactly what was happening. Other times I had to go back and forth with it for a while before we finally found the actual cause.&lt;/p&gt;

&lt;p&gt;It wasn't exactly the effortless AI-assisted development experience you see in demos.&lt;/p&gt;

&lt;p&gt;But eventually, it started working.&lt;/p&gt;

&lt;p&gt;Then it worked with another video.&lt;/p&gt;

&lt;p&gt;Then another one.&lt;/p&gt;

&lt;p&gt;And eventually I had something I was comfortable shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's finally live
&lt;/h2&gt;

&lt;p&gt;The video upscaler now runs directly on the user's device.&lt;/p&gt;

&lt;p&gt;There's no need to upload the video to my server for processing, and the tool is free to use without a usage limit.&lt;/p&gt;

&lt;p&gt;If you have an old low-resolution video, or a video that looks noticeably worse after being compressed by a social platform, you can give it a try:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://squishyfile.com/video-upscaler" rel="noopener noreferrer"&gt;SquishyFile Video Upscaler&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The interesting part for me isn't really that AI helped write the code.&lt;/p&gt;

&lt;p&gt;It's that AI made it possible for me to keep working on a problem that would have been much harder to tackle alone.&lt;/p&gt;

&lt;p&gt;It just didn't make the problem disappear.&lt;/p&gt;

&lt;p&gt;I still had to run the code, look at the broken output, figure out what was actually wrong, and keep pushing until it worked.&lt;/p&gt;

&lt;p&gt;After nearly a week of doing that, I'm just glad the thing finally has a shape.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Browser-based DOCX to PDF converter using Pandoc and Typst (WASM)</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Thu, 12 Feb 2026 16:02:26 +0000</pubDate>
      <link>https://dev.to/maxslashwang/browser-based-docx-to-pdf-converter-using-pandoc-and-typst-wasm-1o7g</link>
      <guid>https://dev.to/maxslashwang/browser-based-docx-to-pdf-converter-using-pandoc-and-typst-wasm-1o7g</guid>
      <description>&lt;p&gt;Hi Fr,&lt;/p&gt;

&lt;p&gt;I built a Word-to-PDF converter that runs 100% in your browser. No files ever touch a server.&lt;/p&gt;

&lt;p&gt;Link: &lt;a href="https://toolkuai.com/word-to-pdf" rel="noopener noreferrer"&gt;https://toolkuai.com/word-to-pdf&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;The challenge was handling the complex layout of .docx files without a heavy backend. I settled on a "double-engine" approach using WebAssembly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pandoc (WASM): Used to parse the DOCX structure and convert it into Typst code. I’m utilizing a WASI shim to run Pandoc's Haskell-compiled WASM in the browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typst (WASM): Instead of using a heavy TeX engine, I used Typst's modern rendering engine to turn the intermediate code into a polished PDF.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Media Extraction: The tool extracts images from the DOCX file via Pandoc’s --extract-media, maps them into a virtual file system (WASI), and then passes them to Typst for final rendering.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom Fonts: To support multi-language characters (especially CJK), I’m side-loading custom fonts (GenYoGothic) into the Typst compiler within the worker.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Svelte 5: The UI is built with Svelte 5, leveraging its new runes ($state, $derived) for a very snappy, reactive file queue management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Web Workers: All the heavy lifting (WASM instantiation and conversion) happens in a dedicated worker to keep the UI at a buttery 60fps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Pandoc + Typst?
&lt;/h2&gt;

&lt;p&gt;Pandoc is the swiss army knife of document conversion, but its direct PDF output usually requires a LaTeX distribution. By routing it through Typst, I can get high-quality PDF output with a much smaller WASM footprint and faster rendering times.&lt;/p&gt;

&lt;p&gt;I’d love to hear your thoughts on the performance or any edge cases you find with complex DOCX layouts!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Spent 3 Years in the App Store. Now I'm Building My First SaaS in Public.</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Mon, 06 Oct 2025 14:55:53 +0000</pubDate>
      <link>https://dev.to/maxslashwang/i-spent-3-years-in-the-app-store-now-im-building-my-first-saas-in-public-2ch9</link>
      <guid>https://dev.to/maxslashwang/i-spent-3-years-in-the-app-store-now-im-building-my-first-saas-in-public-2ch9</guid>
      <description>&lt;p&gt;Hey devs!&lt;/p&gt;

&lt;p&gt;For the last three years, my world has been Xcode, Swift, and the App Store's monetization maze. It's been an incredible learning experience, but I've always been curious about the web and the SaaS world—a completely different beast with its own rules.&lt;/p&gt;

&lt;p&gt;Today, I'm finally diving in and sharing the start of a new public experiment: mktgrowkit.com.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, What Did I Build?&lt;/strong&gt;&lt;br&gt;
At its core, &lt;a href="https://mktgrowkit.com" rel="noopener noreferrer"&gt;https://mktgrowkit.com&lt;/a&gt; is a simple, free suite of marketing calculators (ROAS, CPM, etc.). I built it to scratch my own itch: I needed quick, no-fluff tools to make data-driven decisions for my apps without hitting a paywall or signing up for a complex platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Tech Stack&lt;/strong&gt;&lt;br&gt;
As a mobile dev, jumping into the modern web ecosystem was fun. Here’s the stack I chose for the MVP:&lt;/p&gt;

&lt;p&gt;Framework: Nuxt.js. I chose this specifically for the out-of-the-box Server-Side Rendering (SSR), which is crucial for my SEO-centric strategy.&lt;/p&gt;

&lt;p&gt;Styling: Tailwind CSS. It allowed me to build the UI incredibly fast without writing a lot of custom CSS.&lt;/p&gt;

&lt;p&gt;Deployment: Vercel. The seamless Git integration and global CDN made launching and iterating a breeze.&lt;/p&gt;

&lt;p&gt;Backend: Currently static, but I'm using Supabase for some server-side logic and plan to integrate it more for future features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Real Challenge: From ASO to SEO&lt;/strong&gt;&lt;br&gt;
Building the tool was familiar territory. The real experiment is the challenge I've set for myself: Can I grow this from zero purely through Search Engine Optimization?&lt;/p&gt;

&lt;p&gt;No paid ads, no big launch campaign. Just the long, slow grind of content creation, technical SEO (sitemaps, canonicals, Core Web Vitals), and authority building. It's a whole new world compared to the ASO (App Store Optimization) I'm used to. It feels daunting, but as an engineer, I'm excited by the technical challenge of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Build in Public?&lt;/strong&gt;&lt;br&gt;
I'm a huge believer in learning from others, so I'm committing to tracking this entire journey in public—the good, the bad, and the ugly. I'll be sharing traffic stats, keyword rankings, technical hurdles, and any lessons learned along the way.&lt;/p&gt;

&lt;p&gt;I'd love to hear from other devs who have made a similar jump from mobile to web.&lt;/p&gt;

&lt;p&gt;What was your biggest "aha!" moment or surprise?&lt;/p&gt;

&lt;p&gt;Any advice on surviving the SEO grind?&lt;/p&gt;

&lt;p&gt;If you want to follow the data and see how this experiment plays out, I'm posting all my updates on X (Twitter). I've created this pinned tweet to track the journey: [Link đến Pinned Tweet của bạn]&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>saas</category>
      <category>seo</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I just launched TL;Dev, a curated collection of notes for developers and indie builders.</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Wed, 27 Aug 2025 14:38:14 +0000</pubDate>
      <link>https://dev.to/maxslashwang/i-just-launched-tldev-a-curated-collection-of-notes-for-developers-and-indie-builders-4ghc</link>
      <guid>https://dev.to/maxslashwang/i-just-launched-tldev-a-curated-collection-of-notes-for-developers-and-indie-builders-4ghc</guid>
      <description>&lt;p&gt;Hey everyone,&lt;/p&gt;

&lt;p&gt;Like many of you, I spend a lot of time on Reddit, X, and YouTube looking for interesting case studies and strategies. The problem is, there's too much content and not enough time. I'd save dozens of links intending to read them later, but that "later" never came.&lt;/p&gt;

&lt;p&gt;So, I built a solution for myself called TL;Dev.&lt;/p&gt;

&lt;p&gt;It's a simple, no-BS website where I personally curate and summarize the most valuable content into short, easy-to-read notes. The goal is to help developers and indie builders grasp the core ideas in minutes, not hours.&lt;/p&gt;

&lt;p&gt;My site is covering everything from successful launches to costly failures.&lt;/p&gt;

&lt;p&gt;It's 100% free, no paywall, no annoying pop-ups. Just content.&lt;/p&gt;

&lt;p&gt;For those interested, it’s built with Astro and deployed on Cloudflare. It's ridiculously fast.&lt;/p&gt;

&lt;p&gt;I'm posting here because I think it could be genuinely useful for this community, and I'd love to get your feedback on the site, the content, or any ideas you might have.&lt;/p&gt;

&lt;p&gt;You can check it out here: &lt;a href="https://tldev.co" rel="noopener noreferrer"&gt;https://tldev.co&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!!&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>startup</category>
      <category>developer</category>
    </item>
    <item>
      <title>How to Find Profitable App Ideas (Even If You Think Everything Already Exists)</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Fri, 28 Feb 2025 08:29:31 +0000</pubDate>
      <link>https://dev.to/maxslashwang/how-to-find-profitable-app-ideas-even-if-you-think-everything-already-exists-1c7</link>
      <guid>https://dev.to/maxslashwang/how-to-find-profitable-app-ideas-even-if-you-think-everything-already-exists-1c7</guid>
      <description>&lt;p&gt;Finding a great app idea can feel impossible—especially when it seems like every idea has already been done. But the truth is, you don’t need a 100% original idea to succeed. You just need to build something that solves a real problem in a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Don’t Need a Completely Original App Idea
&lt;/h2&gt;

&lt;p&gt;Many aspiring indie developers believe that success comes from launching a completely new, never-before-seen product. But in reality, most successful apps weren’t the first of their kind—they were just better, simpler, or more focused than existing solutions.&lt;/p&gt;

&lt;p&gt;Think about physical products: there are dozens of energy drink brands, water bottles, and vanilla ice creams, yet all of them make millions. Apps work the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Find a Profitable App Idea That Actually Works
&lt;/h2&gt;

&lt;p&gt;Instead of chasing originality, focus on solving real problems for a well-defined audience. Here’s a proven framework to discover high-potential app ideas:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with a Niche You Understand Deeply
&lt;/h2&gt;

&lt;p&gt;The best ideas come from inside knowledge of a specific market. If you personally experience a problem, chances are thousands of others do too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the biggest challenges in industries you’re familiar with?&lt;/li&gt;
&lt;li&gt;What daily annoyances do you or your colleagues face?&lt;/li&gt;
&lt;li&gt;Have you ever said, “I wish there was an app for this”?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start by identifying problems in an area where you already have expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Study Communities to Discover Hidden Needs
&lt;/h2&gt;

&lt;p&gt;Instead of forcing an idea, immerse yourself where your target audience hangs out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reddit, Discord, Slack, Twitter (X), and Facebook groups&lt;/li&gt;
&lt;li&gt;Product review sites like G2, Capterra, and App Store reviews&lt;/li&gt;
&lt;li&gt;Quora and niche forums where people ask for advice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look for repeated frustrations or complaints about existing solutions. When you see the same pain point mentioned repeatedly, you’ve found a validated demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Improve on an Existing App Instead of Reinventing the Wheel
&lt;/h2&gt;

&lt;p&gt;Rather than struggling to come up with a unique idea, find a popular app and make a better version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make it simpler, cheaper, or faster&lt;/li&gt;
&lt;li&gt;Solve a missing pain point&lt;/li&gt;
&lt;li&gt;Focus on a specific audience (e.g., a budgeting app tailored for freelancers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many successful startups started this way—Slack improved on workplace chat, Notion refined note-taking, and Duolingo made language learning more engaging.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Test Demand Before Writing a Single Line of Code
&lt;/h2&gt;

&lt;p&gt;Before building, validate your idea:&lt;/p&gt;

&lt;p&gt;✅ Post about it on social media and gauge interest&lt;br&gt;
✅ Run a small paid ad campaign to see if people click&lt;br&gt;
✅ Create a simple landing page and collect email signups&lt;br&gt;
✅ Ask potential users directly in communities&lt;/p&gt;

&lt;p&gt;If no one is excited, pivot or refine the idea before coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Must-Have Ingredients for a Successful App Idea
&lt;/h2&gt;

&lt;p&gt;To maximize your chances of success, your app idea should check these boxes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to Build – Aim to launch an MVP within 30 days.&lt;/li&gt;
&lt;li&gt;Solves a Clear Pain Point – The problem should be urgent and painful.&lt;/li&gt;
&lt;li&gt;Has an Audience You Can Reach – If you don’t know where your users hang out, it’ll be hard to market.&lt;/li&gt;
&lt;li&gt;Excites You Enough to Stay Committed – If you’re not passionate about it, you’ll quit too early.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Proven Formula for Finding Profitable App Ideas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Identify a niche you know well&lt;/li&gt;
&lt;li&gt;Find a real problem people complain about&lt;/li&gt;
&lt;li&gt;Improve an existing solution or fill a gap&lt;/li&gt;
&lt;li&gt;Test interest before coding&lt;/li&gt;
&lt;li&gt;Launch, get feedback, and iterate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 You don’t need the next "big idea"—just a product that solves a real problem for real users.&lt;/p&gt;

&lt;p&gt;Ready to find your next app idea? &lt;/p&gt;

&lt;p&gt;Start by diving into online communities and observing what people truly need!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>saas</category>
      <category>startup</category>
      <category>ios</category>
    </item>
    <item>
      <title>Why Developers Struggle to Make Money with Apps (And How to Fix It)</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Thu, 27 Feb 2025 07:30:00 +0000</pubDate>
      <link>https://dev.to/maxslashwang/why-developers-struggle-to-make-money-with-apps-and-how-to-fix-it-13k4</link>
      <guid>https://dev.to/maxslashwang/why-developers-struggle-to-make-money-with-apps-and-how-to-fix-it-13k4</guid>
      <description>&lt;p&gt;Developers love building things. We get excited about new ideas, start coding right away, and create something we think is amazing. But then… nothing happens.&lt;/p&gt;

&lt;p&gt;📉 &lt;strong&gt;No downloads. No revenue. Just silence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why? Because we often forget to ask the most important question—Does anyone actually need this app?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Market Research Matters More Than Your Code
&lt;/h2&gt;

&lt;p&gt;❌ Most developers build first and validate later. That’s a mistake.&lt;/p&gt;

&lt;p&gt;Here’s how to avoid wasting months on an app no one wants:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Analyze the market.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look at similar apps. If an app in your niche is making money, that’s a strong sign of demand.&lt;br&gt;
If there are zero competitors, be careful—maybe there’s no market.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Read user reviews on the App Store.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1-2 star reviews = complaints &amp;amp; frustrations.&lt;br&gt;
4-star reviews = users love the app but want improvements.&lt;br&gt;
Find gaps and build a better alternative.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Check ASO keywords.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use Sensor Tower or AppTweak to see if people are searching for solutions like yours.&lt;br&gt;
If search volume is low, validate on Reddit, Google Trends, or Facebook groups before building.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Mistake I Made (And What I Learned)
&lt;/h2&gt;

&lt;p&gt;I once spent a whole month building an app that I thought was genius.&lt;br&gt;
Launched it… and nothing happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🚀 Not because the app was bad—but because nobody needed it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;💡 Don’t make the same mistake. Before writing a single line of code, validate your idea.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/y0hNXf4wwQU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>ios</category>
      <category>mobile</category>
      <category>swift</category>
      <category>marketing</category>
    </item>
    <item>
      <title>How I Built an iOS App That Made $1,400 in 90 Days (Lessons Learned)</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Wed, 26 Feb 2025 16:18:58 +0000</pubDate>
      <link>https://dev.to/maxslashwang/how-i-built-an-ios-app-that-made-1400-in-90-days-lessons-learned-2nbn</link>
      <guid>https://dev.to/maxslashwang/how-i-built-an-ios-app-that-made-1400-in-90-days-lessons-learned-2nbn</guid>
      <description>&lt;p&gt;I started with zero downloads, no marketing, and no clue if my iOS app would make money.&lt;/p&gt;

&lt;p&gt;📊 Fast forward 90 days:&lt;br&gt;
💰 $1,400 revenue&lt;br&gt;
📆 $347 in the last 30 days&lt;br&gt;
📈 $2,850 total since launch&lt;/p&gt;

&lt;p&gt;Not life-changing, but proof that even a small app can generate real income.&lt;/p&gt;

&lt;p&gt;The process wasn’t smooth. I made mistakes, wasted time, and almost gave up. But after testing and refining, I found what actually works when it comes to making money with iOS apps.&lt;/p&gt;

&lt;p&gt;🚀 6 Key Lessons I Learned&lt;br&gt;
💡 1. Build What You Love, But Validate Demand&lt;br&gt;
Most devs start with an idea they love, but that’s not enough. There has to be demand.&lt;br&gt;
✅ Check Google Trends, Reddit, Twitter.&lt;br&gt;
✅ Look at App Store reviews to see common complaints.&lt;br&gt;
✅ If nobody is looking for it, don’t build it.&lt;/p&gt;

&lt;p&gt;💡 2. Marketing is More Important Than Coding&lt;br&gt;
Most devs think "build it and they will come." Nope. Without marketing, your app is invisible.&lt;br&gt;
✅ ASO (App Store Optimization) matters.&lt;br&gt;
✅ Running Apple Search Ads, even with a small budget, can make a huge difference.&lt;br&gt;
✅ Leverage social media (Twitter, Reddit) to drive early traction.&lt;/p&gt;

&lt;p&gt;💡 3. Monetization Matters From Day One&lt;br&gt;
✅ Don’t just launch a free app and hope for ad revenue.&lt;br&gt;
✅ Subscriptions are better for long-term revenue.&lt;br&gt;
✅ In-App Purchases (IAPs) work well if you offer real value.&lt;/p&gt;

&lt;p&gt;🎯 I go into full detail in this breakdown video:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/y0hNXf4wwQU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;💡 Thinking about launching an iOS app? Watch this before you start!&lt;/p&gt;

</description>
      <category>indiedev</category>
      <category>ios</category>
      <category>makemoney</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Unlocking the Power of App Intents: Prepare Your App for the AI Revolution</title>
      <dc:creator>Max/Wang</dc:creator>
      <pubDate>Thu, 24 Oct 2024 08:53:17 +0000</pubDate>
      <link>https://dev.to/maxslashwang/unlocking-the-power-of-app-intents-prepare-your-app-for-the-ai-revolution-3ipl</link>
      <guid>https://dev.to/maxslashwang/unlocking-the-power-of-app-intents-prepare-your-app-for-the-ai-revolution-3ipl</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%2Fahz2rkih3if8yyku5d6z.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%2Fahz2rkih3if8yyku5d6z.png" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whispers in the tech world are getting louder: AI is poised to become a central player in the iOS ecosystem. As developers, we're always looking for ways to stay ahead of the curve, and App Intents might just be the key to unlocking the potential of this AI revolution. While often overlooked, App Intents offer a powerful way to expose our app's functionality to the broader operating system, including Siri, Spotlight, and Widgets.&lt;/p&gt;

&lt;p&gt;Imagine a future where users can seamlessly interact with your app through natural language commands, effortlessly completing tasks without even opening the app. This is the promise of App Intents, and with the rise of AI, their importance is only set to grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why App Intents Matter in an AI-Driven World
&lt;/h2&gt;

&lt;p&gt;App Intents act as a bridge between your app and the system's intelligence, allowing Siri to understand and execute actions on behalf of the user. By defining clear intents and parameters, you empower Siri to perform tasks such as adding items to a shopping list, starting a workout, or sending a message, all through voice commands or automated shortcuts.&lt;/p&gt;

&lt;p&gt;With Apple's increasing focus on AI, it's likely that Siri will become even more integrated into our daily lives. This means that apps that leverage App Intents will have a significant advantage, offering users a frictionless and intuitive experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Example: Building a Feature Wishlist with App Intents
&lt;/h2&gt;

&lt;p&gt;Let's dive into a real-world example to see how App Intents work in practice. We'll create a simple app called "Features," which allows users to maintain a wishlist of features for Apple platforms. Using SwiftData and SwiftUI, we'll implement an App Intent that enables users to add new features to their wishlist using Siri.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Defining the Data Models
&lt;/h3&gt;

&lt;p&gt;First, we define our data models: &lt;code&gt;Feature&lt;/code&gt; and &lt;code&gt;Platform&lt;/code&gt;. A &lt;code&gt;Feature&lt;/code&gt; has a title, description, and a corresponding &lt;code&gt;Platform&lt;/code&gt;. The &lt;code&gt;Platform&lt;/code&gt; is an enum representing various Apple platforms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftData&lt;/span&gt;

&lt;span class="c1"&gt;// MARK: - Feature&lt;/span&gt;

&lt;span class="kd"&gt;@Model&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Feature&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;featureDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iphone&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;featureDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Platform&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;featureDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;featureDescription&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To enable Siri to understand our &lt;code&gt;Platform&lt;/code&gt; model, we make it conform to the &lt;code&gt;AppEnum&lt;/code&gt; protocol. This involves providing display representations for both the type and its cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;AppIntents&lt;/span&gt;

&lt;span class="c1"&gt;// MARK: - Platform&lt;/span&gt;

&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CaseIterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AppEnum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;allPlatforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"All Platforms"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;iphone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"iPhone"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ipad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"iPad"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;applewatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Apple Watch"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;visionPro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Vision Pro"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Mac"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;appletv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Apple TV"&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (icon code omitted for brevity)&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;typeDisplayRepresentation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TypeDisplayRepresentation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stringLiteral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Platform"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;caseDisplayRepresentations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DisplayRepresentation&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="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;allPlatforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"All Platforms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;iphone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"iPhone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ipad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"iPad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;applewatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Apple Watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;visionPro&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Vision Pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Mac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;appletv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Apple TV"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Managing Features with FeatureManager
&lt;/h3&gt;

&lt;p&gt;Next, we create a &lt;code&gt;FeatureManager&lt;/code&gt; class to handle the creation, retrieval, updating, and deletion of &lt;code&gt;Feature&lt;/code&gt; objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftData&lt;/span&gt;

&lt;span class="c1"&gt;// MARK: - Feature Manager&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;FeatureManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... (Properties and Init code omitted for brevity)&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;addFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;withAnimation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;feature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;featureDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;platform&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;modelContext&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (deleteFeature code omitted for brevity)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Creating the App Intent
&lt;/h3&gt;

&lt;p&gt;Now, we define the &lt;code&gt;CreateFeature&lt;/code&gt; App Intent, which allows users to add new features to their wishlist through Siri. We specify the intent's title, parameters, a summary of its action, and the code to perform when invoked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;AppIntents&lt;/span&gt;

&lt;span class="c1"&gt;// MARK: - Create Feature App Intent&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CreateFeature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppIntent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... (Properties code omitted for brevity)&lt;/span&gt;

    &lt;span class="kd"&gt;@Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;requestValueDialog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Where would you like to see this feature?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Platform&lt;/span&gt;

    &lt;span class="kd"&gt;@Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;requestValueDialog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"What's the title of the feature?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="kd"&gt;@Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;requestValueDialog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"What's the description of the feature?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (Summary code omitted for brevity)&lt;/span&gt;

    &lt;span class="kd"&gt;@MainActor&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;IntentResult&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="kt"&gt;ReturnsValue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;FeatureManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;platform&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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"New Feature for &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="se"&gt;)\n\n\(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n\(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Exposing the App Intent to Siri
&lt;/h3&gt;

&lt;p&gt;Finally, we expose our &lt;code&gt;CreateFeature&lt;/code&gt; App Intent to Siri through an &lt;code&gt;AppShortcutsProvider&lt;/code&gt;. This involves defining an App Shortcut that links to our intent and providing phrases that users can say to trigger it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Foundation&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;AppIntents&lt;/span&gt;

&lt;span class="c1"&gt;// MARK: - App Shortcuts&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;AppShortcuts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppShortcutsProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@AppShortcutsBuilder&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;appShortcuts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppShortcut&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;AppShortcut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CreateFeature&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nv"&gt;phrases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Make a New &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationName&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;shortTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;LocalizedStringResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stringLiteral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"New Feature"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;systemImageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"apple.logo"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;App Intents are crucial for integrating your app with system functionalities like Siri, Spotlight, and Widgets.&lt;/li&gt;
&lt;li&gt;As AI becomes more prevalent in iOS, App Intents will play an even greater role in enhancing user experience.&lt;/li&gt;
&lt;li&gt;By defining clear intents and parameters, you enable Siri to understand and execute actions within your app.&lt;/li&gt;
&lt;li&gt;Investing time in learning and implementing App Intents can significantly improve your app's discoverability and user engagement.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>ios</category>
      <category>developer</category>
    </item>
  </channel>
</rss>
