Skip to content
Frontend Feeds
  • Today
  • Archive
  • Sources
  • Categories
    • The Giants6 Sources
    • Multi Author Blogs18 Sources
    • Top Front-end Bloggers25 Sources
    • More Front-end Bloggers77 Sources
    • Browsers, engines, etc.11 Sources
    • Libraries, Frameworks, etc.12 Sources
    • Company/Startup Blogs12 Sources
    • Developer/Designer News4 Sources
    • YouTube Channels13 Sources
    • Podcasts7 Sources

Today

Front-end, in one read.

The latest from the sources worth following, newest first.

Thursday, 5 November 2026

  • Web Tools WeeklyMulti Author Blogsread at source

    Web Tools Weekly Issue #694

    React Tools, AI Tools, Text Editors

Tuesday, 3 November 2026

  • David BushellMore Front-end Bloggers2 min read

    Death comes for XSLT

    Last halloween, Google decided to kill XSLT. That was not a unilateral decision mind you, Google said “jump” and other browser vendors said “how high?” In response, I published my Intent to Deprecate and Remove Google Chrome. A performative declaration of course, I don’t install spyware. […]

  • DEV CommunityThe Giantsread at source

    I used Claude with an MCP server to prep for an earnings report: six prompts and the tool calls behind them

    Before a company reports, I want six things: the date, the consensus, management's guidance, last quarter's call, the options-implied move and the short interest. They live in six different places. Lately I've been asking for them in Claude with the Equibles MCP server connected (I work on Equibles), and I wanted to write down what the assistant actually calls behind each prompt. The example is SoundHound (SOUN), which reports Q3 on November 5. Every answer below came from tool calls on November 3. The six prompts "When does SoundHound report Q3, and when is the call?" The assistant calls GetUpcomingInvestorEvents : November 5, 22:00 UTC (5 p.m. Eastern), with a link to the event on SoundHound's investor site. "What do analysts expect, and has it moved?" GetAnalystEstimates : revenue of $62.18M on average from 6 analysts, EPS of -$0.05 from 5, in a snapshot captured October 29. "What's the 2026 revenue guidance, what does it leave for the second half, and has anything happened since that changes it?" GetGuidance , GetFinancialStatement and GetInvestorRelationsNews : $230M to $260M, set August 5; first-half revenue of $106.1M, so the second half needs $123.9M to $153.9M; and a LivePerson acquisition that closed September 4. "Summarize the last earnings call." GetEarningsBrief : record Q2 revenue of $61.9M, up 45%, a GAAP net loss of $42.8M, and the CFO's quote raising the outlook. "What move are options pricing for the report?" GetOptionExpirations , then GetOptionChain for November 6: the $4 straddle cost about $0.52 at the midpoints with the stock at $4.15, a move of about 12.5%. "How much of the stock is sold short?" GetShortInterest : 170.8M shares at the October 15 settlement, 10.1 days of average volume to cover. The prompt that mattered most was the third My first version of prompt 3 asked only for the guidance and what it left for the second half. The answer was correct and misleading. The guidance tool's newest source was the August 5 call, and SoundHound closed its acquisition of LivePerson on September 4. LivePerson booked $52.5M of revenue in Q2 on its own, and on the August call the CEO said the outlook would be updated to include it once the deal closed. So reported Q3 revenue will include 27 days of LivePerson, while the $62.2M consensus is barely above SoundHound's own $61.9M in Q2. Comparing the result with that consensus without knowing about the deal would mislead you. Adding "has anything happened since that changes it?" made the assistant read the investor relations news and the 8-K list, and the deal showed up. That's the general lesson for any tool-using assistant: a tool answers from what's on file as of its last source, so ask for the date of every number and for what has been filed since. What else I'd tell someone setting this up Ask for the as-of time. The consensus was almost five days old; option quotes carry a timestamp to the second; FINRA short interest comes out about a week and a half after its settlement date. Check the arithmetic once by hand. 44.2 + 61.9 = 106.1, and 230 - 106.1 = 123.9. After that, let the assistant do it. Combine the prompts once each one works: "Build me an earnings prep sheet for SoundHound's Q3 2026 report: the date and call time, the consensus with its date, the 2026 guidance and what it leaves for the second half, anything filed since that changes it, a summary of last quarter's call, the options-implied move for the first expiry after the report, and the latest short interest. Cite the source and date of every number." The full walkthrough, with every answer, how to check each one and the setup steps for ChatGPT and Claude, is here: https://equibles.com/blog/how-to-research-a-stock-before-earnings-with-chatgpt-or-claude What do you check before a company reports that isn't on this list? Data: SEC filings, FINRA short interest, company investor relations pages and option quotes, processed by Equibles.

  • DEV CommunityThe Giantsread at source

    Hyperliquid API in Python: our $25 cap refused 892 of 892 $25 orders

    Originally published on BotCookery , a free site of build recipes for AI-built trading and prediction-market bots. Written with AI assistance. Every number below comes from real runs against Hyperliquid's public API on Oct 25, 2026. Signed calls are testnet only, and nothing here is financial advice or a trading signal. We publish a copy-paste prompt that has an AI coding tool build a small Python starter for the Hyperliquid API: reads on mainnet or testnet, and signed orders on testnet only, capped at $25. On Oct 25 we checked whether that prompt actually holds up. We pasted the Oct 22 version into a clean folder, let it build, and ran it live: reads on both networks, a dry-run order for every listed perp, and signed testnet calls with a throwaway key that held no funds. Most of it worked. Reads, asset lookups, price rounding, the --mainnet flag and the testnet-only guard all did what they should. Every one of the 158 live testnet perps and 178 live mainnet perps got a valid price at $10. Then we asked for $25, and the bot refused. Every time, on both networks: 424 of 424 dry-run $25 orders on testnet and 468 of 468 on mainnet. The cap meant to keep orders small was the thing blocking them. That was one of four problems the rebuild turned up; here's each one, what we saw in the logs, and how the prompt changed. Hyperliquid in one paragraph Everything is a JSON POST. Reads go to /info with a type ( meta , allMids , l2Book , candleSnapshot , clearinghouseState , openOrders ) and need no signature. Orders go to /exchange and are signed, which is where the official hyperliquid-python-sdk earns its keep. Testnet lives at api.hyperliquid-testnet.xyz , mainnet at api.hyperliquid.xyz , and they are different markets with different prices. The first lines of each of our cycles were testnet mids, then mainnet mids: BTC 80973.5 ETH 2352.75 SOL 101.445 BTC 80646.5 ETH 2354.95 SOL 101.925 A few rules the prompt spells out, because an AI tool will otherwise guess: Asset IDs differ between networks. Look up the index and szDecimals from meta() on the same network every time. Never hardcode them. Prices: at most 5 significant figures and at most 6 - szDecimals decimals for perps. Integer prices are always valid. $10 minimum notional per order. Agent wallets have no balance. You approve an agent key to sign for your main account, but balance and open-order lookups must use the main address. 1. A $25 order could never pass The starter places a post-only ( Alo ) limit 10% away from the mid, so the order rests instead of filling. Size is usd / limit , rounded up to szDecimals . Rounding up is deliberate: rounding down, or sizing on the mid, leaves a $10 buy under the $10 exchange minimum. But rounding up always lands the notional a little over what you asked for. And the cap compared that notional to $25. So in the original build, plan BTC buy 25 printed: refusing: notional 25.51 > $25.0 That wasn't a BTC quirk. It refused all 424 dry-run $25 orders on testnet and all 468 on mainnet. The only orders that could pass were ones small enough that rounding didn't push them over, which made the "$10 to $25" range really "$10 to a bit under $25". The fix is to check two different things. The requested amount must be between $10 and $25. The order's own notional must be at least $10 and no more than the request plus one size step ( 10^-szDecimals x limit ). From the rebuilt version: $ python3 cli.py plan BTC buy 25 "sz": 0.00035, "limit_px": 72876.0, "notional": 25.5066, checks: pass 0.00035 x 72,876 = 25.5066. Over $25 by one step's worth of rounding, which is exactly what the cap now allows. 2. Delisted coins looked tradable allMids still quotes delisted perps. The original build treated "has a mid" as "trades", so: $ python3 cli.py plan MATIC buy 10 ... checks: pass MATIC is one of 54 delisted perps on testnet. The meta response marks them with "isDelisted": true , and the asset lookup now raises on that flag: $ python3 cli.py plan MATIC buy 10 error: MATIC is delisted on testnet Across three cycles of the rebuilt version, testnet listed 212 perps and mainnet 234. Every live one passed at both $10 and $25 on both sides (316/316 testnet, 356/356 mainnet), and every delisted one was refused: testnet 212 perps: {'pass_10': 316, 'pass_25': 316, 'refused_delisted': 216} [] mainnet 234 perps: {'pass_10': 356, 'pass_25': 356, 'refused_delisted': 224} [] 3. The dead-man switch would have blocked every order on a new account The prompt has the bot call scheduleCancel before every order: a dead-man switch that cancels all open orders at a set time if the bot dies. The original rule was strict. If scheduleCancel didn't return ok, send no order. The problem is that Hyperliquid only allows scheduleCancel above a trading-volume threshold. A published reply shows the error as Cannot set scheduled cancel time until enough volume traded. Required: $1000000 . A brand-new testnet account will never clear that, so the strict rule meant a reader following the recipe could never place an order at all. To be clear about what we saw: our throwaway key was never approved and the account was never funded, so the exchange stopped us earlier, before the volume check could come up. The orders.jsonl log from the run shows that refusal on every attempt: { "kind" : "schedule_cancel" , "req" : { "time" : 1792953489708 }, "resp" : { "status" : "err" , "response" : "User or API Wallet 0x46b7… does not exist." }} So the volume error itself is sourced, not observed, and the code path for it is tested offline only. The new behavior keeps the safety without the dead end: On that exact volume error, print dead-man switch unavailable for this account and continue. On any other non-ok reply, still refuse and send no order. That's what happened in our runs: refusing: schedule_cancel failed, no order sent . place now takes --hold SECONDS (10 to 300). If the order rests, the bot waits, then cancels it by oid in a finally block, so Ctrl-C during the hold still cancels. It then checks openOrders that the oid is gone and exits non-zero if it isn't. Without the exchange's dead-man switch, the bot is its own. That's a weaker guarantee, so the hold is short and the cancel is confirmed. One more check from the same runs: place and cancel-all don't accept --mainnet at all. Passing it gets cli.py: error: unrecognized arguments: --mainnet before anything is signed. 4. The main key was left on disk Hyperliquid lets a main account approve an "agent" (API wallet) key that signs on its behalf. The prompt's safety notes say to keep the main key away from the bot and give the bot only the agent key. The original approve_agent.py loaded the main key from the same env file it then wrote the agent key to. So after a successful approval, the key you're supposed to keep away from the bot sat right next to it. Now it reads the main key once with getpass.getpass() , never writes it anywhere, and saves only HL_MODE=testnet , HL_AGENT_PRIVATE_KEY and HL_ACCOUNT_ADDRESS (the main address) to ~/.config/hl-bot/.env with chmod 600 . It also saves only the agent key the SDK returns from approve_agent , and only if the result is ok. On our unfunded account it saved nothing: $ python3 approve_agent.py approve failed, nothing saved: {'status': 'err', 'response': 'Must deposit before performing actions. User: 0xd40d…'} Already built it? The follow-up prompt If you built the starter from the earlier prompt, paste this into the same project: Update my hl-starter: 1. asset_info(): raise on any coin whose meta entry has "isDelisted": true. allMids still returns a mid for delisted perps, so plan/place must not accept them. 2. check_order(): check the requested usd is between $10 and $25, and allow the order's own notional (size x limit) to be at most usd plus one size step (10^-szDecimals x limit). Rounding the size up means comparing the notional itself to $25 refuses every $25 order. 3. place(): Hyperliquid rejects schedule_cancel on accounts without enough traded volume ("Cannot set scheduled cancel time until enough volume traded..."). On that exact error, print "dead-man switch unavailable for this account" and continue; on any other schedule_cancel error, still send no order. Add `--hold SECONDS` (10-300, default 60): after the order rests, wait, then cancel it by oid in a `finally` (so Ctrl-C cancels too) and confirm with openOrders that it's gone; exit non-zero if it isn't. 4. approve_agent.py: read the main testnet key with getpass.getpass() and never write it to any file. Write only HL_MODE=testnet, HL_AGENT_PRIVATE_KEY and HL_ACCOUNT_ADDRESS, keeping other lines in the env file. Add a test for each, then run pytest and `python3 cli.py plan BTC buy 25`. Run it yourself The full prompt is on the recipe page . If you'd rather start from code, the rebuilt starter is on GitHub at lazyclaw137-ship-it/hyperliquid-api-starter-testnet . Reads need nothing: pip install hyperliquid-python-sdk requests python-dotenv pytest python3 cli.py mids python3 cli.py book BTC --mainnet python3 cli.py plan BTC buy 25 Signed calls need a testnet account and an approved agent key, and stay on testnet. The code checks for that twice. Verified working on live data in read-only mode, Oct 25, 2026. Reads ran live on testnet and mainnet, and order sizing was checked against every listed perp on both. Signed testnet calls used an unfunded throwaway key, so the exchange refused them before any order existed. We did not see a resting order, a fill or the dead-man volume error ourselves; the code path for that error is tested offline only. More recipes, including a Hyperliquid funding-rate monitor , are at botcookery.com .

  • DEV CommunityThe Giantsread at source

    Fine Grained Password Policies - FGPP

    Giriş Ön Koşullar FGP Oluşturmak Policy'nin Uygulandığını Teyit Etmek Giriş Tek bir domain ortamında birden fazla password policy oluşturulmasını sağlar. Bir domain ortamındaki farklı kullanıcılar ve gruplar için birbirinden farklı şifre ve hesap kilitlenme kuralları oluşturabilirsin. FGP policy sadece kullanıcı nesnelerine ve global security gruplarına uygulanabilir. Varsayılan olarak Domain Admins grubunda olan kullanıcılar FGP belirleyebilir ancak diğer kullanıcılara yetki devri yapılabilir. Ön Koşullar Domain function level değeri Windows Server 2012 veya üstü olmalıdır. Domain Admins grubuna üye olunmalıdır. Active Directory Administrative Center veya Active Directory Powershell modülü kurulu olmalıdır. FGP Oluşturmak Active Directory Administrative Center uygulamasını Server Manager üzerinden aç. Sol tarafta navigasyon çubuğunda şu yola ilerle: Domain Name > System > Password Settings Container Sağ tarafta Tasks çubuğunda new > password settings seçeneğine tıkla. Alanları doldurarak password ayarlarını oluştur. Precedence değeri düşük olan yüksek önceliklidir. Bu sebeple daha genel policy'lerine daha yüksek değer vermelisin. Directly Applies To bölümünde password policy'nin uygulanacağı kullanıcı veya grubu seç. Policy'nin Uygulandığını Teyit Etmek Get-ADUserResultantPasswordPolicy -Identity kullanici_adi komutunu Powershell üzerinde çalıştır. Okuduğunuz için teşekkürler, faydalı olması dileğiyle 😊

  • DEV CommunityThe Giantsread at source

    Trusted SSL Certificates for Localhost

    The best way I’ve found to add SSL certificates to a localhost environment is using mkcert , which is technically not a self-signed certificate at all. Instead, it creates a local certificate signing authority, adds it to your machine, and uses that to sign certificates. The end result is a fully valid SSL certificate that is not self-signed , so it works with modern browsers and aligns with security protocols that block self-signed SSL certs. What is mkcert? mkcert is an open source command line tool written by Filippo Valsorda , a cryptographer who spent years on the Go security team at Google. As described on GitHub , it’s: a simple tool for making locally-trusted development certificates that requires no configuration. Under the hood it does two things. First, it generates a private certificate authority (CA) that lives only on your machine and installs it into your system and browser trust stores, the same lists of trusted authorities that let your browser trust certificates from Let’s Encrypt or DigiCert. Second, it signs leaf certificates with that CA for whatever hostnames you ask for. Because the signing authority is trusted by your machine, every certificate it issues is trusted too: real HTTPS, green padlock, no warnings, no exceptions to click through. This is fundamentally different from the classic openssl self-signed certificate. A self-signed cert has no authority behind it, so browsers correctly treat it as untrustworthy. mkcert gives you the full chain of trust, just scoped privately to your computer. The private CA key never leaves your machine, which is also why this is safe: nobody else’s browser will ever trust certificates signed by your local CA. Fixing “Not Secure” Warnings on Localhost If you landed here trying to get rid of the “Your connection is not private” interstitial, the Not Secure label, or the red X through https:// in your address bar on a local site, this is the fix. Those warnings (usually NET::ERR_CERT_AUTHORITY_INVALID in Chrome or SEC_ERROR_UNKNOWN_ISSUER in Firefox) all mean the same thing: the certificate your local server presented isn’t signed by an authority your browser trusts. The common workarounds are all worse than the disease. Clicking “Proceed anyway” trains you to ignore security warnings and has to be repeated constantly. The chrome://flags/#allow-insecure-localhost flag was removed from Chrome entirely. And plain HTTP isn’t a real option anymore either, because features like service workers, secure cookies, HTTP/2, and many modern browser APIs only work in a secure context. mkcert gives your local environment full SSL, indistinguishable in the browser from a production site: valid certificate, padlock, no exceptions. Your dev environment behaves like production, which is the whole point of a dev environment. Installation mkcert supports Mac, Windows and Linux machines, so it should work with your setup. Mac OS & Linux brew install mkcert brew install nss The nss package is only needed if you use Firefox, which keeps its own certificate store separate from the operating system’s. Windows choco install mkcert Adding Localhost SSL Certificates Step 1: Create a Local Certificate Authority Once you’ve installed the tool, create the local certificate authority (CA) and install it into your trust stores: mkcert -install Step 2: Add SSL Certificates for Local URL’s Once you’ve set up a local authority for signing certificates, you can add SSL certificates for your localhost domains: mkcert example.test "*.example.test" localhost 127.0.0.1 ::1 This will create two *.pem certificate files, ./example.test+4.pem and ./example.test+4-key.pem , for the following hostnames: example.test *.example.test localhost 127.0.0.1 ::1 These SSL certificates effectively cover the following URLs that are common localhost hosts: https://example.test and any subdomain, like https://www.example.test https://localhost Root Stores The mkcert -install step works by adding your local CA to every certificate root store it can find. It currently supports: macOS system store Windows system store Linux variants that provide either update-ca-trust (Fedora, RHEL, CentOS), update-ca-certificates (Ubuntu, Debian, OpenSUSE, SLES), or trust (Arch) Firefox (macOS and Linux only) Chrome and Chromium Java (when JAVA_HOME is set) To install the local root CA into only a subset of them, set the TRUST_STORES environment variable to a comma-separated list. The options are system , java , and nss (which includes Firefox): TRUST_STORES = system,nss mkcert -install Configuring the Local Web Server You’ll need to configure a host pointing example.test and www.example.test to your local web server. Once that’s set up, and you have the certificates added to your system, you might be done. Many localhost servers automatically load certificates from your machine’s certificate store. If you restart your browser and the URL loads successfully with a secure, valid SSL certificate attached (lock icon to the left of your URL) then you’re all set. If you don’t see a secure connection yet, you may need to specifically point your local web server to use the *.pem files generated by mkcert in the previous steps. Apache Open your httpd.conf file and add or update the following to it: * :443> ServerName example.test SSLEngine on SSLCertificateFile "/path/to/example.test+4.pem" SSLCertificateKeyFile "/path/to/example.test+4-key.pem" Restart Apache to load the certificates, then re-check your browser to verify they’re working. You may need to restart your browser to get your site(s) working. nginx Open your nginx.conf file and add or update the following to the http block: ssl_certificate "ssl/example.test+4.pem" ; ssl_certificate_key "ssl/example.test+4-key.pem" ; Restart nginx to load the certificates, then re-check your browser to verify they’re working. You may need to restart your browser to get your site(s) working. How I Use It for Local Development My setup is nginx configured to load the certificate files mkcert outputs, exactly as shown above. Every local project gets a .test domain, and they all share one certificate. When I spin up a new project and need another domain covered, I don’t touch the CA at all. I just re-run mkcert with the full list of hostnames: mkcert host.test host2.test host3.test …point nginx at the newly generated .pem files if the filenames changed, and restart nginx: sudo nginx -s reload That’s the entire workflow. The new certificate is signed by the same local CA my machine already trusts, so the browser accepts it immediately with no warnings and no browser restart. When do you need to re-run mkcert -install? Adding domains never requires it. mkcert -install only puts the CA into your trust stores, and once it’s there, every certificate the CA signs is trusted automatically. You only need to re-run it when the set of trust stores changes or the CA itself does: A new machine , or a fresh OS install, where the CA has never been trusted A new browser or root store appears : you install Firefox (after adding nss ), or set JAVA_HOME for the first time, and that store needs the CA added to it The CA was regenerated : if you delete the mkcert -CAROOT directory or sync dotfiles to a new machine without it, a new CA gets created and must be installed before its certificates are trusted If certificates that used to work suddenly throw authority warnings, re-running mkcert -install is the first thing to try; it’s idempotent and safe to run repeatedly. Related Links mkcert on GitHub Supported root stores (official README) mkcert releases & pre-built binaries Homebrew formula

  • DEV CommunityThe Giantsread at source

    What a killed Playwright script leaves behind on Linux: processes, profile dirs and a SIGTERM that does not exit

    A Playwright script ends in more ways than await browser.close() : it throws, someone calls process.exit() , a container runtime sends SIGTERM, the OOM killer sends SIGKILL. I wanted to know what each of these leaves behind on a Linux host: running Chromium processes, temp directories, or a Node process that ignores the signal. I ran all of it on 2026-11-03: Linux 6.8, Node v24.22.2, Playwright 1.63.0 with the bundled chrome-headless-shell (revision 1243). Everything below is that one machine and one browser build. Containers, systemd, macOS and Windows are not covered. The test setup The child script launches a browser, opens a page, prints READY , and then either waits for a GO line on stdin and ends in a chosen way, or hangs so that the harness can send it a signal. The cdp mode does not launch anything: it attaches to a Chromium that the harness started separately, which is what many browser pools do. // child.cjs [port] const { chromium } = require ( ' playwright ' ); const [ how , action , port ] = process . argv . slice ( 2 ); ( async () => { const browser = how === ' launch ' ? await chromium . launch () : await chromium . connectOverCDP ( `http://127.0.0.1: ${ port } ` ); await ( await browser . newContext ()). newPage (); console . log ( ' READY ' ); if ( action === ' hang ' ) { setInterval (() => {}, 1000 ); return ; } await new Promise ( r => process . stdin . once ( ' data ' , r )); // wait for GO from the harness process . stdin . destroy (); if ( action === ' close ' ) await browser . close (); else if ( action === ' exit ' ) process . exit ( 0 ); else if ( action === ' throw ' ) throw new Error ( ' boom ' ); })(); The harness reads the process tree from /proc while the child is running, ends the child, waits three seconds, and then checks which of those PIDs are still alive (zombies do not count) and whether the browser's profile directory is still on disk. It kills and deletes only what it started. const { spawn } = require ( ' child_process ' ); const fs = require ( ' fs ' ); const { chromium } = require ( ' playwright ' ); const sleep = ms => new Promise ( r => setTimeout ( r , ms )); function descendants ( root ) { // every process below root, from /proc const kids = {}; for ( const d of fs . readdirSync ( ' /proc ' ). filter ( x => /^ \d +$/ . test ( x ))) { try { const st = fs . readFileSync ( `/proc/ ${ d } /stat` , ' utf8 ' ); ( kids [ st . slice ( st . lastIndexOf ( ' ) ' ) + 2 ). split ( ' ' )[ 1 ]] ||= []). push ( + d ); } catch {} } const out = [], stack = [ root ]; while ( stack . length ) { const p = stack . pop (); for ( const k of kids [ p ] || []) { out . push ( k ); stack . push ( k ); } } return out ; } const alive = pid => { // exists and is not a zombie try { const s = fs . readFileSync ( `/proc/ ${ pid } /stat` , ' utf8 ' ); return s . slice ( s . lastIndexOf ( ' ) ' ) + 2 )[ 0 ] !== ' Z ' ; } catch { return false ; } }; const profileOf = pids => { // --user-data-dir of the pids we started for ( const pid of pids ) { try { const m = fs . readFileSync ( `/proc/ ${ pid } /cmdline` , ' utf8 ' ). match ( /--user-data-dir= ([^\0\s] + ) / ); if ( m ) return m [ 1 ]; } catch {} } }; const cases = [ [ ' launch ' , ' close ' ], [ ' launch ' , ' exit ' ], [ ' launch ' , ' throw ' ], [ ' launch ' , ' SIGTERM ' ], [ ' launch ' , ' SIGINT ' ], [ ' launch ' , ' SIGKILL ' ], [ ' cdp ' , ' close ' ], [ ' cdp ' , ' SIGKILL ' ], ]; ( async () => { let port = 9400 ; for ( const [ how , action ] of cases ) { port ++ ; let chrome = null ; if ( how === ' cdp ' ) { // Chromium started by us, Playwright only attaches chrome = spawn ( chromium . executablePath (), [ ' --headless=new ' , ' --no-sandbox ' , `--remote-debugging-port= ${ port } ` , `--user-data-dir=/tmp/probe-profile- ${ port } ` , ' about:blank ' , ], { stdio : ' ignore ' , detached : true }); await sleep ( 2000 ); } const isSignal = action . startsWith ( ' SIG ' ); const child = spawn ( ' node ' , [ ' child.cjs ' , how , isSignal ? ' hang ' : action , String ( port )], { stdio : [ ' pipe ' , ' pipe ' , ' ignore ' ] }); let exited = null , ready = false ; child . on ( ' exit ' , ( c , s ) => { exited = `code= ${ c } sig= ${ s } ` ; }); child . stdout . on ( ' data ' , d => { if ( String ( d ). includes ( ' READY ' )) ready = true ; }); while ( ! ready ) await sleep ( 50 ); await sleep ( 500 ); const procs = how === ' launch ' ? descendants ( child . pid ) : [ chrome . pid , ... descendants ( chrome . pid )]; const profile = profileOf ( procs ); if ( isSignal ) child . kill ( action ); else child . stdin . write ( ' GO \n ' ); const t0 = Date . now (); while ( ! exited && Date . now () - t0 8000 ) await sleep ( 50 ); await sleep ( 3000 ); const left = procs . filter ( alive ); console . log ( ` ${ how . padEnd ( 6 )} ${ action . padEnd ( 8 )} node: ${ String ( exited || ' still running ' ). padEnd ( 20 )} ` + ` chromium procs: ${ String ( procs . length ). padStart ( 2 )} -> ${ String ( left . length ). padStart ( 2 )} ` + ` profile dir left: ${ fs . existsSync ( profile )} ` ); for ( const pid of left ) { try { process . kill ( pid , ' SIGKILL ' ); } catch {} } // clean up only what we started try { child . kill ( ' SIGKILL ' ); } catch {} if ( profile ) fs . rmSync ( profile , { recursive : true , force : true }); await sleep ( 300 ); } })(); Result launch close node: code=0 sig=null chromium procs: 6 -> 0 profile dir left: false launch exit node: code=0 sig=null chromium procs: 6 -> 0 profile dir left: false launch throw node: code=1 sig=null chromium procs: 6 -> 0 profile dir left: false launch SIGTERM node: still running chromium procs: 6 -> 0 profile dir left: false launch SIGINT node: code=130 sig=null chromium procs: 6 -> 0 profile dir left: false launch SIGKILL node: code=null sig=SIGKILL chromium procs: 6 -> 0 profile dir left: true cdp close node: code=0 sig=null chromium procs: 14 -> 10 profile dir left: true cdp SIGKILL node: code=null sig=SIGKILL chromium procs: 14 -> 10 profile dir left: true In the cdp rows the first count was 14 or 15 and the second 10 or 11 across my runs, so read that as "most of the tree stays". The other rows were identical on every run. In the cdp rows the profile directory belongs to the browser the harness started itself, and it is still there because that browser is still running. Three things in that table surprised me. Playwright's own launch cleans up after a crash, even after SIGKILL. throw , exit and SIGKILL all end with zero Chromium processes. In a one-off check (script not included) I printed the command line of the launched browser, and it ends in --remote-debugging-pipe . I did not trace the mechanism, but it fits a browser that exits when the other end of its pipe closes. The cdp rows are the contrast: that browser listens on a port, and nothing ties it to the script. In the same one-off check, chromium.launchServer() also left no processes after SIGKILL, so it is not only launch() . Browsers you did not launch are not cleaned up at all. In the cdp rows, about ten Chromium processes stayed alive after browser.close() and after SIGKILL. This is the case where a leak actually happens. The docs for browser.close() say that for a browser you connected to, it "clears all created contexts belonging to this browser and disconnects from the browser server". It does not stop the browser. Whoever started it owns its lifetime. If you run a pool this way, you need a supervisor that restarts or kills the browser itself, and a periodic check that counts processes. SIGKILL leaves the profile directory. The launch SIGKILL row says profile dir left: true . Each one was about 224 KB in my runs, which is nothing for a single crash and something for a worker that gets OOM-killed a few times a day on a small /tmp . A normal close() , an exception, process.exit() , SIGTERM and SIGINT all removed it. I did not look into which Playwright process does the deleting. Whatever it is, SIGKILL gives it no chance to run. The SIGTERM row launch SIGTERM says node: still running . The child had a setInterval standing in for an HTTP server, and after SIGTERM the browser was gone but Node kept going. The reason is the handleSIGTERM launch option, which defaults to true and is documented as "Close the browser process on SIGTERM". Playwright installs a handler that closes the browser. Node's documentation says that once a SIGTERM or SIGINT listener is installed, the default behaviour (exit with code 128 plus the signal number) is removed. So the server stays up with no browser behind it. SIGINT ended with exit code 130 in my run; I did not look into why it differs from SIGTERM. I did not test what Docker or Kubernetes do from here. The consequence I can state from this measurement is only that a service which does nothing on SIGTERM keeps running with a dead browser, and anything that waits for it to exit has to fall back to SIGKILL. A small fix for both problems This is the service skeleton I used to test two remedies: close the browser and exit on SIGTERM and SIGINT, and sweep profile directories that nothing is using when the service starts. // service.cjs: closes the browser and exits on SIGTERM, sweeps leaked profile dirs at startup const { chromium } = require ( ' playwright ' ); const fs = require ( ' fs ' ); const PREFIX = ' playwright_chromiumdev_profile- ' ; function sweepStaleProfiles ( minAgeMs ) { // a profile dir is stale when no running process names it and it is not brand new const inUse = new Set (); for ( const pid of fs . readdirSync ( ' /proc ' ). filter ( n => /^ \d +$/ . test ( n ))) { try { const m = fs . readFileSync ( `/proc/ ${ pid } /cmdline` , ' utf8 ' ). match ( /--user-data-dir= ([^\0\s] + ) / ); if ( m ) inUse . add ( m [ 1 ]); } catch {} // process vanished or is not readable } let removed = 0 ; for ( const name of fs . readdirSync ( ' /tmp ' ). filter ( n => n . startsWith ( PREFIX ))) { const full = ' /tmp/ ' + name ; if ( inUse . has ( full )) continue ; if ( Date . now () - fs . statSync ( full ). mtimeMs minAgeMs ) continue ; fs . rmSync ( full , { recursive : true , force : true }); removed ++ ; } return removed ; } ( async () => { console . log ( ' swept stale profiles: ' , sweepStaleProfiles ( Number ( process . env . SWEEP_MIN_AGE_MS ?? 60000 ))); const browser = await chromium . launch (); await browser . newPage (); let closing = false ; const shutdown = async signal => { if ( closing ) return ; closing = true ; await browser . close (); // Playwright's own handler closes Chromium but does not end this process console . log ( ' closed on ' , signal ); process . exit ( 0 ); }; process . on ( ' SIGTERM ' , () => shutdown ( ' SIGTERM ' )); process . on ( ' SIGINT ' , () => shutdown ( ' SIGINT ' )); console . log ( ' READY ' ); setInterval (() => {}, 1000 ); // stands in for the HTTP server })(); The sweep treats a directory as stale only if no process on the machine has it in a --user-data-dir argument and it is older than a minute. Chromium rewrites its own argument list with spaces on Linux, which is why the pattern stops at whitespace and not only at the NUL byte. I found that when my first version of the harness reported "profile left: false" for a directory that was plainly still there. To test it I used a second script that is not included: it starts the service, SIGKILLs it, starts it twice more, sends SIGTERM to both, and counts new profile directories under /tmp each time. The sweep deletes everything under that prefix that no process references, so the test script lowered the age limit to five seconds and waited six. Results: service> swept stale profiles: 0 service> READY new profile dirs on disk after SIGKILL: 1 service> swept stale profiles: 1 service> READY service> swept stale profiles: 0 service> READY new profile dirs with two live services: 2 service> closed on SIGTERM second service: exit code 0 after 167 ms service> closed on SIGTERM first service: exit code 0 | new profile dirs left: 0 After SIGKILL one profile directory stayed. The next start swept it ( swept stale profiles: 1 ). Two live services each kept their own directory, so the sweep did not touch a running browser. Both services exited with code 0 on SIGTERM, in 106 to 281 ms across my runs, and no new directories were left. Limits The sweep matches on the playwright_chromiumdev_profile- prefix in /tmp . If other Playwright programs share the host, the "no process names it" test is what protects them, together with the age limit. A browser that is just starting could in principle be missed by both checks. Do not shorten the age limit in production. If you pass your own userDataDir , launch() is not involved and none of this applies; you own that directory. I did not measure memory growth, only whether processes and directories survive. A browser that stays alive and slowly grows is a different problem. For the cdp case there is no fix in Playwright. A process manager that starts the browser and owns its lifetime is the fix, and I did not test one. Sources: Playwright BrowserType launch options for handleSIGTERM and handleSIGINT , the browser.close() text from the Playwright 1.63.0 type definitions, and Node.js process signal events . Written with AI assistance. Every code sample was run on 2026-11-03 with Playwright 1.63.0 (chrome-headless-shell revision 1243) and Node v24.22.2; the output above is from that run.

  • CSS-TricksThe Giantsread at source

    Re-Implementing :nth-child() With Modern CSS

    Combining :nth-child with style queries and sibling-index() to conditionally select child elements. Re-Implementing :nth-child() With Modern CSS originally handwritten and published with love on CSS-Tricks . You should really get the newsletter as well.

  • Hacker News Front PageThe Giantsread at source

    Just a little AI resistance, as a treat

  • Flavio CopesMore Front-end Bloggersread at source

    Give your agent setup a week of ordinary work

    Try a stable agent setup on ordinary work before changing it again. Record finished tasks, review effort, and repeated failures to see what needs improving.

  • Hacker News Front PageThe Giantsread at source

    Microsoft's Canada timezone troubles leave admins fixing calendars

  • Dave RupertTop Front-end Bloggersread at source

    Write mo'

    I decided not to do #NaNoWriMo this year. Part of that is because I’ve never done it. Never even attempted it. Don’t even want to. Writing a novel is a life goal of mine, but cramming it all into one month isn’t for me. November is impossibly busy. But I do think it would be cool to have a big excuse for avoiding all those obligations. “Dave, come to this holiday party!” “Sorry, I’m writing a novel.” “Dave, come work at the school’s fall carnival!” “Sorry, I’m writing a novel.” “Dave, pay attention to your kids!” “Sorry, I’m writing a novel.” See, that level of self-preservation and pre-emptive boxing out of other responsibilities would be so cool. Like cigarettes cool. Despite not having a novel excuse, I am feeling compelled to wri’ mo’ this month. Whether it’s autumnal ghosts or pumpkin spices, there’s a draw pulling me back to my text editor. A vapor of creativity stolen by corporate dementors and their infernal machines, seeping back into my lifeless shell. Last week I powered through a handful of short stories by Arthur C. Clarke. Vintage sci-fi inspires me. Partly because the experimental short form factor containing a single “ What If? ” thought experiment seems achievable and because most of the stories are complete shit. In a modern world of blockbuster movie adaptations and eight volume octologies, vintage sci-fi sets the bar real low. One story I came across was The Nine Billion Names of God . The audiobook has a sick-ass cover of a skeleton in a cracked space helmet on a dying planet with a giant sun rising in the distance. You can pause this and get through it in ten minutes. Nothing in the story relates to that cover. Rather, the story is about two Western men that work for a fancy computer company that makes the Mark V . They’re hired by a group of Tibetan monks to help them write a program that will procedurally generate the nine billion names of God which they believe will bring about the end of the world. The Mark V will shorten the task of writing down all the possible names of God from 15,000 years of manual writing down to 100 days! Incredible productivity gains! AAABBBAAA AAABBBABA AAABBBACA AAABBBADA AAABBBAEA AAABBBAFA AAABBBAGA AAABBBAHA AAABBBAIA AAABBBAJA AAABBBAAB AAABBBABB AAABBBACB AAABBBADB AAABBBAEB AAABBBAFB AAABBBAGB AAABBBAHB AAABBBAIB AAABBBAJB AAABBBAAC AAABBBABC AAABBBACC AAABBBADC AAABBBAEC AAABBBAFC AAABBBAGC AAABBBAHC AAABBBAIC AAABBBAJC AAABBBAAD AAABBBABD AAABBBACD AAABBBADD AAABBBAED AAABBBAFD AAABBBAGD AAABBBAHD AAABBBAID AAABBBAJD AAABBBAAE AAABBBABE AAABBBACE AAABBBADE AAABBBAEE AAABBBAFE AAABBBAGE AAABBBAHE AAABBBAIE AAABBBAJE AAABBBAAF AAABBBABF AAABBBACF AAABBBADF AAABBBAEF AAABBBAFF AAABBBAGF AAABBBAHF AAABBBAIF AAABBBAJF AAABBBAAG AAABBBABG AAABBBACG AAABBBADG AAABBBAEG AAABBBAFG AAABBBAGG AAABBBAHG AAABBBAIG AAABBBAJG ... ... ... ... ... ... ... ... ... ... THIS STORY HAS NO MODERN DAY PARALLELS. An altruistic sex cult from a specific locale using a mountain of money to hasten the end of the world with their enormous computers that endlessly generate combinations of nonsense words that they believe will summon God… well, that would be a work of cheesy science fiction, not real life. The Nine Billion Names of God flipped a switch in me and has renewed my courage to write more. As someone with ideas about ideas burning holes in my head, it’d be worthwhile to offload some of my cognitive surplus. I don’t have any solid plans, words-per-day goals, charts, graphs, storyboards, outlines, or some Benjamin Franklin life hack schedule to make this happen. Instead I have an Obsidian vault with a hundred unsorted drafts. I don’t want commit myself to publishing every day, but my goal is to get up and spend thirty minutes to an hour each day writing something . I want to carve out time before work, before doing my puzzles, before doom-scrolling the news. I want to use those premium dopamine reserves –the flavorful floating creme of consciousness that comes from a good night’s sleep, not the metallic bitter dregs distilled from caffeine and stimulants– I want to savor and use that precious natural dopamine on writing. My promise to you, dear reader, is that I will write more this month. I will write no less than nine billion stories and/or blog posts by the end of November. And upon completing my works, I can shed this mortal coil and leave this forsaken world.

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Rigorix, Execution governance for AI agents, open-source engine

    Comments

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Diogenes – Account-less MCP Gateway to 45 Gemini models and guardrails

    Comments

  • Hacker News Front PageThe Giantsread at source

    Anthropic's IPO Filing Warns It May Be 'Imminently' Close to RSI

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Open-Source Searchable Job Board for HN's Hiring Threads

    Comments

  • Hacker News Front PageThe Giantsread at source

    Show HN: Kvittas, an Expense Splitting App

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Kvittas, an Expense Splitting App

    Comments

  • freeCodeCamp.orgYouTube ChannelsVideoWatch on YouTube

    Vector Databases Full Course for Beginners – Embeddings, Similarity Search & ChromaDB

  • Hacker News Front PageThe Giantsread at source

    Show HN: Bema: Work messaging built for humans

  • Hacker News: Show HNLibraries, Frameworks, etc.read at source

    Show HN: Bema: Work messaging built for humans

    Comments

  • Flavio CopesMore Front-end Bloggersread at source

    Tailscale alternatives for your own servers

    Tailscale alternatives for a VPS: November 2026 prices, the SSH fixes, and when WireGuard, Headscale, or Cloudflare Access fits.

  • Smashing MagazineThe Giants12 min read

    Modern CSS Might Make Your Front-End More Complex

    Replacing JavaScript with a modern CSS feature can simplify a piece of code while making the wider application harder to reason about. Gabriel Shoyombo explores what happens when logic is split between CSS and JavaScript, sharing practical guidance to help you keep complexity in check during migration.

  • AdactioTop Front-end Bloggersread at source

    Bad AI doesn’t smell | Clearleft

    The best way to avoid AI mistakes is to run research the old-fashioned way; get out there and run research in the field and complete your own analysis of the findings. It may take a little longer, but truly valuable research is worth the effort. adactio.com/links/22825

  • Flavio CopesMore Front-end Bloggersread at source

    How to move a DigitalOcean Droplet from Ubuntu to Debian

    Move a DigitalOcean Droplet from Ubuntu to Debian: new server, copy the data, then switch the reserved IP. In-place conversion is unsupported.

  • Net NinjaYouTube ChannelsVideoWatch on YouTube

    GitHub Copilot App Tutorial #7 - MCP Servers & Skills

  • SitePointThe Giantsread at source

    How to Learn Java with AI: A Practical Guide for Beginners

    null Continue reading How to Learn Java with AI: A Practical Guide for Beginners on SitePoint .

  • HTML All The ThingsPodcastsAudioListen

    What Does Your Small Business Website Actually Need?

    Does your small business website really need a slider, testimonials, a blog, pricing tables, and every other feature you've seen on successful websites? Not necessarily. Websites are a lot like LEGO. They're made up of individual building blocks that can be arranged in countless…

  • HeyDesignerDeveloper/Designer Newsread at source

    The new full-stack designer (video)

    Design will never be the same (video), The era of personal software, Making features generic in CSS.

  • SitePointThe Giantsread at source

    Retrying a send is harder than sending

    null Continue reading Retrying a send is harder than sending on SitePoint .

  • AbduzeedoMulti Author Blogsread at source

    Web Design: Grids Season 03 by Obys Agency

    Obys Agency advances educational web design with Grids Season 03, an interactive study exploring spatial tension and layout typography. Most educational websites about graphic design preach strict gri...

