diff --git a/src/content/posts/50-percent-less-bandwidth-per-deploy.md b/src/content/posts/50-percent-less-bandwidth-per-deploy.md
new file mode 100644
index 0000000..87f13b2
--- /dev/null
+++ b/src/content/posts/50-percent-less-bandwidth-per-deploy.md
@@ -0,0 +1,219 @@
+---
+title: "50% Less Bandwidth Per Deploy"
+date: 2026-05-28
+type: post
+tags: [bundler, cdn, bandwidth, cloudpack, delivery]
+summary: "Every deploy, your users re-download the whole bundle because 1% of modules changed. Delta manifests cut that in half. The browser says what it has. The server returns only what's missing."
+---
+
+You changed one line in a utility function. The bundler produced new chunks. The CDN invalidated the old URLs. Every user who visits your app downloads the entire bundle from scratch.
+
+One line. 100% re-download.
+
+This is not a bug in your bundler. It's how content-hashing works. Change one module, the chunk hash changes, the URL changes, and the browser has no way to know that 99% of the code inside that chunk is identical to what it already had. So it fetches everything. Every byte. Every user.
+
+At 50,000 modules with 10,000 daily active users, that's 95 GB of CDN bandwidth per deploy. Most of it wasted on bytes the browser already has cached under a different filename.
+
+## Invert the model
+
+The fix is simple: stop pretending the CDN is the source of truth about what the client needs. The client already knows what it has. Let it say so.
+
+Cloudpack's [Adaptive Bundle Service](https://github.com/kenotron-ms/cloudpack) (ABS) inverts the delivery model. Instead of the browser requesting URLs and getting whatever the CDN serves, the browser sends a manifest request: "here's my entry point, here are the content hashes of every module I already have." The server computes the difference and responds with only the chunk URLs the browser still needs.
+
+
+
+
+
+The wire protocol is two structs.
+
+## The request
+
+```rust
+pub struct ManifestRequest {
+ /// The application entry-point (e.g. `"src/index.ts"`).
+ pub entry_point: String,
+
+ /// SHA-256 hashes of every module the client already holds in cache.
+ pub cached_hashes: Vec,
+
+ /// Opaque build identifier issued by the last manifest response.
+ /// Absent on the very first request or after a hard-reload.
+ pub build_id: Option,
+}
+```
+
+Three fields. The browser sends its entry point and the content hashes of every module it has cached. The `build_id` is a staleness check: if the client's build doesn't match the server's current build, ABS ignores the cache claims and returns the full chunk set. No stale data, no partial mismatches, no guessing.
+
+## The response
+
+```rust
+pub struct ManifestResponse {
+ /// Opaque identifier for the current server build.
+ pub build_id: String,
+
+ /// URLs of chunks the client must fetch now.
+ pub fetch_urls: Vec,
+
+ /// URLs of chunks worth prefetching for future navigations.
+ pub prefetch_urls: Vec,
+
+ /// Cache TTL for this manifest in seconds.
+ pub ttl: u64,
+}
+```
+
+`fetch_urls` is what the client needs right now. `prefetch_urls` is what it will probably need soon, derived from co-request patterns observed in real production traffic. The server never tells the client to re-fetch chunks it already has. That's the whole point.
+
+## The delta computation
+
+The algorithm is simple. Look up which chunks the entry point needs. For each chunk, check whether every module inside it appears in the client's `cached_hashes` set. All modules present? The client already has the full chunk, skip it. Any module missing? Add the chunk's CDN URL to `fetch_urls`.
+
+```rust
+pub fn compute_delta(
+ manifest: &ChunkManifest,
+ request: &ManifestRequest,
+ cdn_base_url: &str,
+) -> ManifestResponse {
+ let chunk_ids = match manifest.entry_chunks.get(&request.entry_point) {
+ Some(ids) => ids,
+ None => return empty_response(&manifest.build_id),
+ };
+
+ // Trust cache only if build_id matches.
+ let trust_cache = matches!(
+ &request.build_id, Some(bid) if bid == &manifest.build_id
+ );
+
+ let cached_set: HashSet<&ContentHash> = if trust_cache {
+ request.cached_hashes.iter().collect()
+ } else {
+ HashSet::new()
+ };
+
+ let mut fetch_urls: Vec = Vec::new();
+ for chunk_id in chunk_ids {
+ if let Some(chunk) = chunks_by_id.get(chunk_id.as_str()) {
+ let fully_cached = !chunk.modules.is_empty()
+ && chunk.modules.iter().all(|m| cached_set.contains(m));
+ if !fully_cached {
+ fetch_urls.push(chunk_url(cdn_base_url, &chunk.hash));
+ }
+ }
+ }
+
+ ManifestResponse { build_id: manifest.build_id.clone(), fetch_urls, .. }
+}
+```
+
+The `trust_cache` gate is important. If the client sends a `build_id` that doesn't match the server's current build, the `cached_set` is empty and every chunk goes into `fetch_urls`. First visit and hard-reload both degrade to a full download. No ambiguity. No stale-cache bugs.
+
+
+
+
+
+## Never on the critical path
+
+ABS is a manifest server. It decides what to load. The CDN serves the actual code. This separation is load-bearing.
+
+The service worker that mediates the protocol has a hard 100ms timeout. ABS doesn't respond in time? The service worker falls back to `manifest.json` on the CDN and the app loads exactly as it would without ABS. Full chunk set, no optimization. Works every time.
+
+ABS failure degrades transparently. No blank screens. No loading spinners. No error toasts. The user gets a few extra megabytes they didn't strictly need, and the app loads fine. The optimization is invisible when it works and invisible when it doesn't.
+
+## The numbers
+
+Benchmarks run against synthetic codebases generated from the statistical fingerprint of real production code. [Reproducible](https://github.com/kenotron-ms/cloudpack). The table shows what a returning user downloads per deploy:
+
+| Code churn | Traditional CDN | ABS delta | Savings |
+|-----------:|----------------:|----------:|--------:|
+| 0.5% | 100% | 50% | **50%** |
+| 1% | 100% | 50% | **50%** |
+| 5% | 100% | 50% | **50%** |
+| 10% | 100% | 50% | **50%** |
+| 25% | 100% | 50% | **50%** |
+
+Up to 25% code churn, ABS cuts download size in half. The savings hold because the delta operates at chunk granularity: a chunk whose modules all match the client's cache is skipped entirely, regardless of how many other chunks changed.
+
+At production scale (50,000 modules, 10,000 daily active users, 1% weekly churn): **47 GB saved per week.** That is not a rounding error. At $0.09/GB, it's $4.29/week in CDN costs alone, and the bandwidth savings compound with user count.
+
+## The dynamic linking analogy
+
+This is not a new idea. It's `ld.so` for the browser.
+
+In native code, the dynamic linker loads shared libraries at runtime. It resolves symbols, checks what's already mapped, and fetches only what's missing. GOT/PLT lets the program reference code that hasn't been loaded yet. The loader fills in the addresses when they're needed.
+
+ABS does the same thing. The service worker is `ld.so`. Content-hashed CDN chunks are shared libraries. The delta manifest is the symbol lookup. The browser fetches only what it doesn't already have.
+
+The architect brief for Cloudpack puts it this way: "the module graph is the software, the bundle is a query." The delta manifest is just another materialization of that graph. Production bundles, dev server output, CI artifacts, per-client delta manifests: all queries against the same persistent module graph. Different clients, different cache states, different answers. One truth.
+
+47 GB per week. Half the bandwidth. Same app.
\ No newline at end of file