[{"data":1,"prerenderedAt":673},["ShallowReactive",2],{"blog-post:\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar":3,"blog-navigation:\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar":602,"blog-audio:debugging-home-wifi-claude-code-dfs-radar":640,"i-lucide:share-2":645,"related-articles:\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar":649},{"path":4,"title":5,"description":6,"date":7,"tags":8,"body":13,"image":598,"imageLight":599,"imageAlt":600,"imagePosition":601},"\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar","Debugging Home Wi-Fi with Claude Code and the 5 GHz Radar Rules","How an AI agent with browser and terminal access found a 2.4 GHz bottleneck on a 5 Gbps fiber line, and why 5 GHz Wi-Fi sometimes has to wait for radar.","2026-11-06",[9,10,11,12],"ai","debugging","networking","hardware",{"type":14,"value":15,"toc":589},"minimark",[16,20,23,28,31,220,231,234,238,241,252,255,258,326,335,338,394,397,401,404,407,415,418,421,425,428,431,523,526,529,532,535,539,542,552,555,558,562,565,572,576,579,582,585],[17,18,19],"p",{},"My internet felt slow, so I ran a speed test: 78 Mbps down, 26 Mbps up. My plan is fiber at 5 Gbps down and 900 Mbps up, so something between the fiber and my laptop was eating more than 98% of the line.",[17,21,22],{},"The usual fix is an evening of clicking through the router's admin panel and guessing which setting matters. I had a lunch break, not an evening, so I asked Claude Code to do it instead. It ran from the desktop app with access to my Chrome and my Mac's terminal, and I ate while it worked.",[24,25,27],"h2",{"id":26},"the-router-already-knows-everything","The router already knows everything",[17,29,30],{},"The router is an iliadbox, the box my ISP ships. I logged in to its admin page myself. That page is a JavaScript app on top of a local JSON API, and the agent noticed. It clicked around for about a minute, then stopped taking screenshots and started calling the API from inside the logged-in tab:",[32,33,38],"pre",{"className":34,"code":35,"language":36,"meta":37,"style":37},"language-js shiki shiki-themes github-light-high-contrast-aaa github-light-high-contrast-aaa material-theme-palenight-aaa","const get = async (path) => (await (await fetch(`\u002Fapi\u002Fv16\u002F${path}`)).json()).result\n\nawait get(\"connection\u002F\") \u002F\u002F line type, state, bandwidth\nawait get(\"connection\u002Fftth\u002F\") \u002F\u002F fiber module, optical power\nawait get(\"wifi\u002Fbss\u002F\") \u002F\u002F the Wi-Fi networks\nawait get(\"wifi\u002Fap\u002F0\u002Fstations\u002F\") \u002F\u002F who is on 2.4 GHz, with signal and link rate\n","js","",[39,40,41,126,133,157,178,199],"code",{"__ignoreMap":37},[42,43,46,50,54,58,61,65,69,72,75,78,82,84,86,90,93,97,101,104,106,109,112,115,118,121,123],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"sk6OM","const",[42,51,53],{"class":52},"sp76X"," get",[42,55,57],{"class":56},"sBxmf"," =",[42,59,60],{"class":48}," async",[42,62,64],{"class":63},"s8YBD"," (",[42,66,68],{"class":67},"sYw69","path",[42,70,71],{"class":63},")",[42,73,74],{"class":48}," =>",[42,76,64],{"class":77},"sLvJc",[42,79,81],{"class":80},"slrJH","await",[42,83,64],{"class":77},[42,85,81],{"class":80},[42,87,89],{"class":88},"sbs6Q"," fetch",[42,91,92],{"class":77},"(",[42,94,96],{"class":95},"sghLD","`",[42,98,100],{"class":99},"szLOH","\u002Fapi\u002Fv16\u002F",[42,102,103],{"class":95},"${",[42,105,68],{"class":77},[42,107,108],{"class":95},"}`",[42,110,111],{"class":77},"))",[42,113,114],{"class":63},".",[42,116,117],{"class":88},"json",[42,119,120],{"class":77},"())",[42,122,114],{"class":63},[42,124,125],{"class":77},"result\n",[42,127,129],{"class":44,"line":128},2,[42,130,132],{"emptyLinePlaceholder":131},true,"\n",[42,134,136,138,140,142,145,148,150,153],{"class":44,"line":135},3,[42,137,81],{"class":80},[42,139,53],{"class":88},[42,141,92],{"class":77},[42,143,144],{"class":95},"\"",[42,146,147],{"class":99},"connection\u002F",[42,149,144],{"class":95},[42,151,152],{"class":77},") ",[42,154,156],{"class":155},"skbv8","\u002F\u002F line type, state, bandwidth\n",[42,158,160,162,164,166,168,171,173,175],{"class":44,"line":159},4,[42,161,81],{"class":80},[42,163,53],{"class":88},[42,165,92],{"class":77},[42,167,144],{"class":95},[42,169,170],{"class":99},"connection\u002Fftth\u002F",[42,172,144],{"class":95},[42,174,152],{"class":77},[42,176,177],{"class":155},"\u002F\u002F fiber module, optical power\n",[42,179,181,183,185,187,189,192,194,196],{"class":44,"line":180},5,[42,182,81],{"class":80},[42,184,53],{"class":88},[42,186,92],{"class":77},[42,188,144],{"class":95},[42,190,191],{"class":99},"wifi\u002Fbss\u002F",[42,193,144],{"class":95},[42,195,152],{"class":77},[42,197,198],{"class":155},"\u002F\u002F the Wi-Fi networks\n",[42,200,202,204,206,208,210,213,215,217],{"class":44,"line":201},6,[42,203,81],{"class":80},[42,205,53],{"class":88},[42,207,92],{"class":77},[42,209,144],{"class":95},[42,211,212],{"class":99},"wifi\u002Fap\u002F0\u002Fstations\u002F",[42,214,144],{"class":95},[42,216,152],{"class":77},[42,218,219],{"class":155},"\u002F\u002F who is on 2.4 GHz, with signal and link rate\n",[17,221,222,223,226,227,230],{},"Its first attempt used an older API version and got empty lists back for the access points. It asked ",[39,224,225],{},"\u002Fapi_version",", saw ",[39,228,229],{},"16.1",", and retried. I would have lost twenty minutes on that. The same endpoint reports the box as a Freebox Server, so the iliadbox runs the same software as Free's Freebox routers in France. Plenty of other routers have no documented local API at all. On those, an agent can still read the web interface page by page, just more slowly.",[17,232,233],{},"The line itself was fine: FTTH, link up, optical power in the normal range, and almost no traffic during the test. Whatever was slow was inside my apartment.",[24,235,237],{"id":236},"seven-devices-on-24-ghz","Seven devices on 2.4 GHz",[17,239,240],{},"The router was broadcasting two network names, one per band, which surprised me. I had set a single name for both bands on purpose, after earlier trouble with 5 GHz going quiet because of radar (more on that below). With one name, devices stay online on 2.4 GHz while 5 GHz is unavailable and then move back. The price is that the router and the device decide which band you're on, and they don't always pick the faster one. Two names give you control over the band, but a device can only fall back to a network it has saved.",[17,242,243,244,247,248,251],{},"At some point the bands had been split into two names again. My laptop had joined the 2.4 GHz one, the only one it had a password for, so it never tried 5 GHz. A second Mac, an iPhone, the printer, and three small devices that only identified themselves as ",[39,245,246],{},"wlan0"," and ",[39,249,250],{},"lwip0"," were on it too. That made seven clients on a 20 MHz channel, with fourteen neighboring networks visible in the same band.",[17,253,254],{},"On 5 GHz there was a single Pixel phone, holding a link around 1.2 Gbps with a weaker signal than my laptop had on 2.4.",[17,256,257],{},"The terminal confirmed it from the laptop's side:",[32,259,263],{"className":260,"code":261,"language":262,"meta":37,"style":37},"language-bash shiki shiki-themes github-light-high-contrast-aaa github-light-high-contrast-aaa material-theme-palenight-aaa","system_profiler SPAirPortDataType | grep -A10 \"Current Network Information\"\n#   Channel: 6 (2GHz, 20MHz)\n#   Transmit Rate: 195\n\nnetworkQuality -s\n#   Downlink capacity: 62.026 Mbps\n#   Uplink capacity: 28.970 Mbps\n","bash",[39,264,265,293,298,303,307,315,320],{"__ignoreMap":37},[42,266,267,271,274,277,280,284,287,290],{"class":44,"line":45},[42,268,270],{"class":269},"sa3fw","system_profiler",[42,272,273],{"class":99}," SPAirPortDataType",[42,275,276],{"class":56}," |",[42,278,279],{"class":269}," grep",[42,281,283],{"class":282},"sd7-E"," -A10",[42,285,286],{"class":95}," \"",[42,288,289],{"class":99},"Current Network Information",[42,291,292],{"class":95},"\"\n",[42,294,295],{"class":44,"line":128},[42,296,297],{"class":155},"#   Channel: 6 (2GHz, 20MHz)\n",[42,299,300],{"class":44,"line":135},[42,301,302],{"class":155},"#   Transmit Rate: 195\n",[42,304,305],{"class":44,"line":159},[42,306,132],{"emptyLinePlaceholder":131},[42,308,309,312],{"class":44,"line":180},[42,310,311],{"class":269},"networkQuality",[42,313,314],{"class":282}," -s\n",[42,316,317],{"class":44,"line":201},[42,318,319],{"class":155},"#   Downlink capacity: 62.026 Mbps\n",[42,321,323],{"class":44,"line":322},7,[42,324,325],{"class":155},"#   Uplink capacity: 28.970 Mbps\n",[17,327,328,330,331,334],{},[39,329,311],{}," ships with macOS and measures against Apple's servers, which makes it a decent second opinion next to a browser speed test. The ",[39,332,333],{},"Transmit Rate"," is the link rate the Wi-Fi negotiated before overhead, so real throughput always lands below it.",[17,336,337],{},"I joined the 5 GHz network and the agent ran the test again.",[339,340,341,357],"table",{},[342,343,344],"thead",{},[345,346,347,351,354],"tr",{},[348,349,350],"th",{},"Band",[348,352,353],{},"Down",[348,355,356],{},"Up",[358,359,360,372,383],"tbody",{},[345,361,362,366,369],{},[363,364,365],"td",{},"2.4 GHz, 20 MHz",[363,367,368],{},"62 Mbps",[363,370,371],{},"29 Mbps",[345,373,374,377,380],{},[363,375,376],{},"5 GHz, 160 MHz",[363,378,379],{},"862 Mbps",[363,381,382],{},"369 Mbps",[345,384,385,388,391],{},[363,386,387],{},"5 GHz, 160 MHz, rerun",[363,389,390],{},"1.02 Gbps",[363,392,393],{},"431 Mbps",[17,395,396],{},"Switching bands was the whole fix. The 2.4 GHz band was too crowded to carry more than a few dozen megabits.",[24,398,400],{"id":399},"then-i-mentioned-the-airport","Then I mentioned the airport",[17,402,403],{},"I live near a small airport, and I already knew radar could knock out my 5 GHz Wi-Fi. That was the reason for the single network name. I didn't know how my router's settings played into it, so I asked the agent to check the rest of the configuration with the airport in mind. It went straight to the 5 GHz channel list.",[17,405,406],{},"Part of the 5 GHz band is shared with radar, and radar has priority. Wi-Fi may use those channels only under DFS (Dynamic Frequency Selection), which is a legal requirement in both Europe and the US, so any router sold there follows it. Before transmitting on a DFS channel, the access point has to listen for radar, usually for a minute. If it hears radar later, it must leave the channel and stay off it for 30 minutes. In Europe, channels 36 to 48 are free of this, while channels 52 to 64 and 100 to 140 are not. The US also has channels 149 to 165 free of radar rules. Most routers mark DFS channels somewhere in their channel settings. Mine did it through the API:",[32,408,413],{"className":409,"code":411,"language":412,"meta":37},[410],"language-text","80 MHz:   36  40  44  48  52* 56* 60* 64* 100* ... 128*\n160 MHz:  36* 40* 44* 48* 52* 56* 60* 64* 100* ... 128*\n","text",[39,414,411],{"__ignoreMap":37},[17,416,417],{},"Every 160 MHz option needs DFS, including the one that starts at channel 36. A 160 MHz channel is so wide that the block from 36 to 64 includes the radar channels 52 to 64. Picking 36 by hand still means a minute of listening before the network starts, and the router still has to leave if radar shows up. Only 80 MHz or narrower on channels 36 to 48 avoids the rules in Europe.",[17,419,420],{},"I knew radar was the problem, but I hadn't connected it to channel width. My router was set to 160 MHz with automatic channel selection, which is the fastest option and also the one most exposed to radar.",[24,422,424],{"id":423},"ten-minutes-without-5-ghz","Ten minutes without 5 GHz",[17,426,427],{},"While checking settings I asked whether the router was up to date. The firmware was 4.12.4, newer than any version I could find mentioned online, and the box installs pending updates when it restarts. So we restarted it.",[17,429,430],{},"The 5 GHz network didn't come back. The laptop fell back to 2.4 GHz, and the status endpoint showed why:",[32,432,435],{"className":433,"code":434,"language":117,"meta":37,"style":37},"language-json shiki shiki-themes github-light-high-contrast-aaa github-light-high-contrast-aaa material-theme-palenight-aaa","{\n  \"state\": \"dfs\",\n  \"primary_channel\": 124,\n  \"channel_width\": \"160\",\n  \"dfs_cac_remaining_time\": 360\n}\n",[39,436,437,442,467,484,504,518],{"__ignoreMap":37},[42,438,439],{"class":44,"line":45},[42,440,441],{"class":63},"{\n",[42,443,444,448,452,454,457,459,462,464],{"class":44,"line":128},[42,445,447],{"class":446},"sbUHo","  \"",[42,449,451],{"class":450},"so3xt","state",[42,453,144],{"class":446},[42,455,456],{"class":63},":",[42,458,286],{"class":95},[42,460,461],{"class":99},"dfs",[42,463,144],{"class":95},[42,465,466],{"class":63},",\n",[42,468,469,471,474,476,478,482],{"class":44,"line":135},[42,470,447],{"class":446},[42,472,473],{"class":450},"primary_channel",[42,475,144],{"class":446},[42,477,456],{"class":63},[42,479,481],{"class":480},"s65Wh"," 124",[42,483,466],{"class":63},[42,485,486,488,491,493,495,497,500,502],{"class":44,"line":159},[42,487,447],{"class":446},[42,489,490],{"class":450},"channel_width",[42,492,144],{"class":446},[42,494,456],{"class":63},[42,496,286],{"class":95},[42,498,499],{"class":99},"160",[42,501,144],{"class":95},[42,503,466],{"class":63},[42,505,506,508,511,513,515],{"class":44,"line":180},[42,507,447],{"class":446},[42,509,510],{"class":450},"dfs_cac_remaining_time",[42,512,144],{"class":446},[42,514,456],{"class":63},[42,516,517],{"class":480}," 360\n",[42,519,520],{"class":44,"line":201},[42,521,522],{"class":63},"}\n",[17,524,525],{},"On boot the router picked channel 124, which sits in the 5600 to 5650 MHz range used by weather radar. There, the listening period before transmitting is ten minutes instead of one. Any restart that lands on those channels means ten minutes without the fast network.",[17,527,528],{},"From the outside, this is what radar trouble looks like. The 5 GHz network disappears for about a minute, or about ten on channels 120 to 128, and then comes back, often on a different channel. Meanwhile your devices drop to 2.4 GHz or lose Wi-Fi entirely. Some routers log radar events. Mine doesn't show one that I could find, so the countdown in the status endpoint was my only evidence.",[17,530,531],{},"I watched the countdown drop to zero. The laptop rejoined 5 GHz on channel 124 at 160 MHz and posted the best result of the day: 1.16 Gbps down, 634 Mbps up.",[17,533,534],{},"That is about as far as Wi-Fi goes on this laptop. Its link rate at 160 MHz was 1729 Mbps, and real throughput landed around 1.1 Gbps. Getting closer to the 5 Gbps on the contract needs a cable, a 2.5 or 10 Gbps Ethernet adapter, and a router port that supports that speed. I haven't checked what my router's ports support yet.",[24,536,538],{"id":537},"_160-mhz-or-80-mhz","160 MHz or 80 MHz",[17,540,541],{},"The agent laid out the trade-off:",[543,544,545,549],"ul",{},[546,547,548],"li",{},"160 MHz gets around 1.1 Gbps on this laptop, but the 5 GHz network can disappear for one to ten minutes after a restart, or whenever the router decides it heard radar.",[546,550,551],{},"80 MHz on channels 36 to 48 gets roughly half the top speed, with no radar rules at all.",[17,553,554],{},"The 6 GHz band used by Wi-Fi 6E and Wi-Fi 7 would avoid the question, since it has no DFS radar rules. It needs a router and devices that support it, and mine only has 2.4 GHz and 5 GHz radios.",[17,556,557],{},"I kept 160 MHz for now. Without a radar log, I don't know how often the airport actually kicks the router off a channel. If 5 GHz starts dropping out for a minute at a time, I'll move to channel 36 at 80 MHz and stop thinking about it. Half of 1.1 Gbps is still close to nine times what I started with.",[24,559,561],{"id":560},"two-security-settings-on-the-way","Two security settings on the way",[17,563,564],{},"The audit turned up two problems unrelated to speed. The IPv6 firewall was off, so any device with a public IPv6 address relied entirely on its own firewall. WPS was also enabled on both networks. With WPS off, devices that join through the WPS button instead of a password, such as some printers and Wi-Fi extenders, now need the network password.",[17,566,567,568,571],{},"The agent only changed settings after I said yes. It fixed both through the same API with two ",[39,569,570],{},"PUT"," requests. Turning off WPS dropped the Wi-Fi for a few seconds and cut the agent's own connection mid-request. It checked from the terminal that the router was reachable again, re-read the settings, and confirmed both changes had stuck.",[24,573,575],{"id":574},"what-the-agent-was-actually-good-at","What the agent was actually good at",[17,577,578],{},"The router had all of this information the whole time: band, channel, client list, signal strength, DFS state, firewall flags. The admin UI spreads it across a dozen panels, and none of them tell you that seven devices on 2.4 GHz is your problem.",[17,580,581],{},"An agent with a logged-in browser tab can read the router's raw state in a minute, and with a terminal it can see the client side too. Seeing both at once made the 2.4 GHz problem obvious.",[17,583,584],{},"I knew about the radar from past outages, and a single network name was my workaround. I didn't know channel 124 had a ten-minute timer or that 160 MHz always means DFS, and I hadn't noticed the workaround was gone. If you live near an airport or a weather radar and your 5 GHz Wi-Fi vanishes for a few minutes after a power outage, that is probably why.",[586,587,588],"style",{},"html pre.shiki code .sk6OM, html code.shiki .sk6OM{--shiki-light:#A0111F;--shiki-default:#A0111F;--shiki-dark:#C792EA}html pre.shiki code .sp76X, html code.shiki .sp76X{--shiki-light:#622CBC;--shiki-default:#622CBC;--shiki-dark:#BABED8}html pre.shiki code .sBxmf, html code.shiki .sBxmf{--shiki-light:#A0111F;--shiki-default:#A0111F;--shiki-dark:#89DDFF}html pre.shiki code .s8YBD, html code.shiki .s8YBD{--shiki-light:#0E1116;--shiki-default:#0E1116;--shiki-dark:#89DDFF}html pre.shiki code .sYw69, html code.shiki .sYw69{--shiki-light:#702C00;--shiki-light-font-style:inherit;--shiki-default:#702C00;--shiki-default-font-style:inherit;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sLvJc, html code.shiki .sLvJc{--shiki-light:#0E1116;--shiki-default:#0E1116;--shiki-dark:#BABED8}html pre.shiki code .slrJH, html code.shiki .slrJH{--shiki-light:#A0111F;--shiki-light-font-style:inherit;--shiki-default:#A0111F;--shiki-default-font-style:inherit;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sbs6Q, html code.shiki .sbs6Q{--shiki-light:#622CBC;--shiki-default:#622CBC;--shiki-dark:#82AAFF}html pre.shiki code .sghLD, html code.shiki .sghLD{--shiki-light:#032563;--shiki-default:#032563;--shiki-dark:#89DDFF}html pre.shiki code .szLOH, html code.shiki .szLOH{--shiki-light:#032563;--shiki-default:#032563;--shiki-dark:#C3E88D}html pre.shiki code .skbv8, html code.shiki .skbv8{--shiki-light:#495159;--shiki-light-font-style:inherit;--shiki-default:#495159;--shiki-default-font-style:inherit;--shiki-dark:#969EC4;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sa3fw, html code.shiki .sa3fw{--shiki-light:#702C00;--shiki-default:#702C00;--shiki-dark:#FFCB6B}html pre.shiki code .sd7-E, html code.shiki .sd7-E{--shiki-light:#023B95;--shiki-default:#023B95;--shiki-dark:#C3E88D}html pre.shiki code .sbUHo, html code.shiki .sbUHo{--shiki-light:#024C1A;--shiki-default:#024C1A;--shiki-dark:#89DDFF}html pre.shiki code .so3xt, html code.shiki .so3xt{--shiki-light:#024C1A;--shiki-default:#024C1A;--shiki-dark:#C792EA}html pre.shiki code .s65Wh, html code.shiki .s65Wh{--shiki-light:#023B95;--shiki-default:#023B95;--shiki-dark:#F78C6C}",{"title":37,"searchDepth":128,"depth":128,"links":590},[591,592,593,594,595,596,597],{"id":26,"depth":128,"text":27},{"id":236,"depth":128,"text":237},{"id":399,"depth":128,"text":400},{"id":423,"depth":128,"text":424},{"id":537,"depth":128,"text":538},{"id":560,"depth":128,"text":561},{"id":574,"depth":128,"text":575},"\u002Fimages\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar.png","\u002Fimages\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar-light.jpg","An AI-generated cartoon of Martin on his lunch break, wearing a headset and holding a half-eaten sandwich, staring wide-eyed at a glowing green radar screen with one red blip on his bedroom desk. A Wi-Fi router, a coffee mug and tangled cables sit beside it, and through the window a small airport shows a control tower, a radar dish and a plane taking off.","40% 30%",[603,604,608,612,616,620,624,628,632,636],{"path":4,"title":5,"date":7},{"path":605,"title":606,"date":607},"\u002Fblog\u002Fbuilding-martinmasevski-dev-nuxt-vue","How I built this site with Nuxt and Vue and kept Lighthouse at 100","2026-11-04",{"path":609,"title":610,"date":611},"\u002Fblog\u002Fdesigning-an-accessible-light-theme","Turning a neon dark site into an accessible light theme","2026-11-01",{"path":613,"title":614,"date":615},"\u002Fblog\u002Fstorybook-swiss-knife-visual-accessibility-checks","Visual and accessibility checks for every Storybook PR","2026-10-14",{"path":617,"title":618,"date":619},"\u002Fblog\u002Fstorybook-pr-previews-github-pages-untrusted-code","Storybook PR previews on GitHub Pages without trusting PR code","2026-09-24",{"path":621,"title":622,"date":623},"\u002Fblog\u002Fvisual-regression-baseline-cache-key","A GitHub Actions cache key for visual regression baselines","2026-08-18",{"path":625,"title":626,"date":627},"\u002Fblog\u002Faccessibility-baseline-block-new-violations","Blocking new accessibility violations with an axe baseline","2026-07-14",{"path":629,"title":630,"date":631},"\u002Fblog\u002Freviving-a-raspberry-pi-with-ai","Reviving a Raspberry Pi with AI","2026-05-26",{"path":633,"title":634,"date":635},"\u002Fblog\u002Fbuilding-local-first-browser-tts-studio-kokoro","Building a local-first browser TTS studio with Kokoro","2026-03-28",{"path":637,"title":638,"date":639},"\u002Fblog\u002Fdebugging-timezone-chrome-devtools-mcp","How to change the timezone in Chrome DevTools and debug it with MCP","2026-03-14",{"hasAudio":641,"sources":642},false,{"opus":643,"mp3":644},"https:\u002F\u002Fbucket.martinmasevski.dev\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar\u002Fdebugging-home-wifi-claude-code-dfs-radar.opus","https:\u002F\u002Fbucket.martinmasevski.dev\u002Fblog\u002Fdebugging-home-wifi-claude-code-dfs-radar\u002Fdebugging-home-wifi-claude-code-dfs-radar.mp3",{"left":646,"top":646,"width":647,"height":647,"rotate":646,"vFlip":641,"hFlip":641,"body":648},0,24,"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"18\" cy=\"5\" r=\"3\"\u002F>\u003Ccircle cx=\"6\" cy=\"12\" r=\"3\"\u002F>\u003Ccircle cx=\"18\" cy=\"19\" r=\"3\"\u002F>\u003Cpath d=\"m8.59 13.51l6.83 3.98m-.01-10.98l-6.82 3.98\"\u002F>\u003C\u002Fg>",[650,658,666],{"path":629,"title":630,"description":651,"date":631,"tags":652,"image":655,"imageLight":656,"imageAlt":657},"How a decade-old Raspberry Pi, recycled cameras, and an AI-assisted workflow became a working remote camera server.",[9,653,12,654,10],"raspberry-pi","side-projects","\u002Fimages\u002Fblog\u002Freviving-a-raspberry-pi-with-ai.png",null,"A square cyberpunk-style illustration of a developer intensely soldering a glowing 2014 Raspberry Pi at a workbench, aided by two small, glowing AI holograms. He is surrounded by floating terminal windows, debugging labels, and a curved \"Surveillance Wall\" displaying low-resolution camera feeds of potted plants.",{"path":637,"title":638,"description":659,"date":639,"tags":660,"image":664,"imageLight":656,"imageAlt":665},"How to override the timezone in Chrome DevTools Sensors, and how Chrome DevTools MCP helped me debug a timezone bug that turned a calendar date into the wrong day.",[661,10,9,662,663],"mcp","devtools","vitest","\u002Fimages\u002Fblog\u002Fdebugging-timezone-chrome-devtools-mcp.png","An editorial cartoon of a developer caught between two giant glowing clocks showing different times, surrounded by neon-lit debugging tools and timezone labels.",{"path":621,"title":622,"description":667,"date":623,"tags":668,"image":670,"imageLight":671,"imageAlt":672},"Our visual regression cache missed on almost every PR: the key hashed the CI environment, not the screenshots. The fix helped halve PR check time.",[669,10,662],"testing","\u002Fimages\u002Fblog\u002Fvisual-regression-baseline-cache-key.png","\u002Fimages\u002Fblog\u002Fvisual-regression-baseline-cache-key-light.jpg","An editorial cartoon of Martin as a Roman emperor in a neon-lit Colosseum, giving a thumbs down to a broken, ornate key tagged ImageVersion + lockfile while holding up a small new key, as the gate to a vault of saved screenshots swings open.",1794215543947]