Monday, 2 November 2026

  • AbduzeedoMulti Author Blogsread at source

    Momotaro Coffee: Small-Batch Packaging Design System

    Pomodoro Design crafts modular packaging design for Bucharest roaster Momotaro Coffee using recycled paper sleeves and warm serif type. Micro-lot coffee roasting moves way too fast for traditional pri...

  • Jens Oliver MeiertTop Front-end Bloggersread at source

    How to Upgrade From HTML Minifier 4 to 5

    A checklist to work through the breaking changes when updating from HTML Minifier 4.0.0, released in 2019.

  • Jens Oliver MeiertTop Front-end Bloggersread at source

    How to Switch From HTML Minifier Next 9 to HTML Minifier 5

    A checklist to move from HTML Minifier Next to HTML Minifier—now maintained again—, which is mostly a matter of renaming.

  • AbduzeedoMulti Author Blogsread at source

    Product Design: COLLECTIBLE NYC Opens This Week Uptown

    Opening this week at 210 East 86th Street, COLLECTIBLE New York 2026 showcases boundary-pushing 21st-century product design and furniture. If you are anywhere near Manhattan over the next few days, cl...

  • Microsoft Edge BlogBrowsers, engines, etc.3 min read

    Client-side threat detection: how Scareware blocker uncovered malicious notification abuse

    Website notifications were designed to help us stay informed. They can alert you to a new message, remind you of an upcoming event, or share an important update from a service you trust. But scammers are turning this useful browser feature into a pe The post Client-side threat detection: how Scareware blocker uncovered malicious notification abuse appeared first on Microsoft Edge Blog .

  • Level AccessCompany/Startup Blogs6 min read

    Why Accessibility Belongs in Your CI/CD Pipeline and IDE

    By building accessibility into your CI/CD pipeline and IDE, you can find and fix issues before code merges or ships. Level CI with The post Why Accessibility Belongs in Your CI/CD Pipeline and IDE appeared first on Level Access .

  • SitePointThe Giantsread at source

    AI-Powered Pentesting for DevSecOps: 7 Checks Developers Should Make First

    null Continue reading AI-Powered Pentesting for DevSecOps: 7 Checks Developers Should Make First on SitePoint .

  • AbduzeedoMulti Author Blogsread at source

    Alianza Rebrand Is a Masterclass in Brand Identity by Mucho

    Mucho gives the Alianza rebrand an open A: a historic Spanish publisher, recast as a cultural brand in bold color and book design. The first thing you notice is how little the system needs to say. A s...

  • Level AccessCompany/Startup Blogs15 min read

    Can AI Make a Website WCAG Conformant? A Comprehensive Guide

    AI can’t make a website WCAG conformant alone. Properly skilled AI can find, fix, and prevent many violations, but validating conformance requires human The post Can AI Make a Website WCAG Conformant? A Comprehensive Guide appeared first on Level Access .

  • Auth0 BlogCompany/Startup Blogsread at source

    Build an AI Wellbeing App with Fireworks and Fitbit via Auth0 Token Vault.

    Build Daily Wellbeing, a Next.js app that connects Fitbit through Auth0 Token Vault, compares your daily activity and sleep with your averages, and uses Fireworks to suggest what to try today.

  • AbduzeedoMulti Author Blogsread at source

    Packaging Design: LeNOSE Fine Fragrance Hand Soap Identity

    Explore LeNOSE's witty packaging design and fine fragrance development, pairing geometric die-cut labels with $10 luxury hand soaps. Somewhere over the last decade, washing your hands turned into a bi...

  • AbduzeedoMulti Author Blogsread at source

    Editorial Design: Merch by Counter-Print

    Counter-Print champions physical branding with Merch, an editorial design survey exploring the strategic power of tactile brand collateral. For decades, branded merchandise occupied a strange, slightl...

  • CodePenCompany/Startup Blogs2 min read

    Chris’ Corner: Doin’ Stuff in CSS

    The float property in CSS is old school. CSS developers used to have to think about it all the time, because it was one of the mechanisms we had for building actual layouts like side-by-side columns and stuff. Those days are gone, but it doesn’t mean float as a property is entirely dead. It remains […]

  • Flavio CopesMore Front-end Bloggersread at source

    How to move a Deno app to Cloudflare Workers

    Keep Deno for development while moving execution to Workers. Build a SQLite-backed Durable Object queue with alarms, retries, and safe migration steps.

  • AdactioTop Front-end Bloggersread at source

    Twenty years ago today, I signed up for Twitter. Ten years ago, I wrote about the first ten years: https://adactio.com/journal/11436 I can’t say I miss it.

  • Rachel AndrewTop Front-end Bloggers6 min read

    Making features generic in CSS

    CSS has two new positional alignment keywords as part of CSS Box Alignment Level 3: flow-start and flow-end. These are defined as aliases for the existing flex-start and flex-end keywords. Therefore, in a supporting browser, the following two lines should align flex items to the end of the flex container (the right assuming you are ... Making features generic in CSS

  • AbduzeedoMulti Author Blogsread at source

    Branding: The White & Case rebrand by Uncommon

    Uncommon gives the White & Case rebrand its energy: carved Roman letterforms, kinetic serif headlines, and electric Catalyst Blue. Law firm branding usually plays it safe: sober serif, navy blue, ...

  • Flavio CopesMore Front-end Bloggersread at source

    Use different models for planning and coding

    Set up a planner and a DeepSeek executor in OpenCode, keep their permissions separate, and compare the cost of accepted changes rather than tokens.

  • CSS-TricksThe Giantsread at source

    What’s !important #20: GTA 6 Scroll Effects, Scrollspies, random()

    This is the final issue of What’s !important on CSS-Tricks.com. The good news is that it’s coming back in an expanded form as the CSS-Tricks newsletter. I had ya there for a second, didn’t I? Sorry, I couldn’t decide whether to trick or treat, so I went for both. Happy Halloween What’s !important #20: GTA 6 Scroll Effects, Scrollspies, random() originally handwritten and published with love on CSS-Tricks . You should really get the newsletter as well.

  • AbduzeedoMulti Author Blogsread at source

    Typography: 3D Embossed Terrazzo by John Kappa

    John Kappa explores dimensional typography in Cinema 4D, crafting subtle embossed letterforms directly within polished terrazzo materials. Most digital letterforms rely on crisp fill colors or high-co...

  • AdactioTop Front-end Bloggersread at source

    Palaces Of The Crow by Ray Nayler

    I really liked Ray Nayler’s The Mountain In The Sea . I also liked The Tusks Of Extinction . But reading those back-to-back gave me pause: At times it seems to relish in describing the worst that humanity is capable of. It starts to veer into misanthropy. I said: I hope that the next Ray Nayler book I read won’t tip over into wallowing in the worst of our species. Well, when I cracked open Palaces Of The Crow , my mind was immediately put at ease. And I mean immediately. Before the story even begins, the book opens with two quotes. One is the closing passage of Darwin’s On The Origin Of Species describing “the war of nature”, but the other quote is from Kropotkin: Mutual aid is as much a law of nature as mutual struggle. That sums up the themes of Palaces Of The Crow . On the one hand, we do indeed get the worst that humans are capable of. But on the other hand, we also get not only the best that humans are capable of, but non-humans too. As with The Mountain In The Sea , this is a terrestrial first-contact story. We’ve gone from cephalopods to corvids. And we’ve gone from the near future back to Eastern Europe during the Second World War. I’m always nervous of any piece of popular entertainment that attempts to feature the Holocaust, but I feel that here it’s done with respect. It never comes across as gratuitous. There are four main human characters; a Polish boy in the Soviet army, a Lithuanian Jewish girl, a young boy who never speaks, and a Roma girl. The narrative is set almost entirely within a forest. At times this book reminded me of The Vaster Wilds by Lauren Groff . Both stories feature a young woman trying to survive in the wilderness. But whereas The Vaster Wilds is ultimately a tale of the individual, Palaces Of The Crow is very much a story of the group. And whether that group comprises humans, crows, or both, the message is clear: Social animals are built to survive only among one another. There is a cautionary subplot involving another denizen of the forest who goes it alone. His fate is very different to that of the group. A story of four youngsters in a forest during World War II sounds like it should be a brutal survivalist tale. But Palaces Of The Crow has more in common with Rebecca Solnit’s A Paradise Built In Hell and Rutger Bregman’s Humankind . Towards the end, the text sometimes can’t resist saying the subtext out loud, but this is a minor quibble with what is otherwise a beautifully constructed work. I’m not going to lie, Palaces Of The Crow is absolutely devastating in places. But it’s also life-affirming. I know that sounds trite, but this book is the opposite of trite. It’s visceral and unflinching and beautiful.

  • Flavio CopesMore Front-end Bloggersread at source

    What changes when an agent improves its own harness?

    Use Google Research's Harness² to understand the pieces around an AI model, how recursive refinement works, and how to evaluate changes on untouched tasks.

  • Frontend Masters BlogMore Front-end Bloggers5 min read

    Overriding Heading Levels With the headingoffset HTML Attribute

    The new headingoffset HTML attribute lets you bump a heading’s level up by adding to it, which is handy when a component’s h1 needs to be an h2 somewhere else. Here’s how the math works, how to reset it, how to style the result with :heading(), and where aria-level fits in.

  • SyntaxPodcastsAudioListen

    1052: Github Universe Keynote Recap

    Scott, Wes, and CJ report live from GitHub Universe in San Francisco, breaking down GitHub’s re-architecture for agent-scale traffic, the GitHub Copilot app, Copilot Fusion, Projects, Automations, and AI push protection. Plus hacked conference badges, a USB-C lanyard, and the…

  • Flavio CopesMore Front-end Bloggersread at source

    Write agent instructions that explain the decision

    Turn a brittle AGENTS.md into useful project guidance. Explain the reason behind a rule, keep hard checks in code, and make approval boundaries explicit.

  • Net NinjaYouTube ChannelsVideoWatch on YouTube

    GitHub Copilot App Tutorial #6 - Parallel Sessions

  • HeyDesignerDeveloper/Designer Newsread at source

    Design is more than creation

    The role of UX in agentic software factories, What does design system AI readiness mean, anyway?, Stageshow.

  • Flavio CopesMore Front-end Bloggersread at source

    CVE-2026-98432: check your Linux server and close public ports

    CVE-2026-98432 is a Linux IPv4 TCP use-after-free. Check your kernel, treat disabling MPTCP as a stop gap, then patch and close every public port on the VPS.

  • SitePointThe Giantsread at source

    How Product Teams Can Use AI User Research to Test Assumptions Before Sprint Planning

    null Continue reading How Product Teams Can Use AI User Research to Test Assumptions Before Sprint Planning on SitePoint .

See everything from the last 30 days →

Articles belong to their publishers. This site only collects what their feeds provide.

Updated 3 November 2026 at 14:56