[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"sanity-D0zZW2iqgWubzyAyFtuCUjej2qNqCnUE4Fc1RTOsDS0":3,"sanity-YISgYYfNbbR8R_EkgVqFkL0CtOuqj2WzD7e5DmV6AhY":374},{"data":4,"sourceMap":-1},{"latestPodcast":5,"latestReleases":14,"post":39,"recent":349},[6],{"_id":7,"publishedAt":8,"slug":9,"sponsored":12,"title":13},"d53f9358-3bb2-4f69-aebe-d31d19522cd4","2026-07-10T07:40:00.000Z",{"_type":10,"current":11},"slug","building-more-than-just-an-agent-harness",null,"Building more than just an agent harness",[15,21,27,33],{"_id":16,"publishedAt":17,"slug":18,"title":20},"eb5b66eb-9410-4329-83bb-22bbff39402a","2026-04-28T13:00:00.000Z",{"_type":10,"current":19},"turn-scattered-knowledge-into-trusted-intelligence","Turning scattered knowledge into trusted intelligence: Stack Internal 2026.3",{"_id":22,"publishedAt":23,"slug":24,"title":26},"369c2401-b62e-4a37-8ff8-bf603023ecad","2026-03-02T15:03:00.988Z",{"_type":10,"current":25},"what-s-new-at-stack-overflow-march-2026","What’s new at Stack Overflow: March 2026",{"_id":28,"publishedAt":29,"slug":30,"title":32},"5e9053a4-07ea-447c-91ea-29e0b6228537","2026-02-02T15:00:00.000Z",{"_type":10,"current":31},"what-s-new-at-stack-overflow-february-2026","What’s new at Stack Overflow: February 2026",{"_id":34,"publishedAt":35,"slug":36,"title":38},"a1b538eb-a8a6-46d0-80a1-ac70ec9bb935","2026-01-05T10:00:00.000-05:00",{"_type":10,"current":37},"what-s-new-at-stack-overflow-january-2026","What’s new at Stack Overflow: January 2026",{"_createdAt":40,"_id":41,"_rev":42,"_system":43,"_type":46,"_updatedAt":47,"author":48,"body":66,"comments":284,"dateUrl":285,"product":12,"publishedAt":286,"slug":287,"sponsored":12,"tags":289,"title":348,"visible":284},"2026-05-12T16:20:21Z","3c902fd7-2d6a-44ce-8b89-e7f08a207edc","2CozUHl7bja0z7No5LEtxI",{"base":44},{"id":41,"rev":45},"Bqj3l3jZyXV24sfjpveRrZ","blogPost","2026-05-18T16:14:46Z",[49],{"_createdAt":50,"_id":51,"_rev":52,"_type":53,"_updatedAt":54,"account_id":55,"avatar":56,"bio":61,"employee":62,"name":63,"slug":64},"2026-05-12T16:21:05Z","e627398e-ce2a-419b-aebd-0111be1e2411","nvZcks16ldBXxsacBMymz6","blogAuthor","2026-05-12T16:22:05Z",32559801,{"_type":57,"asset":58},"image",{"_ref":59,"_type":60},"image-355a488eb8e0fbe6134d04af99285dc0cac80155-1024x1024-png","reference","Najmul Alam Miraj is a Bangladesh-based tech product developer focused on building meaningful digital solutions with modern UI\u002FUX and offline-first architecture. He has worked as a front-end developer, front-end development instructor, and senior graphics designer. He is the creator of the namireng app and the Miraj Note Chrome extension, and actively shares technical insights on cloud integrations and extension development.","none","Najmul Alam Miraj ",{"_type":10,"current":65},"najmul-alam-miraj",[67,78,86,94,103,111,128,136,144,152,160,176,184,192,200,224,240,244,260,268,276],{"_key":68,"_type":69,"children":70,"markDefs":76,"style":77},"d4a7f77af5f4","block",[71],{"_key":72,"_type":73,"marks":74,"text":75},"5ea8db747e34","span",[],"Moving to Chrome’s Manifest V3 (MV3) isn't just a simple syntax update. It completely breaks how we used to build browser extensions.",[],"normal",{"_key":79,"_type":69,"children":80,"markDefs":85,"style":77},"54c57e942b1a",[81],{"_key":82,"_type":73,"marks":83,"text":84},"81ff0184bf30",[],"For simple tools, the fix is easy. But when you are building an offline-first app that constantly talks to Google Drive, MV3 forces you to scrap everything you know about state management, network drops, and dependencies.",[],{"_key":87,"_type":69,"children":88,"markDefs":93,"style":77},"80db0760b08e",[89],{"_key":90,"_type":73,"marks":91,"text":92},"af5116d3498a",[],"Here is a look at the trade-offs I had to make to get a cloud sync engine running smoothly inside the strict limits of an MV3 Service Worker.",[],{"_key":95,"_type":69,"children":96,"markDefs":101,"style":102},"4f4cb9a0e6e6",[97],{"_key":98,"_type":73,"marks":99,"text":100},"7b6058802797",[],"1. The Death of In-Memory State",[],"h2",{"_key":104,"_type":69,"children":105,"markDefs":110,"style":77},"66b728a44659",[106],{"_key":107,"_type":73,"marks":108,"text":109},"e6ecfe8cc7d9",[],"Back in the MV2 days, keeping a sync queue inside a background script variable was standard practice. You can't do that anymore. MV3 will kill your Service Worker whenever it wants to free up memory. If a user clips a webpage and the worker dies before the upload finishes, that data is gone forever.",[],{"_key":112,"_type":69,"children":113,"markDefs":127,"style":77},"1f2ea967f3a4",[114,118,123],{"_key":115,"_type":73,"marks":116,"text":117},"762a2a339213",[],"You have to move to a strict disk-first model. ",{"_key":119,"_type":73,"marks":120,"text":122},"cbbf583889f9",[121],"code","chrome.storage.local",{"_key":124,"_type":73,"marks":125,"text":126},"d1c9ffba889a",[]," becomes your only source of truth.",[],{"_key":129,"_type":69,"children":130,"markDefs":135,"style":77},"b9c96f7365ce",[131],{"_key":132,"_type":73,"marks":133,"text":134},"6364f148e4fe",[],"I had to wire the app so that any user action—clipping text, typing a note, or using voice input—saves directly to local storage right away. Syncing to the cloud happens strictly in the background as an afterthought. Because the Service Worker holds zero state, the browser can wake it up, it checks local storage for pending syncs, fires off the upload, and dies. No data gets lost in the process.",[],{"_key":137,"_type":69,"children":138,"markDefs":143,"style":102},"aee3cb07aca3",[139],{"_key":140,"_type":73,"marks":141,"text":142},"80ecec48ee27",[],"2. Handling Offline Drops",[],{"_key":145,"_type":69,"children":146,"markDefs":151,"style":77},"68d27d527e84",[147],{"_key":148,"_type":73,"marks":149,"text":150},"a90bb1580e03",[],"You can never trust the network, especially for a browser extension running on flaky Wi-Fi or a laptop going to sleep.",[],{"_key":153,"_type":69,"children":154,"markDefs":159,"style":77},"f2fe67b66f2f",[155],{"_key":156,"_type":73,"marks":157,"text":158},"afdf1946e878",[],"If the user drops offline, the extension immediately halts syncing and queues the state locally. The tricky part is coming back online. If you just blindly push local changes to the cloud, you risk wiping out updates the user made from another laptop.",[],{"_key":161,"_type":69,"children":162,"markDefs":175,"style":77},"8be9cbe93400",[163,167,171],{"_key":164,"_type":73,"marks":165,"text":166},"3a7584e1c3d3",[],"I ended up writing a quick script to merge things manually. When the connection comes back, the code pulls the existing JSON from the ",{"_key":168,"_type":73,"marks":169,"text":170},"bc0ee5ad97cd",[121],"appDataFolder",{"_key":172,"_type":73,"marks":173,"text":174},"9cb1c8c65edf",[]," in Drive. Then I just toss the local notes and the remote notes together into a Map. Since my note IDs are basically just timestamps, sorting them is super easy and handles duplicates naturally. Once everything is merged into a single array, I upload it back to Google. It's a bit hacky, but it completely stops accidental overwrites—even if Chrome shuts down the background script right in the middle of syncing.",[],{"_key":177,"_type":69,"children":178,"markDefs":183,"style":102},"0a91fd3c0c6f",[179],{"_key":180,"_type":73,"marks":181,"text":182},"f812c7e731b0",[],"3. Dropping the Google SDK for Native Fetch",[],{"_key":185,"_type":69,"children":186,"markDefs":191,"style":77},"dd431b1e2f9c",[187],{"_key":188,"_type":73,"marks":189,"text":190},"b257f47c1503",[],"The biggest tradeoff I made was stripping out the official Google API client entirely.",[],{"_key":193,"_type":69,"children":194,"markDefs":199,"style":77},"a5a74b660b36",[195],{"_key":196,"_type":73,"marks":197,"text":198},"ce6881ebc544",[],"Sure, SDKs make life easier, but they are huge. Shoving a massive dependency tree into an MV3 Service Worker slows down execution time and bloats the bundle size. It completely defeats the performance goals of the new manifest.",[],{"_key":201,"_type":69,"children":202,"markDefs":223,"style":77},"8b251d307906",[203,207,211,215,219],{"_key":204,"_type":73,"marks":205,"text":206},"c5e486782b48",[],"So, I stuck strictly to the native ",{"_key":208,"_type":73,"marks":209,"text":210},"ac2f33cb3dec",[121],"fetch",{"_key":212,"_type":73,"marks":213,"text":214},"b1f67ea6a8a7",[]," API to talk to the Google Drive v3 REST API. It keeps the extension ridiculously fast and lightweight. The catch? You have to build ",{"_key":216,"_type":73,"marks":217,"text":218},"951d9c4d726a",[121],"multipart\u002Frelated",{"_key":220,"_type":73,"marks":221,"text":222},"cb2eac63f010",[]," HTTP bodies by hand if you want to upload metadata and file content in the exact same request.",[],{"_key":225,"_type":69,"children":226,"markDefs":239,"style":77},"d93d34082e2a",[227,231,235],{"_key":228,"_type":73,"marks":229,"text":230},"25a6cb6006aa",[],"That means manually wrangling string boundaries in vanilla JavaScript and making sure your carriage returns (",{"_key":232,"_type":73,"marks":233,"text":234},"1193c8514fe3",[121],"\\r\\n",{"_key":236,"_type":73,"marks":237,"text":238},"064fabaac2ab",[],") are flawless.",[],{"_key":241,"_type":121,"code":242,"language":243,"markDefs":12},"6df81ebf0117","\u002F\u002F building the raw multipart string\nconst boundary = 'sync_boundary_' + Date.now();\nconst delimiter = \"\\r\\n--\" + boundary + \"\\r\\n\";\nconst close_delim = \"\\r\\n--\" + boundary + \"--\";\n\nconst bodyString = delimiter + \n    'Content-Type: application\u002Fjson; charset=UTF-8\\r\\n\\r\\n' + \n    JSON.stringify(metadata) + delimiter + \n    'Content-Type: application\u002Fjson\\r\\n\\r\\n' + \n    JSON.stringify({ notes: localData }) + close_delim;","javascript",{"_key":245,"_type":69,"children":246,"markDefs":259,"style":77},"9075d1f9cdee",[247,251,255],{"_key":248,"_type":73,"marks":249,"text":250},"f3b82d5e278c",[],"Writing raw HTTP requests like this is honestly pretty annoying, especially when you know ",{"_key":252,"_type":73,"marks":253,"text":254},"ea33ce5a5893",[121],"drive.files.create()",{"_key":256,"_type":73,"marks":257,"text":258},"31604fb83569",[]," is just one line of code in the SDK. But shedding all that dependency weight makes the extension snap instantly, so it's a trade-off I'd make again.",[],{"_key":261,"_type":69,"children":262,"markDefs":267,"style":102},"607b6f852d73",[263],{"_key":264,"_type":73,"marks":265,"text":266},"a56553810063",[],"Engineering for Constraints",[],{"_key":269,"_type":69,"children":270,"markDefs":275,"style":77},"6ead8ff1f781",[271],{"_key":272,"_type":73,"marks":273,"text":274},"259902a4e797",[],"",[],{"_key":277,"_type":69,"children":278,"markDefs":283,"style":77},"b8f3ba008c1b",[279],{"_key":280,"_type":73,"marks":281,"text":282},"cf649bb6c400",[],"Manifest V3 feels restrictive at first. However, treating it as a hard constraint forces better design. By accepting that state will die, writing defensive offline checks, and dropping heavy libraries, you can build cloud integrations that actually feel native to the browser.",[],true,"2026\u002F05\u002F12","2026-05-12T16:20:21.600Z",{"_type":10,"current":288},"building-a-google-drive-sync-engine-that-survives-mv3-service-workers",[290,311,317],{"_createdAt":291,"_id":292,"_rev":293,"_system":294,"_type":297,"_updatedAt":298,"description":299,"slug":308,"title":310},"2025-04-24T16:28:57Z","797b8797-6e65-4723-b53f-8bc005305384","IpfPEqg1c3Byvj9RrB3Xaj",{"base":295},{"id":292,"rev":296},"oc42Nphz1oZNOg9ttSv7Tn","blogTag","2026-05-07T14:43:30Z",[300],{"_key":301,"_type":69,"children":302,"markDefs":307,"style":77},"bb32f75814b4",[303],{"_key":304,"_type":73,"marks":305,"text":306},"dbcf27ef29b3",[],"Community-generated articles submitted for your reading pleasure. If you’re interested in seeing your work here, log in with your Stack Overflow account and click the link below. Articles will be licensed under a CC BY-SA 4.0 grant. ",[],{"_type":10,"current":309},"contributed","The Heap",{"_createdAt":312,"_id":313,"_rev":314,"_type":297,"_updatedAt":312,"slug":315,"title":316},"2023-05-23T16:43:21Z","wp-tagcat-programming","9HpbCsT2tq0xwozQfkc4ih",{"current":316},"programming",{"_createdAt":318,"_id":319,"_rev":320,"_system":321,"_type":297,"_updatedAt":324,"description":325,"slug":345,"title":347},"2025-08-08T15:49:22Z","39391cf4-6f9a-4238-8670-c1e44b66db9e","09X6HDzCi2VfMov6gSLf7H",{"base":322},{"id":319,"rev":323},"TdCcmC7LyfLVwjB8GEXoh6","2025-12-10T19:34:33Z",[326,334],{"_key":327,"_type":69,"children":328,"markDefs":333,"style":77},"a4b1a37cbbcc",[329],{"_key":330,"_type":73,"marks":331,"text":332},"d8e8f3e0fd9c",[],"These articles are licensed under a Creative Commons Attribution-ShareAlike 4.0 International license. ",[],{"_key":335,"_type":69,"children":336,"markDefs":342,"style":77},"7effd489c71f",[337],{"_key":338,"_type":73,"marks":339,"text":341},"538808bb5325",[340],"fd643b288690","creativecommons.org\u002Flicenses\u002Fby-sa\u002F4.0\u002Fdeed.en",[343],{"_key":340,"_type":344},"link",{"_type":10,"current":346},"cc-by-sa","CC BY-SA 4.0","Building a Google Drive Sync Engine that Survives MV3 Service Workers",[350,356,362,368],{"_id":351,"publishedAt":352,"slug":353,"sponsored":12,"title":355},"76c9771b-34e6-4d98-8641-ecefc711f0ef","2026-07-06T15:23:34.559Z",{"_type":10,"current":354},"when-the-sensor-starts-thinking-snortml-agentic-ai-and-the-evolving-architecture-of-intrusion-detection","When the sensor starts thinking: SnortML, agentic AI, and the evolving architecture of intrusion detection",{"_id":357,"publishedAt":358,"slug":359,"sponsored":12,"title":361},"28e560af-f0aa-4d46-bd90-f435ad604aa7","2026-06-26T14:00:27.102Z",{"_type":10,"current":360},"paging-charity-how-can-engineering-leaders-avoid-becoming-bond-villains","Paging Charity! How can engineering leaders avoid becoming Bond villains?",{"_id":363,"publishedAt":364,"slug":365,"sponsored":12,"title":367},"4b22c2a3-3779-4966-93eb-5230391dbdce","2026-06-23T14:08:58.595Z",{"_type":10,"current":366},"your-ai-shipped-a-backend-that-boots-that-is-the-whole-problem","Your AI shipped a backend that boots. That is the whole problem.",{"_id":369,"publishedAt":370,"slug":371,"sponsored":12,"title":373},"5cf362e1-fe7b-45af-b69c-914731c6a052","2026-06-23T14:00:00.000Z",{"_type":10,"current":372},"the-2026-developer-survey-is-now-open-for-human-developers-only","The 2026 Developer Survey is now open (for human developers only)!",{"data":375,"sourceMap":-1},{"count":376,"lastTimestamp":12},0]