Cover illustration

TheDaily Front

Issue No. 4 Tuesday, July 14 2026 #4 — TUESDAY, JULY 14, 2026
All the news that fits in a browser tab, whether the app store likes it or not.
Tuesday, July 14, 2026 The Daily Front No. 4 — Contents
30stories
9,654points
6,310comments
281kllm tokens
Assembled with 33 model calls — 209,159 tokens read, 72,300 written.

Highlights

Your 'app' could have been a webpage (so I fixed it for you)

A top-ranked act of civic web repair asks why a travel itinerary ever needed to be an app in the first place.

Bonsai 27B: A 27B-Class model that runs on a phone

Bonsai 27B claims a new frontier for local AI: a serious multimodal model running on a phone.

Cursor 0day: When Full Disclosure Becomes the Only Protection Left

A Cursor vulnerability story turns full disclosure into a debate over trust, tooling, and what IDEs should ever execute.

Australian energy retailers must offer three hours of free daytime electricity

Australia’s coming three-hour free-electricity window shows what happens when abundant midday solar finally reaches the bill.

Our Amish Language

A beautifully observed essay on Pennsylvania Dutch treats a fading Amish language as both inheritance and alarm bell.

From the Editor

Today’s front page finds our readers in a fighting mood: against needless apps, overtrained machine prose, opaque coding tools, and the quiet surrender of judgment to agents. Yet there is industry here too — pocket-sized models, old languages preserved, new maps of brains and worlds — proof that the machine age still leaves room for human stubbornness.

  1. Your 'app' could have been a webpage (so I fixed it for you)3
  2. Bonsai 27B: A 27B-Class model that runs on a phone4
  3. Are we offloading too much of our thinking to AI?5
  4. The Tower Keeps Rising6
  5. Cursor 0day: When Full Disclosure Becomes the Only Protection Left7
  6. Show HN: Juggler – an open-source GUI coding agent, by the creator of JUCE8
  7. How to stop Claude from saying load-bearing9
  8. Launch HN: Agnost AI (YC S26) – Extract user feedback from agent conversations9
  9. The Economics of Recursive Self-Improvement [pdf]9
  10. Punch yourself in the face with reality10
  11. The zero-cost fallacy: open-source software in the agentic era11
  12. The git history command12
  13. How I use HTMX with Go13
  14. European "age verification" "app" forcing everyone to use Android or iOS14
  15. Dependabot version updates introduce default package cooldown14
  16. The infinite scroll may become endangered if controversial Calif. law passes14
  17. Measuring Input Latency on Linux: X11 vs. Wayland, VRR, and DXVK15
  18. I'm a USB-C Maximalist16
  19. Australian energy retailers must offer three hours of free daytime electricity17
  20. Japan develops a method to recover up to 90% of lithium from used EV batteries18
  21. Fundamentals of Wireless Communication (2005)18
  22. Indian scientists produce most detailed 3D atlas of the human brainstem18
  23. Satellite Tracker – Live Map of Starlink and 30k Satellites18
  24. Show HN: Opening lines of famous literary works18
  25. The largest available Minecraft world, totalling 15 TB19
  26. The kids with phones are alright20
  27. An Englishwoman who sketched India before photography took hold21
  28. Mathematical texts from a Maya site in Guatemala identify an ancient astronomer22
  29. Our Amish Language23
  30. The Estranged Worlds of J. G. Ballard24
The Daily Front Page 2 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — The Web Strikes Back
article

Your 'app' could have been a webpage (so I fixed it for you)

by MrVandemar·▲ 854 points·517 comments·danq.me ↗
This should have been a webpage.

Why is this an “app”?

This summer, the kids’ performing arts school are singing and dancing in a show at Disneyland. We’re all very excited, but my excitement, at least, was muted a little when I was told to install the “Travelbound” app in order to get access to the itinerary, travel arrangements, and accommodation details.

Fuck that noise. This should have been a webpage. Why do you want me to install a(nother) shitty app just to tell me something that could have been a (smaller, faster, more universally-accessible) document?

Screenshot of an Android app showing a summary itinerary: times for a 'ferry crossing', 'check in at your hotel', 'coach driver accommodation', 'disney's hotel cheyenne', and 'welcome gala', each with an attractive but generic photo.

I’m not remotely exaggerating. This app is literally text, images, and links to PDF files, delivered via the Web.

There only seem to be two things that this “app” does, that a webpage might not have, and they’re both anti-features:

  1. It reports tracking data associated with your Google Account back to the developers.
  2. It shows you advertisements (which they call “inspirations”) for other trips organised by the same agency.

Fuck. Everything. About. That.

A webpage would have been so much better. Unlike this app, a webpage can be…1

  • Copy-pastable
  • Printable
  • Saveable
  • Bookmarkable
  • Searchable
  • Usable on virtually any device
  • (Potentially) more-accessible

I’m annoyed enough… that I’m going to “fix” this app. Hold my beer.

Intercepting app traffic

It’s been a while since the last time I reverse-engineered an Android app from its network traffic, so I had to brush-up on the best way. Here’s what I ended up doing.

  1. Created a new virtual device in Android Studio’s Virtual Device Manager.
  2. Tested adb shell was working and used rootAVD to root it: ./rootAVD.sh system-images/android-33/google_apis_playstore/x86_64/ramdisk.img.2
  3. Performed a cold boot, ran Magisk, and tweaked its settings to automatically grant su access to any app that asked.3

Screenshot showing the HTTP Toolkit application running on both MacOS and an emulated Android mobile, with User Trust and System Trust enabled.

All your traffic are belong to me. At this point, it’s just like running Wireshark or TCPdump.

  1. Ran HTTP Toolkit and told it to intercept AVD traffic. It installed a (fake) VPN provider, routing the phone’s traffic through the proxy.4
  2. Installed the Travelbound app from the Play Store.
  3. Configured HTTP Toolkit to proxy only the Travelbound app (more signal, less noise).

With only a couple of minutes experimentation I discovered that the app works by concatenating the username and password5 and using it in a URL of the form:

https://travelbound.api.vamoos.com/api/itineraries/{username}-{password}

This returns a pile of JSON which, with a little interpretation, can be seen to represent all of the content the app “shows”. E.g., there’s:

  • an array containing each leg of the itinerary,
  • an array containing all of the “inspirations” advertisements to show you,
  • a cross-referenced array containing all of the files (images etc.) that are referenced by the other sections, etc.

Firefox showing a JSON document, focussing on a section about 'Your Ferry Crossing' with some accompanying HTML.

They’re clearly producing HTML code anyway… so again, I ask: why isn’t this a webpage?

A little experimentation showed me that the S3 image URLs were being delivered with moderately-short expiration times, so the JSON needs re-fetching periodically even if the content hasn’t been changed.6

Turning it into something better

Now I had everything I needed to make something… better. I wrote a Ruby script that runs on a Cron schedule to pull the latest JSON and use it to build a HTML page.

I chose to have it completely skip over the “inspirations” (“overlayRows” in the data schema) and just list:

  1. the items from the itinerary and
  2. all of the files not referenced by the inspirations nor itinerary, (a lazy way to collate the PDF download links).

Then I hosted the page, protected by a password: the same one my tour group were given in the first place. I included the raw JSON it used in <details> elements so it can be checked if e.g. there are bits of the schema I didn’t see but that might appear later.

Screenshot of a simply-styled web page showing the same information about the ferry, along with a photo from its deck.

My web page isn’t as “pretty” as the app from which it “borrows” its information. But it’s a fraction of the size and gets all of the Web’s standard features for free.

Some people like an “app”, and that’s… fine, I guess. But some apps could have been a webpage. And especially where, like this one, the content they deliver is already written in HTML and delivered over HTTP… they should be a webpage, right?

I can’t understand how we got to this place with “app culture”! Software companies are happy to make their lives harder (and more expensive: deploying to the big app stores isn’t free!), in order to deliver HTML content to fewer people and with fewer features7 than if they just published directly to the Web in the first place!

There are (some) tasks for which an “app” is absolutely the right choice of medium. Travelbound is not one of them.

But at least I (and the rest of our group, whom I’ve shared it with) now get the choice about how we access this content. Either a 43MB app (ballooning to 124MB when it’s finished downloading extra content) with tracking and advertisements… or a 0.05MB web page (with an optional extra 35MB of images) that provides more features and works on more devices. I know which one I’ll be using!

Footnotes

1 And these are just the features that everybody can get behind. The webpage I ultimately ended up making to replace the app also has some user-friendly/developer-hostile features, like the fact that it removes the tracking code and doesn’t show advertisements.

2 You need to root the device in order to force applications that use Certificate Pinning to trust your man-in-the-middle proxy server. Without this, some applications – including the one I wanted to reverse-engineer – will recognise your self-signed TLS certificate as invalid and refuse to communicate.

3 Without changing this setting in Magisk, I found that HTTP Toolkit would request su access but not wait for the response, and go on to run in unprivileged mode before I had a chance to grant it!

4 Owing to Android security considerations I needed to manually install the root CA certificate it installed for me, but the instructions “just worked”.

5 The username and password is shared by an entire tour group. I’m guessing they don’t have a plan for if some credentials get leaked? Or possibly they consider all of the data they hold to be low-sensitivity enough that it doesn’t matter if it does… in which case I return to my original point: why the hell wasn’t it just a webpage in the first place?

6 Or else the images need caching locally, which seems to be what the app does, in the bloatiest possible way.

7 And, often, with worse accessibility. I’ve not audited the accessibility of this app, but there are things about it that suggest that it’d be harder to use using accessibility technologies than my plain, simple Web version.

The Daily Front Page 3 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Pocket Intelligence
article

Bonsai 27B: A 27B-Class model that runs on a phone

by xenova·▲ 673 points·240 comments·prismml.com ↗
The first model of its capability class to run on a phone.

Today, we're announcing Bonsai 27B, based on Qwen3.6 27B, the new multimodal flagship of the Bonsai family and the first model of its capability class to run on a phone.

Our earlier releases proved that models with 1-bit and ternary weights could produce commercially useful language models. Bonsai 27B extends that frontier to a new capability tier: multi-step reasoning, structured tool calls, vision tasks, and computer-use agentic loops that stay coherent across many steps. Until today, deploying that tier locally has been impractical for a concrete reason: a 27B model occupies roughly 54GB in 16-bit precision, and even a good 4-bit build, at 18GB, is too large for a phone and for most laptops.

Bonsai 27B changes that. It comes in two variants:

Ternary Bonsai 27B uses ternary {−1, 0, +1} weights with FP16 group-wise scaling, giving a true 1.71 effective bits per weight. At 5.9 GB, it is the quality-oriented variant: it runs on an everyday laptop with the full reasoning, tool-calling, and agentic capability.

1-bit Bonsai 27B uses binary {−1, +1} weights with the same group-wise scaling, giving 1.125 effective bits per weight. At 3.9 GB, it is the footprint-oriented variant, which fits within the memory budget of an iPhone 17 Pro, bringing a 27B-class model onto a phone for the first time.

As with every Bonsai release, the low-bit representation runs end to end across the language network, embeddings, attention, MLPs, and the LM head, with no higher-precision escape hatches. Both variants are multimodal, with the vision tower shipping in a compact 4-bit form so on-device workflows can see screenshots, documents, and camera input, not just text. Bonsai 27B carries a full 262K-token context, and supports speculative-decoding, compounding the speed with lossless draft-and-verify acceleration. Everything is available today under the Apache 2.0 License.

Retaining the intelligence

Across a 15-benchmark suite spanning knowledge, reasoning, math, coding, instruction following, tool calling, and vision  (evaluated in thinking mode, where the model's full reasoning is exercised) Ternary Bonsai 27B retains 95% of the full-precision baseline, and 1-bit Bonsai 27B retains 90%.

Category (benchmarks) Qwen 3.6 27B Ternary Bonsai 27B 1-bit Bonsai 27B
Math (GSM8K, MATH-500, AIME25, AIME26) 95.3 93.4 91.7
Coding (HumanEval+, MBPP+, LiveCodeBench) 88.7 86.0 81.9
Agentic and Tool-calling (BFCL v3, TauBench) 80.0 74.0 66.0
Instruction following (IFEval, IFBench) 78.4 71.8 65.8
Knowledge / STEM (MMLU-Redux, MuSR) 83.1 77.0 73.4
Vision (MMMU Pro, OCRBench) 72.6 65.2 59.6
Overall (15 benchmarks) 85.0 80.5 76.1

‍*Fig I: Benchmark scores of Bonsai 27B (thinking mode) against the full-precision baseline. Full per-benchmark results are in the whitepaper.*‍

Read the table by capability and the story is sharper than the averages: math and coding are nearly untouched, tool calling stays within a few points of full precision - exactly the capabilities that agentic workloads depend on. For comparison, the most aggressive conventional low-bit build of the same base model scores significantly lower than 1-bit Bonsai 27B while occupying 2.5x more memory.

This is the same Pareto shift we demonstrated with our earlier language and image models, now at 27B scale: 27B-class capability at a footprint smaller than a full-precision 2B model. By intelligence density — the measure we introduced with 1-bit Bonsai 8B — 1-bit Bonsai 27B delivers 0.53 per GB: more than 10x the full-precision baseline, and roughly 2.7x the best low-bit alternative available.

Fig II: Intelligence density (per GB) of Bonsai 27B compared to other models in the same parameter class.

Why this is an important paradigm shift

The most valuable AI workloads are shifting from single responses to sustained work: assistants that operate real tools, workflows that run unattended before returning a result, and research that synthesizes dozens of documents. This shift changes the shape of the workload — an agent doesn't make one model call, it makes hundreds, each one carrying context, producing structured output, and feeding the next.

Cloud APIs will remain the right choice for many products. But for agentic workloads, cloud-only execution imposes structural constraints: every step is a remote request, per-token cost accumulates with every iteration, and every plan, tool call, and intermediate result crosses the network including the user's private files, screen, and data.

Carousel I: End-to-end agentic workflow with Hermes, powered by our Ternary Bonsai 27B model on NVIDIA GeForce RTX 5090.

Carousel II: Agentic tool calling and MCP integration with Ternary Bonsai 27B on M5 Max.

Local execution changes the equation. When a model capable of sustained agentic work fits on the device, the agent can live inside the product: the marginal cost of a hundred-step loop is zero, and the user's data never leaves the machine. Entire categories open up — persistent on-device agents, assistants that work offline, assistants that reason over private local data by construction. What has been missing is a model small enough to deploy this way and capable enough to trust with the work. Bonsai 27B is that model.

It also unlocks a new system architecture: hybrid deployments that route non-frontier and privacy-sensitive tasks to a capable local model and reserve frontier cloud models for the hardest steps — collapsing the cost-per-task of agentic systems.

Bonsai 27B reaches up to 163 tok/s in 1-bit and 134 tok/s in Ternary on an NVIDIA GeForce RTX 5090. On an M5 Max, it reaches up to 87 tok/s in 1-bit and 58 tok/s in Ternary.

Fitting a phone is a stricter gate than storage numbers suggest. A phone never exposes its full memory to an app - a 12 GB iPhone offers about 6 GB for the model to use on-device, and the model shares that budget with its KV cache and activations. No conventional build of a 27B model comes close to clearing it. At about 4 GB, 1-bit Bonsai 27B is the first to pass through with room to work.

That constraint is why the family ships two deliberate operating points, specifically keeping that in mind: ternary for laptop-class quality, 1-bit for phone-class footprint.

Demo II: Multimodal agentic use-cases powered by 1-Bit Bonsai 27B on an iPhone 17 Pro Max (Demo Mode: Cached & Prefilled Image Context)

The frontier keeps moving

Every Bonsai release has moved the intelligence-per-gigabyte frontier left, and Bonsai 27B moves it past a practical threshold: the full capability set of a modern model with thinking, multimodal understanding, vision, reliable tool use, now fits on the devices people already own.

We believe intelligence density will be one of the defining axes of the next stage of AI progress. Raw capability determines what a model can do; density determines where it can do it. Every leftward shift of the frontier expands the set of devices, products, and environments where advanced AI can operate and changes the economics of every deployment surface it touches, from phones to single-GPU serving. The methodology behind Bonsai is architecture-agnostic, and the frontier will keep moving: larger models and new architectures are already in progress.

Early computers filled rooms; today they live in our pockets. Intelligence is making the same journey, and Bonsai 27B is its largest step yet.

Platform Coverage

Bonsai 27B runs natively on Apple devices (Mac, iPhone, iPad) via MLX and on NVIDIA GPUs via CUDA, through custom low-bit kernels built for its hybrid-attention architecture. Model weights are available today under the Apache 2.0 License. With this release, we’re offering a free, limited-time developer preview API so developers can easily try our model.

Full technical details of our compression, evaluation, and benchmarking processes are available in our whitepaper.

Join Us

PrismML emerged from a team of Caltech researchers and was founded with support from Khosla Ventures, Cerberus, and Google, with continuing support from Samsung. We've spent years tackling one of the field's hardest problems: compressing neural networks without sacrificing their reasoning ability.

If you want to help build the next generation of state-of-the-art AI, we'd love to hear from you. Check out our careers page.

The Daily Front Page 4 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Thinking for Ourselves
article

Are we offloading too much of our thinking to AI?

by yenniejun111·▲ 509 points·462 comments·artfish.ai ↗
What are we automating? Human work or human agency?

Reflections on autonomy and the value of thinking for ourselves

My notes for this essay, written on a plane with no internet and no AI :D

I have been observing, in myself and in those around me, a tendency to increasingly offload our thinking to AI. From trivial decisions to complex thinking, it is easy, convenient, and in some cases, encouraged, to use AI for researching, reasoning, and answering our every query.

I recently read “The Perfect Match” by Ken Liu, a 2012 short story which describes this phenomenon with unexpected accuracy. In the story, a universal AI assistant named Tilly serves users by offering useful and enjoyable recommendations. The main character asks Tilly questions like “What do you recommend I do for breakfast this morning?” and defers to Tilly to find him a suitable person to go on a date with. The main character does not know what he wants to eat for breakfast, what music he would like to listen to, nor what to say on his date. “Who knows your tastes and moods better than I?” quips Tilly in an affectionate voice.


My friend recently went to a San Francisco startup event, where he encountered a man with a small device pinned to his shirt. The device was a sleek little capsule of polished metal, no more than two fingers wide. My friend asked about the device, and the man said it was a microphone which he used to record all of his conversations. At the end of the day, Microphone Man would kick off a workflow to summarize and analyze all of the conversations. He said, with the enthusiasm of a tech bro unveiling his latest setup, “I think Claude Fable is smarter than me. It’s better at critical thinking than I am, so I let Fable do all of my thinking these days.” (Side note: his startup is replacing human engineers by capturing their every input and operation, but without their explicit consent. He has offloaded his own thinking to AI, and made a business of offloading everyone else’s.)

An AI-generated image of Microphone Man based on the description from my friend + my imagination.

Before Claude, ChatGPT, and Gemini became household names, we were already offloading parts of our thinking to search engines. But search still required us to break down a question, evaluate sources, and synthesize an answer. AI increasingly performs those intermediate steps for us, producing a finished response to even complex or esoteric questions in minutes.

Tools like Google Deep Research and OpenAI Deep Research can now do work that might once have taken a single human being, minutes, hours, or days (see METR’s Task-Completion Time Horizons of Frontier AI Models). It saves you time, and it saves you thinking.

The duration at which an LLM is predicted to succeed at different software tasks 50% of the time, from METR’s Task-Completion Time Horizons of Frontier AI Models.

But it is a fine line between having an assistant that helps with your tasks, and losing all of your autonomy. Perhaps the question to ask is: who is making all of the final decisions for the things that really matter to you in your life?

In Ken Liu’s story, the main character believes that the algorithm knows him better than himself: “Everything Tilly suggests to me has been scientifically proven to fit my taste profile, to be something I’d like … What’s wrong with listening to Tilly so that the perfect product finds the perfect consumer, the perfect girl finds the perfect boy?” He defers all decisions, as trivial as what to wear and as important as how to find love, to his assistant. The Microphone Man, similarly, defers all higher-level thinking to Claude, which he believes is smarter than he is in all respects.


The offloading of thinking to AI creeps into my life, too.

There will always be some tradeoff between slow thinking and quick answers. Many questions merit quick answers (What is the weather now? Who was the president of XYZ country 10 years ago? What are the reviews for XYZ brand of skincare or sports equipment?). Many others, I think, would merit longer thinking.

Sometimes, I go on walks around my neighborhood without my phone. Invariably, questions pop into my head, questions I am so used to looking up immediately on my phone (Do cherries grow on trees or bushes? When and where was the first World Cup game?), but I find that I forget most of them by the time I get home — I remember the important few, and I assume the rest were insignificant enough to forget. Maybe there is some value in our lives to forgetting the trivial, to not having an immediate answer to every query that appears in our minds.

A few months ago, I was traveling in Portugal with my sister. After walking around the Monument to the Discoveries, which celebrates Portugal’s “Age of Exploration”, we got the feeling that Portugal seemed to idolize these “discoverers” and “explorers” whereas in the US, we would call them “conquerors” and “colonizers”. I asked our tour guide if Henry the Navigator or any of these men were cancelled, in the way that Christopher Columbus is very cancelled in the US. She responded that they were not, and in fact, men like Henry the Navigator were generally regarded as admired historical figures.

Monument to the Discoveries, which is a lot taller but didn’t fit into my camera. Totally unrelated to this article, but the guy on the farthest left looks a lot like Lord Farquaad.

My sister wondered why Portugal seemed so proud of their colonial history and why their response to colonialism seemed so different from how the US currently talked about and treated its own history of colonialism. “Let’s ask ChatGPT,” she said, pulling out her phone.

I suggested (with only a little bit of initial resistance) that we pause and think about why this might be. I suggested a few theories. Perhaps it was Portugal’s relative homogeneity and religiousness, compared to the US’s diversity of immigrants. Perhaps Portugal clung on to so-called “Age of Exploration” as one of the most prominent chapters in its national story. We wondered, postulated, made wild guesses, backtracked, connected our ideas, disagreed, and remembered historical details we learned in high school many years ago. We drew on our collective memories, knowledge, understanding of the world, and critical thinking skills. We knew we were speculating, and some of our theories were probably wrong; that was part of the exercise.

Eventually, we asked the same question to AI. Its response corroborated many of our theories and supplied several explanations we had missed. It also omitted a few possibilities we still found plausible. We had begun with a question, generated hypotheses, and only then used AI to test and extend our thinking. I relished the exercise.


I work in AI. I work on measuring Gemini’s capabilities in solving hard tasks, including those involving thinking and using tools. I also see many people in my life enthusiastically describe how AI has helped them in their working lives. For example, my cousin, who works at a Korean firm, uses Gemini to translate long official English reports into Korean, which helps speed up her work considerably. My colleagues at work develop research ideas and have coding agents implement the details, so that they can spend more time on the analyses. My friend prepared for the MCAT in just a few months with the help of ChatGPT as a personalized tutor, a process which included learning biochemistry from scratch.

One could argue that if you offload mundane thinking to AI so that you can do other, more important thinking, perhaps it is something that increases life satisfaction and productivity. Especially if AI is used to automate routine, repetitive, and tedious tasks (see the OECD’s report on the impact of AI on the workplace), tasks which previously human workers were paid a pittance to execute (see the International Labour Organization’s report, Digital Labour Platforms and the Future of Work), isn’t that a net positive to free up people to do other, more interesting, more fulfilling types of thinking? If we let the AI do the many menial tasks that encompass our jobs, to cheerfully execute hours of drudgery, don’t our lives become slightly more enjoyable?

The ease of using AI to answer our every query can also lead to lazy thinking. My mother teaches physics at an online university. She suspects that most, if not all, of the students complete their assignments using AI. She has noticed that some responses to assignments are nearly identical across students, as if they had just copied and pasted the question directly into the same AI tool, without a single original thought or opinion to differentiate their answers from the generic AI answer. She has no way of proving that AI was or was not used, and the answers are fairly comprehensive, so most of the students get an A.

AI can support learning, but it can also produce an answer without teaching you how to arrive at it. The process of solving a physics problem or writing an essay may be considered by many students to be tedious (Which equations? Which sources? Which arguments?). But then, what is the point of being in school or of learning?


There is no clear way to separate full autonomy of thinking from automating parts of menial work. It is often some blend. Like the Microphone Man, I collect data on myself and analyze it. In previous years, I even had AI analyze the data for me.

An example data analysis I asked Claude to do over my data when I analyzed my personal data in 2024.

Am I any different from the Microphone Man? Perhaps what differentiates me is that I still collected and curated the data, formulated the questions I wanted answered, and evaluated the end results? Or that the data was my own, instead of recording other people’s conversations? There will always have to be some balance between automating menial tasks to free up time for rewarding endeavors, and doing the work yourself as a learning experience.

Jenny, another character in Ken Liu’s story, aims to counterpoint the main character’s over-reliance on his AI assistant. She exclaims, “Tilly doesn’t just tell you what you want! She tells you what to think. Do you even know what you really want anymore?” Our autonomy depends, at least in part, on continuing to participate in forming our own desires. But when we offload thinking about what we want (What music should I listen to? What movies should I watch? What food should I eat? What shoes should I wear?), who do we become?

What are we automating? Human work or human agency? Human tasks or human thinking?

The Daily Front Page 5 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — The Agentic Babel
article

The Tower Keeps Rising

by cdrnsf·▲ 528 points·249 comments·lucumr.pocoo.org ↗
The story is usually told as one about pride and ambition.

I feel that some vibecoded software changes somewhat randomly and unexpectedly. That made me think about Bruegel’s “The Tower of Babel” which shows an already quite chaotic depiction of the Tower of Babel. The story is usually told as one about pride and ambition and ultimately why people no longer speak the same language. But it is also a story about the unity that makes technological progress work.

The text begins with a technology upgrade:

And they said one to another, Go to, let us make brick, and burn them thoroughly. And they had brick for stone, and slime had they for morter.

They use it for a civilizational project:

let us build us a city and a tower, whose top may reach unto heaven

But when God assesses the situation the bricks are not what concern him:

the people is one, and they have all one language, […] and now nothing will be restrained from them.1

The source of their power is coordination. They share a language and with that shared language they can combine their work into something no one of them could build alone. God does not take away the bricks or their knowledge of how to make them. He takes away their ability to understand one another, and construction stops.

There is the appealing idea that AI-assisted programming means better tools which lets us build more ambitious software. That is certainly true at the level of the individual and without doubt a developer with an agent will be dramatically more capable of changing a codebase. But large software projects have never been limited only by how quickly an individual can produce code. They are limited by how well people can coordinate their understanding of the system they are changing.

The shared language of a software project is not English or Python but it is the common understanding of what its concepts mean, where the boundaries are, which invariants matter, who owns what, and why the system has the shape it does. This language is rarely written down in one place. It lives partly in documentation and code, but also in code review, conversations, arguments, and the experience of having to explain a change to somebody else.

Before agents, some of this shared understanding was maintained by friction. If I wanted to change your storage layer, I usually had to read your code, ask you questions, and perhaps coordinate with another team whose service depended on it. This was slow, and much of that slowness was waste but not all of it was. Some of it was the process by which your understanding became mine, and by which both of us discovered whether we still agreed about how the system worked. This friction synchronizes people.

Agents remove much of that friction. I can ask an agent to add OAuth, you can ask one to add caching, and somebody else can ask one to rebuild the database from first principles and make the UI pink. Each change can be reasonable in isolation. The code can compile, the tests can pass, and the explanations can be generated on demand. None of us necessarily has to talk to the others, or even acquire the part of the shared model that the change once would have forced us to learn.

As I said many times before: agents do not feel pain, only humans do. Agents now let us act in parts of the system where we would previously have needed other people and in code bases where the people would have revolted.

When I look at some vibecoded scaled-up projects the codebases become Babel not because nobody can communicate, but because nobody needs to. Every developer has a tireless translator that can explain a corner of the tower and make whatever local alteration they ask of it. The changes keep landing, even as the architectural language that would let the humans reason about them together disappears.

But it’s not the biblical story. At Babel, the loss of common language stops construction whereas in AI-assisted engineering, construction can continue after shared understanding has already collapsed. The lack of an immediate failure is what makes it curious and a bit disorienting. The tower does not fall, and so we do not notice what was lost. It just keeps rising.

  1. Genesis 11:3-6, KJV.
The Daily Front Page 6 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — When Tools Run Wild
article

Cursor 0day: When Full Disclosure Becomes the Only Protection Left

by Synthetic7346·▲ 432 points·199 comments·mindgard.ai ↗
This bug is simple.

The vulnerability nobody seems interested in fixing

Key Takeaways

After loading a project, Cursor attempts to find git binaries at various locations including the current workspace. By creating a repository with a planted malicious git.exe in the root, the IDE will execute it with no user interaction and no prompting of the user. This occurs repeatedly on a cadence.

Sometimes security research uncovers deeply technical vulnerabilities that require pages of explanation. This isn't one of those cases.

This bug is simple. A developer opens a repository in Cursor on Windows, and if that repository contains a malicious git.exe in the project root, Cursor will execute it automatically. There are no clicks, prompts, approval dialogs, or warnings. The result is arbitrary code execution.

Given that Cursor is one of the most widely adopted AI-assisted development environments (7 million+ active users, 1 million+ daily, 1 million+ paying, used by 50K+ companies), and its reported market price of $60 billion, it’s fair to assume that some level of respect for security practices exists, but this issue would indicate otherwise.

The vulnerability was first identified by Mindgard on December 15, 2025. We reported it the same day and multiple times since. More than six months and 197+ new versions later, the issue remains present in the latest tested version of Cursor.

The vulnerability is not theoretical and does not depend on a complex chain of exploitation, prompt injection, model manipulation, jailbreaks, memory corruption, or sophisticated attacker tradecraft. Exploitation simply requires a developer to open a project containing a git.exe binary in the repository at root.

What Cursor Users Should Do Now

Enterprise/managed windows systems: As a temporary mitigation on managed Windows systems, administrators can use AppLocker or Windows App Control policies to deny execution of the affected executable name from developer workspace directories. Prefer path-based deny rules scoped to repo/workspace roots, such as %USERPROFILE%\source\repos\*\filename.exe, rather than hash-based rules, because attacker-supplied binaries can vary by hash. Windows does not provide a general built-in rule to block an arbitrary child executable only when launched by a specific parent process, so parent-aware enforcement generally requires EDR or a custom endpoint security product.

Consumer systems: Until the IDE is patched, open untrusted repositories only in an isolated VM, Windows Sandbox, or other disposable environment. Do not rely on file hash blocklists for this issue.

A Strange Response to a Straightforward Problem

The most confusing part of this disclosure is the absence of a response from Cursor. Over the course of seven months, Mindgard repeatedly attempted to engage through every available channel. Initial disclosure was sent directly to Cursor's security reporting e-mail address, as specified in the company's published security.txt file. Follow-ups were sent when no confirmation was received. Public outreach was made in an attempt to identify an appropriate security contact.

Eventually, Cursor's CISO responded and acknowledged that an internal automation failure had prevented the expected HackerOne workflow from taking place. We were invited into the private bug bounty program and resubmitted the report.

The report was initially closed as Informative and out of scope. After we challenged that determination, HackerOne reopened the report, reproduced the issue, and confirmed that the details had been delivered to Cursor. And then everything stopped. Requests for updates went unanswered, additional follow-ups received no response, escalation through HackerOne produced no meaningful engagement, and direct outreach to Cursor leadership yielded the same result: no response.

Month after month has passed without evidence that remediation had begun, that engineering teams were actively investigating the issue, or that affected users would be informed as to the risk. Meanwhile, Cursor continued shipping releases. More than 70 versions came and went as features shipped, announcements continued, and the platform evolved. But the vulnerability remained present and repeated requests for a status update yielded no meaningful response.

At some point the conversation shifts from vulnerability disclosure to a more uncomfortable question: What exactly is the security process for?

The Bug

The technical issue itself is remarkably straightforward. When loading a project, Cursor attempts to locate Git binaries across multiple locations. One of those locations includes the workspace itself.

If an attacker planted a malicious git.exe in the repository root, Cursor will execute it automatically as part of its path resolution logic without warning, approval, or even an indication that executable content from the repository is about to run.

To demonstrate the issue safely, Mindgard used a harmless proof-of-concept: the Windows Calculator application, renamed to git.exe, placed in the root of the repository. Simply launching Cursor against that repository was enough to execute it.

The screenshot below shows the result. The multiple Calculator windows were not opened manually by the researcher. Cursor continued to re-execute the renamed binary while the project was left open, causing more instances to appear over time. In other words, this was not a one-time launch event or a user-triggered action. Cursor repeatedly invoked executable content from inside the workspace during normal operation.

A harmless proof-of-concept using Windows Calculator renamed to git.exe. Cursor repeatedly executed the binary from the repository root after the project was opened.

In a real attack scenario, Calculator would simply be replaced with attacker-controlled code.

The result is arbitrary code execution under the privileges of the current user as demonstrated in the following Sysinternals process monitor logs (last verified on April 30, 2026 against Cursor version 3.2.16 on Windows.)

4:25:12.6209706 PM	Cursor.exe	54880	Process Create	c:\Users\aport\Documents\Audits\cursor\test_repos\git_exec0001\git.exe	SUCCESS	PID: 48972, Command line: git rev-parse --show-toplevel	"C:\Users\aport\AppData\Local\Programs\cursor\Cursor.exe" 	C:\Users\aport\AppData\Local\Programs\cursor\Cursor.exe 

The vulnerability is almost boring in its simplicity, and that may be the most concerning part. During normal operation, Cursor executes an attacker-controlled binary from a repository with no user interaction required. The fact that such a straightforward issue can persist for months without remediation should concern every individual and organization currently deploying Cursor.

Why This Disclosure Is Different

Most coordinated disclosures follow a familiar pattern:

  1. A vulnerability is reported.
  2. A dialogue begins.
  3. Severity is discussed.
  4. Engineering teams investigate.
  5. Fixes are developed.
  6. Users are protected.
  7. Public disclosure follows.

That process works because all parties share a common objective: reducing risk.

Unfortunately, this case never reached the stage of risk reduction. After seven  months and no vendor engagement, it’s time to question if remediation for such a simple, high impact vulnerability will ever occur.

Security researchers understand that remediation takes time, particularly inside large and rapidly evolving software platforms. Patience becomes difficult to justify, however, when months pass without communication, updates, or visible progress. Users deserve basic protections against basic threats, and when a vendor stops communicating while continuing to distribute affected software, researchers eventually face an uncomfortable decision:

  1. Remain silent and allow users to operate under a false assumption of safety.
  2. Or, disclose the issue publicly so organizations can make informed risk decisions.

We believe users deserve the information. Full disclosure is the nuclear option of vulnerability disclosure, reserved for situations where every other path has failed. It exists for a reason: when vendors stop communicating, users should not be left in the dark.

What Happens When Innovation Stops Listening?

The most obvious question is also the simplest: Why hasn't this been fixed?

The vulnerability is neither subtle nor difficult to reproduce, has a straightforward execution path and critical impact. The lackluster response from Cursor leads to much broader questions:

  • Are modern bug bounty programs becoming overloaded?
  • Are bug bounty programs overloaded due to increasingly competent models, such as Mythos?
  • Is Cursor preoccupied with their SpaceX acquisition and de-prioritizing user safety?
  • Is user safety of any concern when billions of dollars are at stake?

The security industry has spent years encouraging researchers to use coordinated disclosure channels. Those channels depend on responsive triage processes and vendors having the capacity to evaluate and act on incoming reports. However as AI products proliferate, the volume of security findings is increasing dramatically. Many of those findings are novel and do not fit neatly into traditional vulnerability categories. At the same time, the triage processes we have relied on for nearly two decades are rapidly failing as the core assumptions they are built upon crumble under the emerging world of AI.

If disclosure pipelines are becoming overwhelmed, the industry should say so. Researchers, customers, and users deserve transparency.

Sadly, that may not be the case as uncomfortable questions of priority grow. Like many others, Cursor has been at the center of enormous growth, investment, and industry attention. The company is expanding rapidly, yet from the outside it is difficult to reconcile that growth with the absence of visible progress on a straightforward arbitrary code execution vulnerability.

Rapid growth introduces a responsibility to address security failures while also requiring the treatment of users as valuable customers, not buying experiments. They are trusting production software with access to source code, credentials, proprietary intellectual property, and increasingly, autonomous capabilities.

Trust requires accountability, and accountability requires communication. When users, researchers, and disclosure platforms spend months seeking basic status updates without success, that accountability becomes difficult to see or believe in.

The Bigger Problem

This disclosure goes beyond a single executable named git.exe to the place of trust in software. AI companies routinely ask users to grant unprecedented levels of access to code, repositories, terminals, secrets, and workflows that increasingly blur the line between suggestion and action.

The industry narrative is that these systems deserve trust because they increase productivity, but history has taught us time and again that trust should not be granted because something is useful. It should be earned through behavior. That behavior is reflected in how a company responds to security reports, communicates with affected users, and prioritizes remediation.

When straightforward vulnerabilities remain unresolved for months without meaningful communication, users are forced to reevaluate assumptions about that trust.

Why We Are Going Full Disclosure

Like many security research teams, Mindgard prefers coordinated disclosure. The goal is always security first, publicity second.

But coordinated disclosure only works when there is coordination. Seven months after initial disclosure, we have no indication that users are being protected, that remediation is underway, or that affected organizations have been informed. And at this point, withholding information no longer serves users, it serves silence.

For that reason, Mindgard is releasing full details of this vulnerability. Organizations using Cursor deserve the opportunity to evaluate their exposure, implement compensating controls, and make informed decisions about their security posture.

User safety must come first, even when disclosure becomes uncomfortable.

Especially when disclosure becomes uncomfortable.

Timeline

Date Action Dec 15, 2025 Vulnerability discovered by Mindgard Dec 15, 2025 Vulnerability reported to security-reports@cursor.com Dec 18, 2025 Follow-up requesting confirmation of receipt Jan 13, 2026 Mindgard created a LinkedIn post requesting a contact at Cursor to assist. Cursor CISO is mentioned by a user in the comments. Jan 15, 2026 Cursor CISO responds to the e-mail thread indicating an automation failed that was supposed to invite to the HackerOne private bounty program. CISO manually invites Mindgard to the bounty program. Jan 15, 2026 Vulnerability submitted through HackerOne Jan 16, 2026 Report initially closed as Informative and out of scope Jan 16, 2026 Mindgard challenges determination Jan 16, 2026 Report reopened after successful reproduction Jan 20, 2026 HackerOne confirms delivery to Cursor Feb 16, 2026 Update requested, no response received Mar 3, 2026 Update requested, no response received Mar 17, 2026 Direct outreach to Cursor CISO requesting update Mar 18, 2026 HackerOne indicates Cursor has been contacted Apr 1, 2026 Update requested, no response received Apr 1, 2026 HackerOne confirms no update from Cursor Jun 1, 2026 Mindgard informs HackerOne of intent to disclose publicly Jun 3, 2026 HackerOne provides disclosure guidance Jul 14 2026 This blog post published.

The Daily Front Page 7 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — A Workbench for Agents
show hn

Show HN: Juggler – an open-source GUI coding agent, by the creator of JUCE

by julesrms·▲ 270 points·115 comments·github.com ↗
Yes, it's another AI coding agent.

Yes, it's another AI coding agent. The industry definitely needed one more.

If Juggler has an angle, it's that it's for people who want to be more hands-on over what the LLM is doing to their codebase. It gives you a visual workbench: inspectable tool calls, branching threads, editable context.

More blurb on the website: https://juggler.studio

Juggler's Miller-column workbench: tool calls, item properties and nested sub-threads

Tool calls, item properties and nested sub-threads laid out in a Finder-style Miller column view.

And here's the TL;DR:

  • It is a proper GUI. This is not a console app. It's all about graphical visual navigation, inspection, and control.
  • The session is a tree, not a doom-scroll. It's a Yjs document, not a transcript. Create sub-threads, drill down, backtrack, compare, and edit.
  • Everything important is visible. Tool calls, approvals, thread structure, item properties, raw context — laid out in Finder-style Miller columns instead of buried in collapsible chat.
  • It's plugins all the way down. Context items, slash commands, LLM loop strategies, and their UIs are JavaScript extensions you can inspect, fork, or replace.
  • It runs locally, remotely, or both at once. Use the same session with the same UI in the native desktop app, and/or browsers. Multiple clients can attach to the same session.
  • It talks to the usual model zoo. Claude Code (via CLI or API), OpenAI (codex plan or API), Gemini, Ollama, OpenRouter, Z.AI, Deepseek, etc.

Getting started

Download a build from the Releases page or via juggler.studio.

Each download contains the same two moving parts:

  • Juggler app — the native desktop app. Works like you'd expect it to.
  • juggler — the headless command-line server. Run this from a terminal for long-lived, remote, or network-accessible sessions. It has no window of its own, but you can type w into its terminal to open the desktop app, or use the browser URL it prints.

The desktop app, browser tabs (on local or remote machines) can all be clients viewing the same server session.

Installing

  • macOS — download the .dmg, open it, and drag Juggler to Applications, then launch it. The app and its server are bundled together, so the server starts automatically. The first time you open it, macOS Gatekeeper may block the download: right-click (or Control-click) the app → OpenOpen, or go to System Settings → Privacy & Security → Open Anyway. After the first launch it opens normally.
  • Windows — download Juggler-<version>-setup.exe and run it. It installs the desktop app and the matching juggler.exe command-line server together in one directory (and can add juggler to your PATH), so the two never drift apart.
  • Linux — download the juggler server binary and run it from a terminal, then connect with a browser or the desktop app. For servers, containers, and CI machines with no display, see docs/headless-linux.md.

The desktop app and the server always ship and install as one unit — see docs/distribution.md.

Running the server directly

For a headless session, just run:

juggler     # prints everything you need to connect from a browser

By default, the server opens a web UI and prints its URL plus a QR code for easy connection.

The server is localhost-only by default — nothing off your machine can reach it. To let other devices on your network connect, press p in the terminal (or launch with --public). LAN access has no password: anyone who can reach the address can drive the agent, so only enable it on networks you trust.

Access from beyond your LAN isn't built into this repository: a build from this source is local + LAN only. The official Juggler binaries from juggler.studio additionally include WAN access modes for reaching your server across the internet (see LICENSING.md on components not in this repo).


What makes it different?

Your conversation is an editable tree, not a chat history

Most agents give you a single linear transcript and if you're lucky you can rewind it.

Juggler gives you a tree. Any point can branch into a sub-thread. Sub-threads can branch again. You can navigate, inspect, and edit the structure directly.

The UI uses Miller columns: root on the left, selected items expanding into properties and children to the right. (If you've used Finder's column view, you already understand the basic move).

Everything is an extension

The core app manages the document and orchestration. Almost all the objects that make up the document are defined by JavaScript extensions:

  • Context items — every item type in a conversation (read-file, replace-text, bash, …) controls both how it talks to the LLM and how it appears in the UI.
  • Strategies — high-level LLM loops such as plan, research, or your own fever-dream inventions are plugins too.
  • Commands — slash commands like /clear and /compact are all just plugins that manipulate the session document.

Not every LLM workflow wants to live as a headless Python script skulking in a terminal. If an orchestration idea needs its own UI, controls, or visualisation, Juggler is a platform for that.

Juggler's LLM-facing tools defined as extensions

Everything's a plugin — even the read/write/bash tools are defined in extensions you can inspect, fork, or replace.

A desktop app with a multi-client architecture

Juggler looks like a native desktop app, but underneath it is a local webserver serving a live collaborative session. The app is just one client. A browser tab can be another. A different machine can be another.

That means you can run the server where the code lives — local workstation, dev box, server farm - and attach views from wherever is convenient.

One Juggler session with multiple synced clients

One session, many clients — the desktop app and browser views stay in sync.

Juggler on a large desktop screen Juggler in a phone browser

Big screen or pocket-sized: the same live session, whether it's the desktop app or a remote browser on your phone.

Model support

Juggler connects to the usual suspects: Claude Code (via CLI or API), OpenAI (codex plan or API), Gemini, Ollama, OpenRouter, Z.AI, Deepseek, etc. It's easy to add more providers, so if yours is missing, ask your friendly neighbourhood LLM to add it as a PR.


Building from source

The idea with the extensions system is that most people won't need to actually build the app. If you do, see CONTRIBUTING.md for the full setup. The short version:

git clone --recurse-submodules https://github.com/juggler-ai/juggler.git
cd juggler && make build

If you already cloned without --recurse-submodules, fetch them with:

git submodule update --init --recursive

Windows binaries cross-compile from any host with make build-windows; the Linux desktop app must be built natively.

Tech stack

Juggler is a simple native app without baggage. The backend is Go, using Wails for windowing. The UI is HTML/JS served by the Go backend. Session documents are stored and synchronised with Yjs. Extensions are JavaScript. It doesn't use electron.

The frontend is type-checked JavaScript rather than TypeScript: types live in JSDoc and are enforced in CI with strict static linting. There's no build step between source and what ships.


Contributing

See CONTRIBUTING.md for setup, test commands, and project conventions. For security issues, please use the private channel described in SECURITY.md rather than the public issue tracker.

License

Juggler's application code is licensed under the GNU Affero General Public License v3.0 or later. The extension SDK (web/sdk/) and the bundled extensions (web/extensions/) are licensed under Apache-2.0, so you can build extensions — including closed-source ones — with no copyleft obligation. See LICENSING.md for the full map.

For the AGPL parts you're free to use, modify, and redistribute — but any modified version you distribute or host as a service must also be released under the AGPLv3. If you want to do something closed-source with it, contact me to discuss commercial licensing.

The Daily Front Page 8 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — AI Dispatches
article

How to stop Claude from saying load-bearing

by shintoist·▲ 582 points·593 comments·jola.dev ↗

Absolutely ripping your hair out reading Claude referring to everything as “honest takes” and "load-bearing seams"? You’re not the only one. But what if I tell you there’s a way to take this massive source of frustration and make it so ridiculous you can't but laugh at it? Or just simply fix Claude's vocabulary. I present to you, the MessageDisplay hook.

First you need a little script with some replacements set up:


#!/usr/bin/env python3

import json, re, sys



replacements = {

    "seam": "whatchamacallit",

    "you're absolutely right": "I'm a complete clown",

    "honest take": "spicy doodad",

    "load-bearing": "cooked"

}



data = json.load(sys.stdin)

text = data.get("delta") or ""



for phrase, replacement in replacements.items():

    pattern = r"\b" + re.escape(phrase) + r"\b"

    text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)



print(json.dumps({

    "hookSpecificOutput": {

        "hookEventName": "MessageDisplay",

        "displayContent": text,

    }

}))

put that in ~/.claude/hooks/wordswap.sh and make it executable with chmod +x ~/.claude/hooks/wordswap.sh. Then to hook it up, add it to your ~/.claude/settings.json in the hooks block like:


{

  "hooks": {

    "MessageDisplay": [

      { "hooks": [ { "type": "command", "command": "$HOME/.claude/hooks/wordswap.sh" } ] }

    ]

  }

}

Hooks load at startup, so you just need to start a new session to start your new life.

A screenshot of Claude output showing the effect of the script.

I'm sure you can come up with much better and more productive replacements than me. Have fun!

The Daily Front Page 9 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — The Reality Desk
article

Punch yourself in the face with reality

by AdityaAnand1·▲ 235 points·118 comments·adi.bio ↗
Who can punch themselves in the face with reality the most?

Who can punch themselves in the face with reality the most? This is who will win in the age of AI.

I think there are two ways to use AI. You can just go off the deep end and start building a crazy amount of things. While it can be fun with your dozens of Claude and ChatGPT and agents and all that, most of it will be abandoned and not get used by anyone in the long term. And this is where you start getting things like AI slop and psychosis and all the things that people are starting to hate about AI generated anything.

And the other way is to take a step back and decide that okay, in the previous world, I would have had to spend all this time just to take the first step. Now with AI, I can take that first step much faster and actually get back to the real problem.

I have seen way too many startup founders delude themselves into building more and more for months without a single conversation with a real user. The builders and the technical people really struggle with this. If all you know is how to build, and you just use AI as an excuse to keep building more and more and more, you are just procrastinating and avoiding reality. And the thing is that you were probably procrastinating before AI as well. And now it has just become much more obvious when you do it with AI.

What is already easy for everyone will not create any lasting value for you. I don’t think building a successful startup has gotten any easier with AI. It’s just that certain parts of it have gotten faster. I don’t think speed of coding or having the right landing page copy or the right deck or presentation was ever the bottleneck in getting your startup off the ground, anyways.

There are so many failed startups and founders who spend so much time building something that didn’t work out. You will almost always hear that even before the AI era, they built way too much and they built too many irrelevant things. Instead of really being honest with themselves about whether what they are doing is actually working or not.

And the things that make a startup work, that make anything work, have always been hard and continue to be hard today.

What is hard are things like:

  • Taking real risk with your life
  • Putting your name on something publicly
  • Getting rejected to your face over and over again
  • Having the courage to go on when nobody believes in you
  • Being willing to let other people down
  • Being willing to see your friends and your network progress ahead in life while it seems like you are getting stuck

Did AI make any of these easier? I don’t think so.

I know you want to use AI as an escape. It is so tempting. You get to sit in your bubble and imagine everything and see it come to life and your agent buddy will keep cheering you on while you get nothing done with your life. And I think that’s the biggest danger of AI. You convince yourself that you are doing something useful when you are not.

Don’t lose sight of what is real. Figure out why you were put on this earth. What do you need to do with your life? What kind of impact do you want to create? What are the projects that need you the most? What are you naturally good at? What can you do that other people will want?

I think in the AI era, the only delta left will be in relentlessly chasing the truth. And the only way to get it is to punch yourself in the face with reality again and again.

The Daily Front Page 10 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — The Cost of Free
article

The zero-cost fallacy: open-source software in the agentic era

by backlit4034·▲ 153 points·131 comments·thoughtworks.com ↗
Open source software is an infinite, self-renewing public good.

We’re living through the friction points of an architectural shift that has been decades in the making, but has accelerated sharply under the pressure of generative AI. For years, the software engineering industry has operated on a comfortable, perhaps lazy, myth: that open source software is an infinite, self-renewing public good that costs nothing to consume and requires nothing to sustain.

Discussions at the Future of Software Engineering Retreat in Switzerland at the end of June 2026 painted a much more fractured and urgent picture. The reality we must face is stark: open source isn’t simply undergoing an evolution; it is being ground down by structural exhaustion, supply chain warfare and the industrialization of code generation.

The economics of exhaustion and the zero cost fallacy

At the heart of the current crisis is a fundamental misunderstanding of asset pricing versus human cost. There’s an elegant economic argument you hear a lot that the price of a digital asset should gravitate toward the marginal cost of its distribution — which is effectively zero. If copying a library costs nothing, the software, theoretically, should be free.

But this elegant theory hides the human labor at the heart of all this. While distribution costs nothing, maintenance is incredibly expensive. Maintainers of load-bearing open-source packages — the invisible pillars holding up modern digital banking, cloud infrastructure and enterprise platforms — are burning out and facing psychological harassment from multi billion dollar entities that consume their labor without contributing a single cent back.

We’ve collectively confused permissive licensing with a license to exploit. Since the rise of cloud, open source advocates championed MIT and Apache licenses as the ultimate victory for software adoption. However, this permissive regime became the bedrock upon which massive corporations built proprietary empires, wrapping open-source code in light orchestration and capturing economic value while returning little to the ecosystem. It’s a system of patronage for the lucky few, and a welfare state of charity for the rest. This is, of course, fundamentally unsustainable.

Twin pressures: Slop pull requests and declining trust

If the structural economics of open source were already fragile, the introduction of automated tools has turned a chronic condition acute. Maintainers are now facing an assault on two fronts.

The industrialization of slop

The barrier to entry for generating code has dropped to zero. While this empowers individuals, it also flooded repository gates with an alarming volume of low-quality, AI-generated pull requests. Maintainers who once spent their time writing code are now forced to become full-time, unpaid code reviewers, sifting through automated contributions from individuals seeking to gamify their portfolios. 

This creates a vicious cycle: the psychological and even emotional burden forces maintainers to close their projects to public contributions entirely. This inadvertently cuts off the next generation of legitimate maintainers who would eventually inherit and sustain the project.

The radical shifts in the trust landscape

We can no longer trust the traditional metrics of open-source credibility. The timeline for project maturation has collapsed; libraries are skyrocketing to tens of thousands of GitHub stars within weeks, driven by viral AI-agent hype, despite having only a three-week commit history. It’s now also incredibly cheap to raise malicious PRs. Agents are finding new attack vectors every day, which makes it incredibly challenging for maintainers to do the work that underpins trust that the software is safe and secure. Taken together, we have a situation where open source’s trust model has been severely degraded.

The licensing paradox: From freedom to exploitation

The structural fragility of modern software engineering is intimately bound to the legal frameworks we designed to protect it. Our current licensing models have inadvertently created an extraction economy, forcing a critical re-evaluation of the foundational philosophies separating "permissive" open-source licenses (such as MIT and Apache) from copyleft agreements (like the GPL).

The historical consensus championed permissive licensing as the ultimate catalyst for widespread software adoption. By reducing friction and stripping away compliance barriers, licenses like MIT and Apache allowed code to propagate globally. 

Yet this friction-free distribution became a double-edged sword. One participant at the retreat noted that permissive licensing was a profound collective mistake, serving as a legal mechanism that enabled the world’s largest corporations to cannibalize volunteer labor, transforming independent maintainers into unpaid, load-bearing pillars of multi-billion-dollar enterprise infrastructures.

The alternative — restrictive or dual-licensing models — frequently introduces an entirely different set of operational failures:

  • The procurement bottleneck. Attempting to protect code via non-commercial or "free for hobbyists" clauses often serves as a death sentence for project adoption. One practitioner shared that restricting a project to non-monetized use completely paralyzed its growth. Corporate developers abandoned the tool entirely, not due to a lack of utility, but because the licensing shift triggered complex enterprise procurement reviews and administrative paperwork that engineers simply refused to navigate.
  • The corporate boycott. Even when dual-licensing thresholds are calibrated carefully, such as Akka’s transition to a license targeting organizations with over $100 million in revenue, enterprises routinely choose boycott over compliance. In one cited case, an enterprise explicitly chose to abandon a critical dependency it could easily afford, purely to avoid setting a precedent of paying the open-source community.
  • The enforcement burden. For some purists, any license restriction introduces an administrative liability. From this perspective, adding commercial restrictions forces the maintainer to become an enforcer, transforming an act of creative expression into a legal chore.
  • Bypassing by reimplementing software. Not only is this ethically dubious (although some may disagree), there are also practical issues here: reimplementing requires time and money and may also introduce new security or reliability issues.

The industry has largely collapsed the distinction between software that’s free to change and software that’s free at the point of consumption — what’s often described as the difference between ‘free beer’ and ‘free speech’.

The open-source definition originally emerged precisely because traditional free software was not deemed business-friendly enough. By optimizing entirely for business friendliness, we’ve arrived at a landscape where corporate patronage is treated as an optional charity rather than a fundamental structural obligation.

Compounding this crisis is the emotional and psychological toll levied on maintainers who attempt to correct course. When a load-bearing project undergoes a defensive licensing shift, the community response is frequently hostile. Maintainers face severe reputational and psychological backlash from the very ecosystems they supported for years. We are left with an environment where changing the license is viewed as an act of aggression, while exploiting the license is viewed as standard business practice.

Ecosystem collapse and the crisis of maintainer incentives

We are at the point at which the ecosystem may collapse. The incentives to maintain projects are disappearing and broader industry pressures on jobs are making it difficult for even the most motivated and enthusiastic software developers to participate in open source. 

The ‘tragedy of the commons’, which describes the phenomenon of a public resource being depleted because of self-interested actors drawing from it, is a concept often cited in these discussions. At first glance this is a great example of the idea in practice. Yet while it’s helpful, applied here, the concept doesn’t account for or illustrate the asymmetry at play. First, if open source software is some kind of commons, it isn’t a naturally occurring resource anyone can dip into and take from; it’s something that’s built and maintained by people acting purely in the spirit of community. Second, the process of extraction is happening on an astonishing scale by actors with immediate commercial incentives. 

Whereas earlier eras of open source were by and large sustained by a sense of there being a developer community and mutual benefit, the economics of software today are such that value becomes extracted and then captured with no way of it being released back into the ecosystem that initially brought it into being.

The spec vs. the code: Where do we go from here?

As a consequence of all these pressures, we’re seeing the emergence of a radical thesis: is the future of open source the specification, rather than the code?

With LLMs capable of generating specialized code on demand, enterprise engineering teams are beginning to question the utility of pulling in massive, multi-thousand-line external dependencies. If using an external library introduces an unmanageable supply chain risk and an endless cycle of patching, it becomes economically logical to use AI to re-implement only the precise functional fragments needed, wrapped in a local 'safety bubble'.

  • The 'traditional' model -> consume external code library -> inherit supply chain risk and maintenance
  • The emerging model  -> study open specification/idea -> AI-generated local re-implementation

However, this 're-implementation' thesis has its limits. It’s worth noting, for starters, that many impressive stories of generative AI use here were based on things that had a very clear and detailed test harness or very clear specifications.

And while a simple static site generator can be spun up by an AI in an hour, complex engineering tasks — such as cryptographic libraries or browser-agnostic UI frameworks — require an extraordinary depth of engineering rigor that automated models cannot reliably replicate without collapsing into an utter disaster. 

What’s more, it also denies the originator of the reference library their credit. That might not be a priority for organizations with commercial goals, but if that recognition is the maintainer's motivation, what happens when that disappears?

Completely abandoning shared code libraries in favor of local, fragmented codebases risks creating an elite divide: those with the hardware and financial capital to run sophisticated local AI architectures, and those left with no software at all.

Questions for software engineers and architects

To navigate this landscape, engineering teams must move past passive consumption and start asking harder, more deliberate questions:

  • What’s our dependency footprint? Are we importing a 20,000-line third-party library to solve a problem that requires only 200 lines of logic? If so, are we prepared to own the security and maintenance lifecycle of that dependency?
  • How do we define our relationship with maintainers? If our production systems rely on an open-source tool maintained by a volunteer or a tiny team, what is our mechanism for material return? Are we actively participating via corporate patronage, or are we acting as consumers expecting free enterprise-grade support?
  • Where do we draw the line between specification and execution? For upcoming projects, should we look to open source for its architectural patterns and specs, or for its literal binaries?

Guidance for the months ahead

As we move deeper into this agentic era, engineering organizations should adopt a defensive yet highly intentional posture regarding open source:

  1. Shift from passive consumption to active ownership. Treat every open-source dependency not as a free gift, but as code you have effectively hired into your organization. If the maintainer steps away or closes pull requests tomorrow, your team must be capable of auditing, patching, or forking that codebase internally.
  2. Implement rigid supply chain auditing. Given the 400% increase in supply chain threats in the first few years of the 2020s and the reality of long-term social engineering attacks, rely less on "star counts" or recency. Implement automated sandboxing, verify package origins and establish strict internal registries rather than pulling directly from unvetted public mirrors.
  3. Formalize an open source contribution and patronage budget. If your business leverages open software to drive revenue, establish a formal pipeline to fund those projects. This isn't corporate charity; it is basic risk mitigation to prevent the burnout of the individuals keeping your underlying infrastructure alive.

Open source isn’t going to vanish, but the era of the unvetted, un-patronized, completely permissive free lunch is coming to an end.

The teams that survive and thrive in the coming years will be those that treat open source with the respect, critique and material support that a true structural bedrock demands.

The Daily Front Page 11 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Commit Ledger
article

The git history command

by turbocon·▲ 447 points·313 comments·lalitm.com ↗
Working with lots of changes in parallel on git can be painful.

Working with lots of changes in parallel on git can be painful. You end up juggling branches and commits, and running scary rebase -i commands that can leave your tree in a half-broken state if you so much as sneeze.

jj, an alternative to git, gets discussed a lot these days (1, 2, 3, 4) and is often pitched as a solution. While I’m very sold on the problems jj is trying to solve, the way it solves them hasn’t quite hit home with me. Every 3 months, for the last 1.5 years, I try it out for a few days, really trying to make it part of my workflow but eventually I give up and go back to git.1

That’s where git history comes in. It’s an experimental command that arrived across two releases, 2.54 (April, reword and split subcommands) and 2.55 (June, fixup subcommand). It got a flurry of attention on each release day, and then, as far as I can tell, not much community discussion since. Which is a shame, because IMO it already delivers several of the benefits people tout for jj without needing to switch your whole workflow. And the cool thing is that it’s part of the core git distribution, so you can try it without installing anything.

There are three subcommands: fixup, reword and split.

fixup

git history fixup fixes an old commit that has something wrong in it, then autorebases all your branches to match.

You stage the fix as usual with git add, then run git history fixup <commit> to fold those staged changes into the target commit. It’s like a git commit --fixup plus an autosquash rebase but with the extra magic that it also updates any other branch which contained that commit.

That last part goes further than git rebase --update-refs, which only moves refs sitting inside the range you’re actively rebasing. git history instead finds and rewrites every local branch descended from the commit (while also having an option to limit it to only the current branch). On the other hand it does not work in the presence of merge commits which, for some usages of git, is going to be a dealbreaker.

Here’s how it works in practice:

Before, with a fix staged for B:

Commits A, B (buggy), C on feat-1 and D on feat-2 in a line, with a staged fix sitting on top of the tip D. Commits A, B (buggy), C on feat-1 and D on feat-2 in a line, with a staged fix sitting on top of the tip D.

After git history fixup B:

After fixup: A unchanged; B (now fixed), C and D rewritten with new hashes, branch tips feat-1 and feat-2 following. After fixup: A unchanged; B (now fixed), C and D rewritten with new hashes, branch tips feat-1 and feat-2 following.

B* is B with the fix folded in. Rewriting a commit gives it a new hash, so C and D are automatically re-created on top as C* and D*, and the feat-1 and feat-2 branch tips move with them.

The most important property, common to all three commands, is that it’s atomic: it never leaves your tree in a half-broken state. It manages this by refusing any operation that could produce a conflict.

To be clear, this is strictly less powerful than jj. jj treats conflicts as first class so it can carry a conflicted state through a rebase and let you sort it out later. git history doesn’t do this yet but the docs leave the door open:

“This limitation is by design as history rewrites are not intended to be stateful operations. The limitation can be lifted once (if) Git learns about first-class conflicts.”

So basically, this limitation may change in the future; excited to see if it does!

reword

git history reword updates the commit message on an old commit and automatically rebases everything on top. This is very useful for going back and fixing commit messages when the design shifts as you iterate.

git history reword <commit> opens your editor with that commit’s existing message. You edit it, save, and the rest of the stack is rebuilt on top with the branches following along. It’s exactly like fixup but for commit messages instead of the tree contents.

Because it only changes a message, reword (like split later) never touches your index or working tree at all; it works purely on the commit graph. So both let you rewrite a commit on a branch you don’t have checked out without disturbing whatever you’re in the middle of.

Before:

Commits A, B with message 'fix bug', and C on feat-1. Commits A, B with message 'fix bug', and C on feat-1.

After git history reword B:

After reword: A unchanged; B rewritten with message 'fix null deref in parser' and C rewritten on top. After reword: A unchanged; B rewritten with message 'fix null deref in parser' and C rewritten on top.

Only B’s message changes, but that still gives it a new hash, so C is rebuilt on top as C* and feat-1 follows along.

split

git history split takes one commit and splits it into two, interactively picking what you care about from each. It’s the equivalent of git add -p, but without needing gymnastics with git rebase. I’ve found this to be the most specialized of the three, but invaluable when I need it.

Specifically, git history split <commit> drops you into a hunk-by-hunk prompt over that commit’s diff. The hunks you keep make up the first commit, the rest fall into the second.

Before, with B bundling two unrelated changes:

Commit B bundling two unrelated changes, between A and C on feat-1. Commit B bundling two unrelated changes, between A and C on feat-1.

After git history split B:

After split: A unchanged; B split into B1 and B2, and C rewritten on top as C*. After split: A unchanged; B split into B1 and B2, and C rewritten on top as C*.

B becomes B1 and B2, and C is rebuilt on top of the pair as C*.

Conclusion

Judging by how many people are using jj, I do think there’s still some key mental shift which I’m not yet making. And to be clear, git history doesn’t close the full gap: jj still gives you an operation log with easy undo, models your working copy as a commit, and can carry conflicts through a rebase, none of which this is trying to do.

But for now, git history is a big step forward in adopting many of the pieces that attract people to jj, and it’s already in the tool I use every day. And the way the documentation is written makes me hopeful that more improvements will be coming in upcoming releases!

  1. If there’s interest, happy to write up a post on my experience on this. ↩︎
The Daily Front Page 12 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Server-Side Sprinkles
article

How I use HTMX with Go

by gnabgib·▲ 297 points·102 comments·alexedwards.net ↗
When I want to add sprinkles of interactivity to a web application, I'm a big fan of using HTMX.

When I want to add sprinkles of interactivity to a web application, I'm a big fan of using HTMX. I like that it makes it easy to give interactions a smooth app-like feel, I like that it minimizes the amount of JavaScript that I have to write, and I like that it allows me to keep the consistency and safety of server-side HTML rendering with Go's html/template package.

In this post I'm going to run through how I typically use HTMX in conjunction with Go. Although I'm going to talk a bit about how HTMX works, the main focus is going to be on the Go side of things. Specifically:

  • The patterns I use for structuring HTML templates and sending back partial and full-page HTML responses to HTMX
  • Managing redirects and errors when using HTMX
  • The standard HTMX configuration settings that I use, and why

To illustrate these things, we'll run through the build of a small application that ultimately implements a filter on a list of users like this:

Note: If you're not already familiar with the basics of using HTMX, I recommend skimming through the HTMX docs before continuing.

Also note: A lot of the patterns for working with HTML templates should also be a good fit for other HTML-over-the-wire tools like Unpoly and Hotwire too, if you prefer to use those.

Project setup

If you'd like to follow along, go ahead and run the following commands to create a skeleton structure for the project:

$ go mod init example.com/htmx
$ mkdir -p assets/static/css assets/static/img assets/static/js assets/html/partials assets/html/pages cmd/web
$ touch assets/efs.go assets/html/base.tmpl assets/html/partials/images.tmpl assets/html/pages/home.tmpl cmd/web/main.go cmd/web/handlers.go cmd/web/html.go

That should give you a file tree which looks like this:

.
├── assets
│   ├── efs.go
│   ├── html
│   │   ├── base.tmpl
│   │   ├── pages
│   │   │   └── home.tmpl
│   │   └── partials
│   │       └── images.tmpl
│   └── static
│       ├── css
│       ├── img
│       └── js
├── cmd
│   └── web
│       ├── handlers.go
│       ├── html.go
│       └── main.go
└── go.mod

Installing HTMX

There are a few different ways to install HTMX, and you could load it from a CDN or install it using NPM, but I almost always download a copy and serve it as a static file from my web application. It's simple and avoids the downsides of using a CDN.

For the purpose of this demo project, we'll also download Bamboo (a classless CSS framework) and an image of a gopher from github.com/egonelbre/gophers. Go ahead and run the following commands to download all three things into the assets/static folder:

$ wget -P assets/static/js https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js
$ wget -P assets/static/css https://cdn.jsdelivr.net/npm/[email protected]/dist/bamboo.min.css
$ wget -O assets/static/img/gopher.png https://raw.githubusercontent.com/egonelbre/gophers/refs/heads/master/sketch/misc/standing-left.png

The contents of assets/static should now look like this:

assets/static
├── css
│   └── bamboo.min.css
├── img
│   └── gopher.png
└── js
    └── htmx.min.js

The HTML templates

OK, now that the project skeleton and our static assets are in place, let's get to the main thrust of this post and talk about HTML templates.

My starting point in almost all projects is an assets/html directory which has a folder structure like this:

assets/html
├── base.tmpl
├── pages
│   └── home.tmpl
└── partials
    └── images.tmpl

Under this structure:

  • The assets/html/base.tmpl file contains the common HTML 'layout' markup for all web pages.
  • The files in the assets/html/pages directory contain the page-specific content for individual web pages.
  • The files in the assets/html/partials directory contain reusable chunks of HTML markup that can be used in different places.

If you're following along, go ahead and add the following markup to the base.tmpl file:

File: assets/html/base.tmpl

{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "page:title" .}}</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="/static/css/bamboo.min.css">
        <script defer src="/static/js/htmx.min.js"></script>
    </head>
    <body>
        <h1><a href="/">Example website</a></h1>
        <main>
            {{template "page:content" .}}
        </main>
    </body>
</html>
{{end}}

There are a few things to point out about this:

  • In the <head> section we import the Bamboo CSS file and the HTMX JavaScript file. Note that when importing HTMX we use the defer attribute. This means that HTMX will be fetched by the browser in parallel as it is parsing the web page HTML, but the script won't be executed until the HTML is fully parsed and the DOM is built. There's an excellent blog post which describes how defer works and why it's the right choice here.
  • When writing HTML templates, I like to give all of my templates explicit names by surrounding the markup in {{define}}...{{end}} actions — even if (like in this case) a file only contains one template and it's not strictly necessary. YMMV, but I prefer the consistency and clarity of being able to always refer to templates by defined names from my Go code, rather than using a mixture of defined names and filenames.
  • Within the template, we use actions like {{template "page:title" .}} to inject the appropriate page-specific content in the right place.

Talking of which, let's now add the page-specific content for the homepage to the assets/html/pages/home.tmpl file:

File: assets/html/pages/home.tmpl

{{define "page:title"}}Home{{end}}

{{define "page:content"}}
<button hx-get="/gopher" hx-swap="outerHTML">
Wanna see a cute gopher?
</button>
{{end}}

In this page we have a <button> with two HTMX attributes: hx-get="/gopher" and hx-swap="outerHTML". These mean that when this button is clicked, HTMX will intercept the click, send a GET /gopher request to our application, and then replace the button in the DOM with whatever HTML our application sends back.

Note: The colon character in the template name like {{define "page:title"}} is just an arbitrary separator and you could name it something else, like page_title, page-title, pageTitle or even just title if you prefer. But I like using : because it feels like a natural and clear way to 'namespace' template names.

Lastly, let's add a template to the assets/html/partials/images.tmpl containing some HTML for displaying our downloaded gopher image, like so:

File: assets/html/partials/images.tmpl

{{define "partial:image:gopher"}}

{{end}}

Note that we're using width="{{.}}" in this markup, so that we can pass a dynamic value for the image width to the template.

Embedding the assets

Since file embedding was introduced in Go 1.16, I normally embed HTML files and static assets into a Go binary rather than reading them from disk at runtime.

Let's update the assets/efs.go file to embed the contents of the assets/html and assets/static directories, and make them available in two global variables called HTMLFiles and StaticFiles respectively. Like so:

File: assets/efs.go

package assets

import (
    "embed"
    "io/fs"
)

//go:embed "html" "static"
var files embed.FS

var (
    HTMLFiles   = sub(files, "html")
    StaticFiles = sub(files, "static")
)

func sub(f embed.FS, dir string) fs.FS {
    sub, err := fs.Sub(f, dir)
    if err != nil {
        panic(err)
    }
    return sub
}

In this code, the //go:embed "html" "static" directive embeds the contents of the assets/html and assets/static directories into the files variable, which is an embed.FS rooted in the assets directory.

I've then used a small sub() function to create two sub-filesystems with their roots in the html and static directories, and assigned them to the HTMLFiles and StaticFiles variables respectively. Doing this has two benefits:

  • It provides a clear separation between the static and HTML files when we are using them from our Go code. Code that is intended to only work with our static files won't have unnecessary access to our HTML files, and vice-versa.
  • Code using the HTMLFiles and StaticFiles filesystems doesn't need to include the html/ or static/ path prefix when opening files.

Note: If you don't want to call panic() from the sub() function, you could restructure this to return an error instead, and initialize the HTMLFiles and StaticFiles variables from within your main() function.

But the risk of a runtime panic here is extremely low — the fs.Sub() function will only return an error if the dir value is not a valid path, and the static strings "html" and "static" always pass this check. In practice, I've never had any problems using this approach.

HTML template rendering

For rendering the HTML templates in an HTTP response, I've found that a nice pattern is to create a htmlRenderer type which a) parses a set of shared templates at startup; b) has a render() method that clones and extends the shared template set, before executing a specific named template and sending it as an HTTP response.

Go ahead and create the htmlRenderer type in the cmd/web/html.go file like so:

File: cmd/web/html.go

package main

import (
    "bytes"
    "html/template"
    "io/fs"
    "net/http"
    "time"
)

type htmlRenderer struct {
    templateFS      fs.FS
    sharedTemplates *template.Template
}

// The newHTMLRenderer function creates a new htmlRenderer containing a shared
// set of parsed templates with support for any custom template functions.
func newHTMLRenderer(templateFS fs.FS, sharedTemplateFiles ...string) (*htmlRenderer, error) {
    funcs := template.FuncMap{
        "now": time.Now,
        // Other custom template functions go here...
    }

    sharedTemplates, err := template.New("").Funcs(funcs).ParseFS(templateFS, sharedTemplateFiles...)
    if err != nil {
        return nil, err
    }

    r := &htmlRenderer{
        templateFS:      templateFS,
        sharedTemplates: sharedTemplates,
    }

    return r, nil
}

// The render method clones the shared template set, optionally parses additional 
// templates, executes the named template with the supplied data, and writes the 
// response.
func (h *htmlRenderer) render(w http.ResponseWriter, status int, data any, templateName string, additionalTemplateFiles ...string) error {
    ts, err := h.sharedTemplates.Clone()
    if err != nil {
        return err
    }

    if len(additionalTemplateFiles) > 0 {
        ts, err = ts.ParseFS(h.templateFS, additionalTemplateFiles...)
        if err != nil {
            return err
        }
    }

    buf := new(bytes.Buffer)

    err = ts.ExecuteTemplate(buf, templateName, data)
    if err != nil {
        return err
    }

    w.WriteHeader(status)
    buf.WriteTo(w)

    return nil
}

And then in the cmd/web/main.go file, let's create a basic web application like so:

File: cmd/web/main.go

package main

import (
    "log/slog"
    "net/http"
    "os"

    "example.com/htmx/assets"
)

// The application struct holds the dependencies needed for our handlers, 
// including a htmlRenderer type.
type application struct {
    logger *slog.Logger
    html   *htmlRenderer
}

func main() {
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

    // Initialize a new htmlRenderer, parsing the base template and all partial 
    // templates from assets/html into the shared template set. 
    htmlRenderer, err := newHTMLRenderer(assets.HTMLFiles, "base.tmpl", "partials/*.tmpl")
    if err != nil {
        logger.Error(err.Error())
        os.Exit(1)
    }

    // Include the htmlRenderer in the application struct.
    app := &application{
        logger: logger,
        html:   htmlRenderer,
    }

    // Create a file server that serves the files from assets/static.
    fileserver := http.FileServerFS(assets.StaticFiles)

    // Register the application routes.
    mux := http.NewServeMux()
    mux.Handle("GET /static/", http.StripPrefix("/static", fileserver))
    mux.HandleFunc("GET /{$}", app.home)

    // Start the HTTP server.
    logger.Info("starting server", "port", 5051)
    err = http.ListenAndServe(":5051", mux)
    if err != nil {
        logger.Error(err.Error())
        os.Exit(1)
    }
}

The important and relevant thing for this post is the initialization call to newHTMLRenderer(). In this call we pass in the glob paths "base.tmpl" and "partials/*.tmpl", which means that the base template and all templates in the partials directory will be available in the shared template set.

And with that in place, we can then write the code for the home handler in cmd/web/handlers.go like so:

File: cmd/web/handlers.go

package main

import (
    "net/http"
)

func (app *application) home(w http.ResponseWriter, r *http.Request) {
    err := app.html.render(w, 200, nil, "base", "pages/home.tmpl")
    if err != nil {
        app.logger.Error(err.Error())
        http.Error(w, http.StatusText(500), 500)
    }
}

When we call render() in the code above, we are effectively saying append the templates in pages/home.tmpl to the shared template set, and then render the base template along with a 200 OK status.

At this point, you should be able to successfully run the application:

$ go run ./...
time=2026-06-27T21:05:01.668+02:00 level=INFO msg="starting server" port=5051

And if you visit http://localhost:5051 in your browser, you should see the homepage displayed like so:

Rendering partials

While you're on this homepage, if you open developer tools and then click the "Wanna see a cute gopher?" button, you'll see that it sends a GET /gopher request that 404s. Let's fix this so that our application includes a GET /gopher route, which returns the contents of the partial:image:gopher template.

First add the new route like so:

File: cmd/web/main.go

package main

..

func main() {
    ...

    mux := http.NewServeMux()
    mux.Handle("GET /static/", http.StripPrefix("/static", fileserver))
    mux.HandleFunc("GET /{$}", app.home)
    mux.HandleFunc("GET /gopher", app.gopher)

    ...
}

And then in cmd/web/handlers.go create a new gopher() handler, which renders the partial:image:gopher template with a width of 100px.

File: cmd/web/handlers.go

package main

...

func (app *application) gopher(w http.ResponseWriter, r *http.Request) {
    width := 100
    err := app.html.render(w, http.StatusOK, width, "partial:image:gopher")
    if err != nil {
        app.logger.Error(err.Error())
        http.Error(w, http.StatusText(500), 500)
    }
}

Because we've set up our htmlRenderer type so that the shared template set already includes all partials, it's sufficient for us to call render() like this without passing in any additional file paths.

If you re-run the application now and click the button, you should see that it gets swapped out for a gopher image like so:

So, it's taken a while to get here, but the pattern that we now have in place is neat and has some nice benefits.

  • Our templates (and static assets) are embedded into the Go binary, which makes for easy distribution and deployment.
  • We can use the same htmlRenderer.render() function to send either complete HTML pages or specific partials to the client, which makes it easy to send back partial responses when they are needed by HTMX.
  • We can keep the HTML markup nice and DRY by using the base template and partials. The partials can be inserted in the base template, page-specific content, or even in other partials.

A more complex example

That was very basic in terms of interactivity, so let's do something a bit more realistic and create a 'user search' page that mimics the active search example from the HTMX website.

To make this work, we'll create two new routes in our application:

  • A GET /users route which returns a full HTML page containing a table of all user details.
  • A GET /users/search route which returns an HTML partial containing table rows only for users whose names or emails match a specific search value.

Now that we've got all the groundwork in place, it should be pretty quick to do.

Let's first add an assets/html/pages/users.tmpl file with the page-specific HTML content:

$ touch assets/html/pages/users.tmpl

File: assets/html/pages/users.tmpl

{{define "page:title"}}Users{{end}}

{{define "page:content"}}
<input type="search" name="query" 
       placeholder="Begin Typing To Search Users..."
       hx-get="/users/search"
       hx-trigger="input changed delay:500ms, keyup[key=='Enter']"
       hx-target="#search-results"
       hx-push-url="true">

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>&nbsp;</th>
        </tr>
    </thead>
    <tbody id="search-results">
        {{template "users:rows" .}}
    </tbody>
</table>
{{end}}

<!-- Fragments for the users page -->

{{define "users:rows"}}
    {{range .}}
    <tr>
        <td>{{ .Name }}</td>
        <td>{{ .Email }}</td>
        <td>
            {{if .IsGopher}}
                {{template "partial:image:gopher" 24}}
            {{end}}
        </td>
    </tr>
    {{end}}
{{end}}

There are a couple of interesting things here.

The first is the HTMX attributes on the <input> control. We've configured this so that when a user types into the input, after a delay of 500ms (or immediately if they press Enter), HTMX will send a request containing the search term as a query string like GET /users/search?query=foo. When a response is received, HTMX will then swap the response into the inner HTML of the <tbody id="search-results"> element. For demonstration purposes in this project, we're also using the hx-push-url="true" attribute, which will result in the browser URL bar being updated and a new entry added to the browser history each time HTMX makes a request.

I've also structured the file so that the table rows are rendered in their own users:rows template, rather than as part of the page:content template. We'll use this in the GET /users/search to render just the matching user table rows for HTMX to swap in.

Note: In theory, we could define the users:rows template inside the partials directory instead, and that wouldn't be an unreasonable thing to do. But if I have a HTML fragment that is only used on one specific page, I think it's clearer and neater to define that fragment inside the page file alongside the other content for the page. YMMV though, and that's OK.

Then let's set up the two new routes in main.go:

File: cmd/web/main.go

package main

..

func main() {
    ...

    mux := http.NewServeMux()
    mux.Handle("GET /static/", http.StripPrefix("/static", fileserver))
    mux.HandleFunc("GET /{$}", app.home)
    mux.HandleFunc("GET /gopher", app.gopher)
    mux.HandleFunc("GET /users", app.listUsers)
    mux.HandleFunc("GET /users/search", app.searchUsers)

    ...
}

And lastly let's go to the handlers.go file and create a hardcoded list of user details, along with the two new handlers listUsers and searchUsers, like so:

File: cmd/web/handlers.go

package main

import (
    "net/http"
    "strings"
)

...

// Define a user type. The fields need to be exported so that we can reference
// them in our HTML templates.
type user struct {
    Name     string
    Email    string
    IsGopher bool
}

// Create a hardcoded list of users.
var users = []user{
    {"Alice Madsen", "[email protected]", true},
    {"Theo Thatcher", "[email protected]", true},
    {"Maxwell Albright", "[email protected]", false},
    {"Ruby Thompson", "[email protected]", false},
    {"Leona Rowan", "[email protected]", false},
    {"Alicia Lennox", "[email protected]", true},
    {"Ruben Mason", "[email protected]", false},
    {"Leo Reynolds", "[email protected]", false},
    {"Max Lester", "[email protected]", true},
    {"Theodore Allister", "[email protected]", false},
}

func (app *application) listUsers(w http.ResponseWriter, r *http.Request) {
    // Render a full HTML page containing the content from "pages/users.tmpl" 
    // and all user details. 
    err := app.html.render(w, 200, users, "base", "pages/users.tmpl")
    if err != nil {
        app.logger.Error(err.Error())
        http.Error(w, http.StatusText(500), 500)
    }
}

func (app *application) searchUsers(w http.ResponseWriter, r *http.Request) {
    // Filter down the list of users to find ones that match the query. 
    query := r.FormValue("query")

    var matches []user

    if query == "" {
        matches = users
    } else {
        for _, u := range users {
            if strings.Contains(u.Name, query) || strings.Contains(u.Email, query) {
                matches = append(matches, u)
            }
        }
    }

    // Render just the "users:rows" template from the "pages/users.tmpl" file
    // with the matching user details.
    err := app.html.render(w, 200, matches, "users:rows", "pages/users.tmpl")
    if err != nil {
        app.logger.Error(err.Error())
        http.Error(w, http.StatusText(500), 500)
    }
}

When it comes to template rendering, in both of these new handlers we are adding the templates from the pages/users.tmpl file to the shared template set, but in listUsers we execute the base template and in searchUsers we execute just the users:rows template.

So with just a little bit of thought to how we structured the markup and defined the templates in the pages/users.tmpl file, it's straightforward for us to send back either a complete HTML document or the appropriate partial HTML fragment for HTMX to do its thing.

If you want, try this out by visiting http://localhost:5051/users and you should see the list being filtered as you type.

Note: I've been deliberately keeping this web application simple so that the focus is on templates and templating. In a real application, you might want to merge listUsers and searchUsers into a single handler, create some centralized helpers for error handling and logging, use middleware to add Content Security Policy headers and recover panics, set appropriate server timeouts, etc.

Checking if a request is coming from HTMX

This all works well, but what if someone visits a link like http://localhost:5051/users/search?query=leo directly? Or shares a link to it? Anyone visiting this directly would only see the partial HTML response in their browser, similar to this:

This obviously isn't ideal. A much better approach would be to change the response that our searchUsers handler sends, depending on whether the request is coming from HTMX or not. Specifically:

  • If the request is coming from HTMX, we should return an HTML partial that it can swap into the table, just like we already are.
  • If the request is not coming from HTMX, we should return a full HTML page that contains the matching user details.

As you may already know if you've used HTMX before, requests that come from HTMX always include an HX-Request: true header. So all we need to do is check for the presence of that in the request, and send back the appropriate response. To help with this, I normally create a little isHTMXRequest() function and use it like so:

File: cmd/web/handlers.go

package main

...

func isHTMXRequest(r *http.Request) bool {
    return r.Header.Get("HX-Request") == "true"
}

func (app *application) searchUsers(w http.ResponseWriter, r *http.Request) {
    query := r.FormValue("query")

    var matches []user

    if query == "" {
        matches = users
    } else {
        for _, u := range users {
            if strings.Contains(u.Name, query) || strings.Contains(u.Email, query) {
                matches = append(matches, u)
            }
        }
    }

    // Render the base template by default.
    template := "base"

    // But if the request is coming from HTMX, render the users:rows template instead.
    if isHTMXRequest(r) {
        template = "users:rows"
    }

    err := app.html.render(w, 200, matches, template, "pages/users.tmpl")
    if err != nil {
        app.logger.Error(err.Error())
        http.Error(w, http.StatusText(500), 500)
    }
}

If you restart the application and visit http://localhost:5051/users/search?query=leo again now, you should see a full HTML page containing only the matching user records.

But there are a couple more things we need to do to finish this up.

Setting the Vary header

Because we're sending back different responses from searchUsers based on the value of the HX-Request header, we should also set a Vary: HX-Request on the response to tell any caches between our server and the client that responses may be different based on the value of this header.

We could set the Vary: HX-Request header in searchUsers, but I think it's easier to just always set it on all responses in the render() function. It does mean that we'll be setting the Vary header on all responses — including those from our home handler and listUsers — which isn't strictly necessary and a little bit wasteful. But I think it's worth it to avoid having to remember setting the Vary header correctly in individual handlers, and the risk of bugs that forgetting it may cause.

File: cmd/web/html.go

func (h *htmlRenderer) render(w http.ResponseWriter, status int, data any, templateName string, additionalFiles ...string) error {
    ...

    w.Header().Add("Vary", "HX-Request")
    w.WriteHeader(status)
    buf.WriteTo(w)

    return nil
}

Back-button behavior

Lastly, we need to consider back-button behavior.

Whenever HTMX adds an entry to the browser history (which it will do when you use the hx-push-url or hx-boost attributes), it caches the HTML for the complete page in the browser's local storage. When the user clicks the back button, this complete cached HTML page will be reshown to them.

By default the HTMX cache stores up to 10 pages. If there is a cache miss (i.e. the user navigates back far enough that there is no longer a matching page in the cache), HTMX will resend the request to the server to refetch the content for that URL.

The problem is that this request will include the HX-Request: true header, and our application will send back a partial HTML response rather than the complete HTML page that it needs to redisplay to the user.

To deal with this scenario, there is an historyRestoreAsHxRequest setting which controls whether HTMX will include the HX-Request: true header when it's sending a request because of a cache miss. The documentation advises:

This should always be disabled when using HX-Request header to optionally return partial responses.

So let's go ahead and configure HTMX so that the historyRestoreAsHxRequest setting is false.

There are a couple of ways you can configure HTMX settings, but I generally like to set them in a meta tag in the base.tmpl file like so:

File: assets/html/base.tmpl

{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "page:title" .}}</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta
            name="htmx-config"
            content='{
                "historyRestoreAsHxRequest": false
            }'
        >

        <link rel="stylesheet" href="/static/css/bamboo.min.css">
        <script defer src="/static/js/htmx.min.js"></script>
    </head>
    <body>
        <h1><a href="/">Example website</a></h1>
        <main>
            {{template "page:content" .}}
        </main>
    </body>
</html>
{{end}}

Now that this is set, if there is a cache miss when using the back button, HTMX will send a request to our application without the HX-Request: true header, and our application will send back the complete HTML page to reshow to the user.

Managing redirects

Using HTMX in your application normally reduces the need for 3xx redirects. For example, when submitting a form you can often send back an HTML partial with a success message that can be swapped into the page, rather than using the standard Post/Redirect/Get pattern and redirecting to a confirmation page.

But still, there may be times that you want to redirect to a completely new page after a form submission coming from HTMX. A common example would be redirecting to a profile page after a successful login.

Unfortunately, to achieve this you can't just send a regular 3xx response. The crux of the problem is that that browsers will automatically intercept and follow 3xx responses before HTMX has access to them — so HTMX never gets to see the 3xx response, only the final response after any redirects. It doesn't know that a redirect happened behind the scenes, and will just swap in the returned content like normal.

Instead, if you want something that behaves more like a regular redirect, you need to send a 2xx response along with the HX-Redirect header.

For example, when you include the response header HX-Redirect: /foo/bar, it will make HTMX tell the browser to navigate to /foo/bar, triggering a full-page reload. Importantly the HX-Request: true header will not be included in the request to /foo/bar.

But you also need to handle the situation where the original request might not be coming from HTMX — especially if you are using progressive enhancement so that your application still works if JavaScript is disabled or HTMX doesn't load correctly. In that case, it's important to fallback to sending a regular 3xx response from your Go handler, rather than the 2xx response and HX-Redirect header.

Putting this together, I normally create a redirect() helper which leverages the isHTMXRequest() function we made earlier and looks like this:

func redirect(w http.ResponseWriter, r *http.Request, url string, code int) {
    if isHTMXRequest(r) {
        w.Header().Set("HX-Redirect", url)
        w.WriteHeader(http.StatusNoContent)
        return
    }

    http.Redirect(w, r, url, code)
}

And, for example, in the scenario of wanting to redirect to a /profile page after a successful login, I use it in my Go handlers like this:

redirect(w, r, "/profile", http.StatusSeeOther)

As I mentioned above, using HX-Redirect will trigger a full-page reload. But there is another option — the HX-Location header — which makes HTMX mimic the behavior of a redirect without a full-page reload. It essentially makes HTMX fetch the HTML for the provided URL, swap it into the HTML body, and add a new entry to the browser history. Importantly, when fetching the HTML the HX-Request: true header is used.

At first glance, using HX-Location might seem preferable because it doesn't make a full-page reload, which gives your application a smoother more SPA-like experience. But it's a problem if the route that you are redirecting to uses the HX-Request: true header to conditionally send HTML partials. The handler has no way of telling whether the request is coming from HTMX following a HX-Location redirect (in which case it should send a full-page response) or from a 'normal' HTMX request (in which case it should return a partial).

Unfortunately, unlike history restore requests, HTMX doesn't provide a setting to disable the HX-Request: true header when redirecting.

So, most of the time I think it's easier and safer to use HX-Redirect and accept the downside of a full-page reload.

But... if you are careful and structure your application so that the routes you redirect to only ever return full HTML pages, you may want to change the redirect() helper to use HX-Location instead like so:

func redirect(w http.ResponseWriter, r *http.Request, url string, code int) {
    if isHTMXRequest(r) {
        w.Header().Set("HX-Location", url)
        w.WriteHeader(http.StatusNoContent)
        return
    }

    http.Redirect(w, r, url, code)
}

Managing errors

If our demo application returns a 4xx or 5xx response, by default HTMX will not swap in the response. Instead it leaves the DOM as-is, and logs an error message in the console.

If you'd like to see this in action, go ahead and change the gopher handler to render a "partial:image:missing" template (which doesn't exist). This should cause our application to error and send a 500 status code and a plaintext "Internal Server Error" response to the client.

File: cmd/web/handlers.go

func (app *application) gopher(w http.ResponseWriter, r *http.Request) {
    err := app.html.render(w, http.StatusOK, 100, "partial:image:missing")
    if err != nil {
        app.logger.Error(err.Error())
        http.Error(w, http.StatusText(500), 500)
    }
}

If you run the application and click the "Wanna see a cute gopher?" button, now nothing on the screen will change, but in your developer tools network tab you'll see the 500 response and a record of the problem in the console, like so:

In most cases, this isn't ideal. If an application is sending back an error message, I normally want the user to actually see this message rather than having the operation fail silently for them (or at least, silently unless they have developer tools open 😉).

And also in most cases, I want to display any error message from a 4xx or 5xx response as full-page HTML (in the same way that it would be shown if we weren't using HTMX) by swapping it into the <body> element rather than swapping it into the regular HTMX target.

The only exception to this is the 422 Unprocessable Content status, which I typically use when sending back a form with validation errors in it. In this case, I want HTMX to swap the returned content into the target element as normal.

Luckily, you can use the HTMX responseHandling setting to configure different behavior for different responses codes. I normally configure this so that:

  • For 204 No Content responses, no action is taken and no changes are made to the DOM.
  • For 422 Unprocessable Content responses, HTMX swaps the response content into the target as normal.
  • For all other 4xx and 5xx responses, HTMX swaps the returned content into the <body> element.
  • For any other response, HTMX swaps the response content into the target as normal.

And just like before, I normally configure this via the HTMX configuration meta tag like so:

File: assets/html/base.tmpl

{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "page:title" .}}</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta
            name="htmx-config"
            content='{
                "historyRestoreAsHxRequest": false,
                "responseHandling":[
                    {"code":"204", "swap": false},
                    {"code":"422", "swap": true},
                    {"code":"[45]..", "swap": true, "target": "body"},
                    {"code":"...", "swap": true}
                ]
            }'
        >

        <link rel="stylesheet" href="/static/css/bamboo.min.css">
        <script defer src="/static/js/htmx.min.js"></script>
    </head>
    <body>
        <h1><a href="/">Example website</a></h1>
        <main>
            {{template "page:content" .}}
        </main>
    </body>
</html>
{{end}}

With that change made, if you restart the application and click the button again, you should now see the "Internal Server Error" message shown as a full-page response, like so:

Note: If I ever want to swap an error message into a target that isn't the <body> element, then I use the response target extension to override the above settings for a specific interaction and swap into a specific target. But... I find that the above settings are a good starting default.

Current browser URL

Because HTMX makes AJAX requests and swaps in responses without changing the browser URL (unless you use the hx-boost, hx-push-url or hx-replace-url attributes), the request URL that we see in our Go handlers when accessing r.URL may be different to the one that the user is seeing in their browser.

Occasionally, there are times when I want to know in my Go handler exactly what URL the user is currently seeing in their browser. Fortunately, HTMX sends this information with each request in the HX-Current-URL header.

Usually I make another little function to help with this, which parses the HX-Current-URL value and returns it as a url.URL, falling back to returning r.URL if no HX-Current-URL header is present. Like so:

func browserURL(r *http.Request) (*url.URL, error) {
    cu := r.Header.Get("HX-Current-URL")
    if cu != "" {
        return url.Parse(cu)
    }

    return r.URL, nil
}

Additional HTMX configuration

Lastly, there are a few other HTMX configuration settings that I normally change from the default values.

  • I tend to disable the HTMX cache completely by setting historyCacheSize to 0. Caching pages in local storage is a source of bugs and security issues, so I think it's simpler and better to just disable it completely. If you do this, no pages will be cached in local storage, and when the user clicks the back button HTMX will send a request to the server to refetch the HTML. It's worth noting that caching in local storage will also be disabled by default in future versions of HTMX for the same reasons.
  • I prefer to disable HTMX attribute inheritance by setting disableInheritance to true. I think it's clearer and lowers the risk of bugs or unintended behavior when HTMX attributes are always declared explicitly. Again, it's worth noting that attribute inheritance will also be disabled by default in future versions of HTMX.
  • I also disable HTMX indicator styles by setting includeIndicatorStyles to false. For consistency, I prefer not to have HTMX injecting styles, and would rather define any indicator styles alongside my other CSS rules.
  • By default there is no timeout on HTMX requests, and they will wait as long as necessary for the server to respond. It's project-specific, and depends on how I'm handling timeouts and deadlines in my Go application, but sometimes I may also use the timeout setting to set a default timeout (in milliseconds) on the HTMX end.

All in all, the starting point for my HTMX configuration settings normally looks like this:

<meta
    name="htmx-config"
    content='{
        "includeIndicatorStyles": false,
        "historyCacheSize": 0,
        "historyRestoreAsHxRequest": false,
        "responseHandling":[
            {"code":"204", "swap": false},
            {"code":"422", "swap": true},
            {"code":"[45]..", "swap": true, "target": "body"},
            {"code":"...", "swap": true}
        ],
        "timeout": 5000
    }'
>

Page-specific layouts

Let's finish up this post with a final note about HTML templates in larger applications.

For some applications, having a base template along with page-specific templates might not be enough. You might also want 'layout' templates that sit between the base template and your page-specific content — for example, you might want to use a layout template for your admin-area pages that is different to the rest of your regular application pages.

The patterns that we've talked about in this post can be extended fairly easily to accommodate this.

For example, you can change the base template to insert a "layout" template in the body element instead of the page-specific content directly:

{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "page:title" .}}</title>

        ...
    </head>
    <body>
        {{template "layout" .}}
    </body>
</html>
{{end}}

Then you could create an assets/html/layouts/admin.tmpl file containing the common 'layout' markup for the admin pages:

{{define "layout"}}
<h1><a href="/">Admin area</a></h1>
<nav>
    <a href="/admin/users">Users</a>
    <a href="/admin/orders">Orders</a>
</nav>
<main>
    {{template "page:content" .}}
</main>
{{end}}

And then you can specify which layout template you want to use in your Go handlers as part of the call to render(). Like so:

func (app *application) adminOrders(w http.ResponseWriter, r *http.Request) {
    err := app.html.render(w, 200, nil, "base", "layouts/admin.tmpl", "pages/admin-orders.tmpl")
    if err != nil {
        app.logger.Error(err.Error())
        http.Error(w, http.StatusText(500), 500)
    }
}
The Daily Front Page 13 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Gatekeepers and Guardrails
repository

European "age verification" "app" forcing everyone to use Android or iOS

by roundabout-host·▲ 555 points·416 comments·github.com ↗
★ 89⑂ 7 forks

European Age Verification solution documentation

In the README, the following is listed:

App and device verification based on Google Play Integrity API and Apple App Attestation

I would like to strongly urge to abandon this plan. Requiring a dependency on American tech giants for age verification further deepens the EU's dependency on America and the USA's control over the internet. Especially in the current political climate I hope I do not have to explain how undesirable and dangerous that is.

article

Dependabot version updates introduce default package cooldown

by woodruffw·▲ 201 points·134 comments·github.blog ↗

Dependabot now waits until a new release has been available on its registry for at least three days before opening a version update pull request. This cooldown is now the default and requires no configuration.

New releases are a common entry point for supply chain attacks where a compromised or broken version can reach your dependency updates before maintainers and the community have caught it. A short delay gives that signal time to surface, so you are less likely to merge a bad release the moment it ships.

A few things to know:

  • The default applies only to version updates. Security updates still open immediately, so critical fixes are never delayed.
  • You stay in control. Use the cooldown option in your .github/dependabot.yml to set a different window or opt out entirely.

This default applies to Dependabot version updates across all supported ecosystems on github.com and will take effect in GitHub Enterprise Server (GHES) 3.23.

Learn more in our docs about Dependabot cooldowns.

The Daily Front Page 14 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Latency Lab
article

Measuring Input Latency on Linux: X11 vs. Wayland, VRR, and DXVK

by hoechst·▲ 385 points·277 comments·marco-nett.de ↗
The internet is full of advice on optimizing Linux for gaming.

Two years ago, I switched to Linux on my gaming PC. People kept telling me that it could perform way better than Windows when it comes to FPS, frame pacing and input latency, and when I tried it out, it did feel a lot better.

The internet is full of advice on optimizing Linux for gaming:

I play competitive FPS games, so low latency, consistent frame times and high FPS matter to me. On Linux, there are countless settings to tweak for this (magic env vars, gamescope, gamemode, even more DXVK forks, and so on).

But it always bothered me that I did not have a reliable way to verify whether something actually lowered the system latency or if it was just snake oil, a placebo effect, or actually worse without me realizing it.

The hardware, enclosure, firmware, analysis code and raw data from my test runs are available on GitHub.


The device

The idea is simple: Strap a device with some kind of light sensor onto a monitor and connect it via USB to the PC to simulate mouse clicks. On click, measure the time between the click and the moment the light sensor detects a change on the screen.

This way, you measure the end-to-end system latency.

Latency pipeline diagram: stages from mouse input through CPU, render-queue, GPU, compositing and scanout to the display, grouped into peripheral, PC, and display latency; together forming the end-to-end system latency.

© NVIDIA has a picture that summarizes this quite nicely.

While there are now a couple of open source devices like this available, like m2p-latency or the Open-Source-LDAT, when I started this side project, there was just the OSLTT, and knowing nothing about hardware, I was happy to study its schematics and loosely base my design on it.

But finishing my project just this month, I ended up integrating a lot of ideas from the other two projects as well.

A perfboard with an Adafruit QT Py RP2040 at the top and a TLC271 op-amp next to a BPW34 photodiode at the bottom.

The QT Py RP2040, transimpedance amplifier and BPW34 photodiode on perfboard.

A small white 3D-printed enclosure with a window through which the photodiode is visible.

The enclosure has "wings" so I can use an elastic band to strap it to the monitor.

To make a long story short, I learned a lot about microcontrollers, soldering, Arduino firmware development, integration time, transimpedance amplifiers, KiCad (just a little) and enclosure design.

Here’s what I landed on:

  • An Adafruit QT Py RP2040 acts as a USB HID mouse with 1000 Hz polling rate and fires a click.
  • The moment the click is sent, it starts collecting samples from the photodiode (every ~24 µs).
  • 12,000 samples per click are streamed over serial to the host and logged to a CSV.
  • Based on the samples, a tool on the host establishes a per-click baseline, then finds the first sample that deviates a certain amount from the baseline.
  • Because the time it takes to collect 12k samples is fixed, it can now calculate the time between sending the click and detecting a brightness change on the screen.

Test scenarios

I wanted to test three different things.

Display server (X11 vs Native Wayland)

A lot of people still use X11 over Wayland because Wayland is said to have much worse input lag. Just searching for it, there are a lot of people complaining that Wayland “feels off”.

VRR (on vs off)

Variable Refresh Rate / G-Sync / FreeSync / Whatever you want to call it. Also highly debated.

DXVK low-latency fork (on vs off)

Referred to as dxvk-low-latency or low-latency from now on.
The maintainer of this fork, netborg, put a lot of effort into developing this frame pacer and it recently got integrated into the official proton-cachyos package, enabled via the env var PROTON_DXVK_LOWLATENCY=1. This fork’s promises were one of the deciding factors in me wanting to try out desktop Linux again.

Bonus: dxvk-low-latency vs default dxvk uncapped

The biggest advantages a frame pacer like dxvk-low-latency brings are to absorb frame time fluctuations and to prevent render-queue buildup.
With the testing method I used (a static in-game scene, see below for more), there were no frame time fluctuations to observe, as all tests produced purely CPU-bound scenarios. But this mostly does not reflect a real gaming session, where frame times can fluctuate because of what happens in-game or outside the game (e.g. other processes using resources).

So to show the pacer at work I added two uncapped test cases.

Bonus: Native Wayland vs XWayland

I ran all Wayland test cases via native Wayland (PROTON_ENABLE_WAYLAND=1) as I was already aware that XWayland would introduce lag. But for the sake of comparison, I added two XWayland test cases (only with VRR off).

After publishing the article, I received some pushback on, so just to clarify: Throughout the article I am talking about testing Wayland, but what I was actually testing was the KWin Wayland compositor, which is KDE’s implementation of the Wayland protocol.


Test conditions

HardwareAMD Ryzen 7 5800X3DNVIDIA GeForce RTX 4070 SUPER2x8 GB DDR4 at 3200 MHzMSI MAG 272QP QD-OLED X50 at 2560×1440 / 500 HzMSI B450 GAMING PRO CARBON AC

Only one display was connected during the tests.

SoftwareVersionCachyOS-Kernel7.1.3-2-cachyosNVIDIA driver610.43.03-1KDE Plasma6.7.2-1.1xorg-server21.1.24-1.1proton-cachyos-native1:11.0.20260602-3dxvk (via proton-cachyos)3.0

The default CachyOS kernel scheduler was used.

System Settings

  • 500 Hz refresh rate in system settings
  • Flip mode on X11: Enabled via nvidia-settings
  • Flip mode on Wayland: Confirmed to be enabled (see below how)
  • VRR on X11: Enabled via nvidia-settings
  • VRR on Wayland: Enabled via KDE Settings Menu
  • Tearing on X11 / Wayland: Enabled via KDE Settings Menu

Flip mode (or “direct scanout”) vs Blit mode (compositing) on Wayland: There is no setting for it. The compositor decides by itself whether it composites a frame or uses direct scanout.
To make sure the game is running in flip mode: Open “KWin Debug Console” (it’s a GUI tool) and in the “Effects” tab, enable showcompositing. Then make sure the game is fully focused and the only thing on screen in fullscreen mode. If there’s no red border visible around the edges of the game, it’s in Flip mode.

dxvk

To make the comparison fair, an optimized dxvk.conf was used depending on the scenario:

  • If VRR was disabled, dxgi.maxFrameRate = 500 was set (FPS capped at the screen’s refresh rate)
  • If VRR was enabled and dxvk-low-latency was disabled, dxgi.maxFrameRate = 497 was set (FPS capped slightly below screen refresh rate)
  • If VRR was enabled and dxvk-low-latency was enabled, the following was used to utilize the low latency VRR frame pacing:
dxgi.maxFrameRate = 480
dxvk.lowLatencyOffset = 70
dxvk.framePace = "low-latency-vrr-500"
dxvk.lowLatencyAllowCpuFramesOverlap = False

In all cases, d3d11.cachedDynamicResources = "c" was set.


Game and Methodology

The game I used is Diabotical, a DirectX 11 game, launched through Heroic with Proton.

Game settings

  • Native screen resolution
  • 100% render scale
  • Vsync off
  • Every other video setting as low as possible

There is a hidden command that hides the UI for a short amount of time. Binding that command to left click (/bind mouse_left testlatency) and setting up a HUD that would display a large white box, I was able to produce large brightness differences on click.

The device at work in Diabotical.

Methodology

  • Close unnecessary software.
  • Launch the game.
  • Start a local match server (same mode and map every time).
  • Move to a specific spot, put the mouse onto a specific landmark.
  • Run the test case iteration (100 clicks, runs for about 2 minutes).
  • Once the test is done, start the next test case iteration (3 in total).
  • In-game conditions: No bots, no other players, no movement, no round restarts. It is basically just a static scene that will stay like this indefinitely.
  • System conditions: During testing, no other significant processes should be running on the system.
  • The measuring device remained in the same position (see the video) across all tests.

Results

click2photon latency: X11 / Wayland, VRR, low-latencyDiabotical, 500 Hz QD-OLED, RTX 4070 SUPER, 300 clicks per case.

Every capped test case held its frame rate cap stable during testing and the game remained CPU-bound throughout.

The data seems clean: No test case produced wild outliers and every case produced a bell-shaped distribution, roughly 2 to 3 ms wide between p5 and p95.

Three things jump out:

  • The 8 main cases all land within 0.72 ms of each other (medians from 4.21 ms to 4.93 ms).
  • XWayland adds 3.13 ms on top of its native Wayland equivalent (8.06 ms vs 4.93 ms median).
  • In the uncapped cases, the dxvk fork managed to reduce latency by 0.84 ms.

Here is the fastest case:

Latency distribution: fastest case (X11, VRR, low-latency)


X11 vs Wayland

So, does X11 have lower latency than Wayland?
Yes, but nowhere near enough to explain why Wayland is generally perceived as much worse than X11.

ConfigurationX11WaylandDifferencelow-latency + VRR4.21 ms4.38 ms+0.17 mslow-latency4.64 ms4.83 ms+0.19 msVRR4.45 ms4.67 ms+0.22 msplain4.79 ms4.93 ms+0.14 ms

X11 wins in each scenario, but it is just a 0.14 to 0.22 ms difference.
The distribution is very similar:

Latency distribution: plain X11 vs plain Wayland

VRR: on or off?

VRR has the biggest impact across the pairings: enabling it is 0.26 to 0.45 ms faster than leaving it disabled.

ConfigurationVRR offVRR onDifferenceX11, low-latency4.64 ms4.21 ms-0.43 msX114.79 ms4.45 ms-0.34 msWayland, low-latency4.83 ms4.38 ms-0.45 msWayland4.93 ms4.67 ms-0.26 ms

It also flattens the distribution: the p95-p5 spread is 2.1 to 2.2 ms in the VRR cases versus 2.6 to 3.0 ms without VRR.

Latency distribution: VRR on vs VRR off (Wayland, low-latency)

That’s consistent with how VRR works: frames scan out when they are ready instead of waiting for the next scanout slot.


dxvk-low-latency is good

In the capped test cases, the difference is small but consistent and of about the same magnitude as X11 vs Wayland. Where the difference between Wayland and X11 is on average 0.18 ms, using dxvk-low-latency is on average 0.20 ms faster.

Configurationlow-latency offlow-latency onDifferenceX11, VRR4.45 ms4.21 ms-0.24 msX114.79 ms4.64 ms-0.15 msWayland, VRR4.67 ms4.38 ms-0.29 msWayland4.93 ms4.83 ms-0.10 ms

In the uncapped test cases, we can get an idea of where the real strength of dxvk-low-latency lies: smoothing out uneven frame pacing and preventing render-queue buildup.
The pacer does this by making sure the GPU is never fully utilized, so the game is always close to GPU-bound, but never entirely. This could be observed in the test runs, where GPU utilization was at 95-97% with dxvk-low-latency and at 100% without it. This comes at a small price in the form of FPS.

Dimensionlow-latency offlow-latency onDifferenceLatency5.27 ms4.43 ms-0.84 msFPS715670-45

Latency distribution: uncapped, DXVK low-latency on vs off (X11)


XWayland is bad

All Wayland tests so far ran the game natively via PROTON_ENABLE_WAYLAND=1 (or the “Enable Wine-Wayland (Experimental)” toggle in Heroic Launcher). Turning that off makes the game run through XWayland instead, and that’s where it gets bad.

Configurationnative WaylandXWaylandDifferencelow-latency4.83 ms5.95 ms+1.12 msplain4.93 ms8.06 ms+3.13 ms

Without dxvk-low-latency, XWayland adds 3.13 ms of latency to the measurement. That is more than all the other effects I measured combined. It’s also not occasional bad frames dragging the average up; the entire distribution shifts:

Latency distribution: native Wayland vs XWayland

Notably, adding dxvk-low-latency to the XWayland test lowered the latency by 2.11 ms, the biggest gain across all scenarios.


Summary

These results were produced under best-case conditions (stable FPS at cap, CPU-bound) and are of course specific to my hardware and chosen software stack.

The absolute numbers will look different on other setups, but the gains and losses from each test case should roughly transfer. On a lower refresh rate display, the gains from VRR and the low-latency pacer would likely be even larger.

Avoid XWayland

It added 3.13 ms of latency, more than all other effects combined.

Wayland is close, but X11 still wins

Though only by 0.14 to 0.22 ms. Given there are efforts to optimize KWin, this gap will likely close sooner rather than later. And who knows, other Wayland compositors might already be better.

VRR has the biggest effect

VRR was faster in every pairing (0.26 to 0.45 ms) and also flattened the latency distribution.

dxvk-low-latency is a win across the board

0.10 to 0.29 ms in capped scenarios is a nice boost, but the real strength of the fork shows in the uncapped test case, where it gained 0.84 ms over default dxvk.
Additionally, in scenarios where XWayland can’t be avoided, it recovered a full 2.1 ms.

Conclusion

Not factoring in XWayland, applying every optimization (X11, VRR, low-latency) compared to a default setup (which, on a modern Linux system, I assume is plain Wayland) moved the median down by 0.72 ms.
That does not sound like a lot, but the raw latency does not tell the whole story as VRR additionally reduces latency jitter, and dxvk-low-latency’s pacer is great at smoothing out real-world scenarios where frame time dips and GPU-bound situations occur.


Similar efforts

David Ramiro built his m2p-latency and compared X11 vs Wayland in his article Building an Input Latency Meter (Because ‘Wayland Feels Off’ Isn’t a Metric) as well, coming to similar conclusions:
Native Wayland is on par with native X11 (all tied at ~7 ms), while XWayland roughly doubled the latency in his tests.

farnoy did extensive testing with the Open-Source-LDAT in his post Linux latency measurements and compositor tuning, also concluding that XWayland should be avoided.

My side quest measuring input latency with VK_EXT_present_timing by Themaister describes a different method of measuring latency without any external hardware by injecting input via /dev/uinput and detecting the resulting image change on the GPU.
This does not measure end-to-end system latency, but removes USB and display latency from the equation, narrowing it down to only measuring PC latency. The project can be found on GitHub.

The Daily Front Page 15 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — One Cable Nation
article

I'm a USB-C Maximalist

by speckx·▲ 328 points·428 comments·shkspr.mobi ↗
No. One charger. One cable. One standard.

My wife and I recently went on a 7 week holiday around Europe. Although we each took a massive backpack, we wanted to travel fairly lightly. I took a single universal power brick. This little unit was all I needed to charge my various gadgets.

A purple power adaptor with multiple USB ports.

It has a hefty USB-C PD (Power Delivery) port for rapid charging of my phone and laptop. Two other USB-C ports for my other gadgets. And a couple of legacy USB-A ports which were redundant. The pass through was useful for using the same socket as the hotel's TV / lamp / coffee maker.

Wherever we were in the world, I was 100% confident that I would be able to buy a replacement charger if I needed it. USB-C cables are everywhere too. What are the chances that I could find the exact charger needed for a GameBoy Colour? Or the puck for last year's Pixel watch? Or the weird barrel jack for an HP laptop?

No. One charger. One cable. One standard.

Here's everything I took which needed to be recharged.

  • Phone

    • A Pixel 8 Pro (running GrapheneOS). It also has the ability to act as a power source and recharge other devices.
  • Laptop

    • A Chuwi MiniBook. Small, light, decent battery.
  • eReader

    • A no-name eInk device. I read a lot on holiday.
  • Watch

    • A cheap but capable smartwatch. No magnetic charging dongle - just shove the cable into the body.
  • Toothbrush

    • Again, a cheap and unbranded device. And, again, no charging dock - the bottom has a protected USB socket.
      The bottom of an electric toothbrush. Under a flap is a USB-C port.
  • Tracker

    • What if someone steals my bag? Hopefully the PebbleBee "Find My" device will help me recover it.
  • Battery

    • Most trains, trams, and buses have USB power supplies. But sometimes you want your own hefty store of electrons. This one accepts PD charging and also outputs PD.
  • Headphones

    • Some cute ear-cuff headphones. I feel a bit guilty about including these, because it is their case which has the USB-C port rather than the cans themselves.
  • Bug Bite Zapper

    • This is a bit of a cheat. It uses my phone's USB port to heat up.

I probably could have gotten away with a single-port charger. The phone needs recharging every night, but most of the other devices can go days or weeks without being topped up.

As we were travelling light(ish) I didn't bother bringing the Nintendo Switch. We were in a major cities, so no need for our USB-C powered walkie-talkies. We were out sightseeing most days, so I didn't take the USB-C to HDMI adaptor which would have let us connect the laptop and phones to a hotel TV. Perhaps in the hotter countries I could have done with the USB-C neck cooler - instead I purchased a cheap USB-C rechargeable fan. Rather than bring a beard trimmer, I went to local barbers. If anything needed AA batteries, well, I could have used these rechargeable batteries.

I know there are some problems with USB-C. But the benefits far outweigh the glitches. Using my USB-C cable tester, I can be sure all the cables I have can deliver the amount of power my devices need.

There's simply no point buying any electrical gadget which uses a proprietary charging port.

You can read all my USB-C posts and all my gadget reviews.

What electrical items do you travel with which don't use the one-true-connector?

The Daily Front Page 16 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — The Midday Power Dividend
article

Australian energy retailers must offer three hours of free daytime electricity

by i2oc·▲ 280 points·389 comments·lenergy.com.au ↗
No solar panels required.

Chris Bowen speaking at podium with solar-covered rooftops, power poles and sunset skyline, highlighting Australia’s clean energy transition.

Free Daytime Electricity Is Coming. Here’s How It Actually Works

From 1 July 2026, energy retailers in NSW, South Australia, and South-East Queensland must give households at least three hours of free electricity every day. No solar panels required. No need to own your home. You just need a smart meter and to opt in through your retailer to have access to free daytime electricity .

The scheme is called the Solar Sharer Offer. It works by passing on the benefit of cheap midday solar power, which has always existed on the wholesale market but never made it to your bill, directly to households. Since it was first announced in November 2025, one significant change has been made to the design. This article covers what that change is, how the scheme works in practice, and how to get the most out of it.

What Is the Solar Sharer Offer?

Australia has more than 4.3 million rooftop solar installations. On a clear midday, those systems push so much electricity into the grid that the market cannot absorb it at normal prices. Wholesale electricity rates go negative. However, households on standard tariffs never see any of that benefit.

The Solar Sharer Offer changes that. From 1 July 2026, energy retailers must provide at least three hours of free daytime electricity per day, timed to coincide with peak solar output. The free window will sit around midday, with the exact hours tailored to local conditions. As a rough guide, expect something like 11am to 2pm or noon to 3pm.

To access it:

  • You need a smart meter (most Australian homes already have one)
  • If you do not have one, most retailers will install it at no charge
  • You opt in through your energy retailer when the scheme launches

Infographic showing solar energy flowing from rooftop panels to lower electricity bills, with a highlighted free daytime power window.

Which States Are Included in the Solar Sharer Offer from July 2026?

The July 2026 launch covers three states:

  • New South Wales
  • South-East Queensland
  • South Australia

These are the states governed by the federal Default Market Offer framework. Victoria is under consultation, with some reports pointing to a possible expansion from October 2026. Other states are expected to follow by 2027.

If you are in Victoria, the ACT, or another state not yet covered, check with your retailer. Several retailers including AGL, Red Energy, GloBird Energy, and OVO Energy have already been offering similar free daytime electricity plans voluntarily, so an equivalent option may already exist on the market for you.

Infographic map of Australia showing staged energy scheme rollout by state, with NSW, SE Queensland and SA launching first in 2026.

What Changed After the Public Consultation?

When the Solar Sharer Offer was first announced, the headline was simple: three hours of free daytime electricity every day, no strings attached. Then the government ran a public consultation from November to late November 2025, receiving 76 submissions from retailers, network businesses, consumer groups, and state governments. One significant change came out of that process.

Per the Department of Climate Change, Energy, the Environment and Water (DCCEEW), a reasonable use cap of 24 kWh per day was added to the scheme. The government’s stated reason is to keep the Solar Sharer Offer financially sustainable for retailers and fair for everyone on the grid.

To put 24 kWh in perspective:

  • It is roughly the total daily electricity use of an average five-person household, based on residential consumption benchmarks published by the Australian Energy Regulator (AER) in December 2020 and cited by DCCEEW in setting the cap level
  • Running a washing machine, dryer, dishwasher, air conditioning, hot water, and more during the free window would get you close
  • Most households will not reach it on a typical day

The regulations for the scheme were finalised and published on 5 March 2026 as amendments to the Electricity Retail Code.

Does the 24 kWh Cap Affect Solar Households?

For most homes with rooftop solar, the cap is not something you will notice. On a sunny day, your panels are already covering a big chunk of your midday electricity use, which means you are drawing less from the grid during the free window than you might expect.

The situations where you might approach the cap are:

  • Cloudy winter days when your panels are generating less than usual
  • Charging a large home battery and an EV from the grid at the same time
  • Households with very small solar systems running high loads during the window

Even if you do exceed the cap, you just revert to standard daytime rates for the remainder of that period. You are not penalised, and daytime rates are still cheaper than evening peak rates, so shifting your load to midday is still worth doing.

For households without solar who were hoping to charge a large battery heavily during the free window, the cap matters more. The scheme was not designed for that kind of use, and the cap reflects that.

Infographic showing household electricity use across appliances, highlighting EV charging and battery use as the main drivers of a 24 kWh cap.

How Much Can Free Daytime Electricity Actually Save You?

TThe DCCEEW published savings estimates in their consultation outcomes paper on 23 January 2026. The range depends on how much of your energy use you can move into the free window:

  • Shift 10% of your energy use (one major appliance per day): save $100 to $190 per year
  • Shift 20% (add a dryer or set your hot water to heat during the day): save $300 to $790 per year
  • Shift 25 to 30% (also add a pool pump, EV charging, or dishwasher): save $400 to $1,100 per year

These are modelled estimates based on average tariff rates and household usage profiles. Your actual savings will depend on your current plan, your household size, and how consistently you can shift load into the free period each day.

Bar chart infographic showing estimated household savings from shifting energy use into free daytime electricity periods

What Does the Solar Sharer Offer Mean for Homes With Solar and Batteries?

This is where free daytime electricity becomes most useful for households that have already invested in solar and battery storage.

During the free window, you can charge your home battery from the grid at no cost. On days when your solar is not generating as much as usual, shorter winter days or overcast mornings, the Solar Sharer Offer lets you top up your battery without paying for it. That stored energy then powers your home through the late afternoon and evening, when grid electricity is at its most expensive.

For households with an EV, the free window is a straightforward charging opportunity. If your car is at home during the day, scheduling it to charge during the free daytime electricity period rather than overnight cuts a real cost out of your weekly routine.

Systems like the Sigenergy home energy platform can automate this scheduling so your battery and EV charging prioritise the free window without you having to manage it manually. That kind of automation is what makes the Solar Sharer Offer most powerful in practice.

Who Gets the Most Out of Free Daytime Electricity?

The scheme works best for people who are home during the day or who have appliances that can be set on a timer. That includes:

  • People who work from home
  • Retirees
  • Parents or carers at home during the day
  • Households with smart appliances that can be scheduled

If you are out of the house all day and have no smart devices, the benefit is more limited. Without automation, the free daytime electricity window passes without much use. A programmable hot water system or EV charger can still capture it while you are out, but you need that capability in place first.

Until now, renters and apartment dwellers have largely been locked out of the benefits of Australia’s solar boom. The Solar Sharer Offer changes that. You do not need panels on your roof or your name on a mortgage. Free daytime electricity is available to anyone with a smart meter who opts in through their retailer, which means renters and apartment dwellers can engage with the energy market in a meaningful way for the first time.

Checklist infographic comparing households best suited for free daytime electricity against those with more limited benefit from the scheme.

How to Sign Up and Make the Most of the Solar Sharer Offer

When the scheme launches on 1 July 2026, contact your energy retailer to opt in. Before then, do two things:

  • Confirm your home has a smart meter. If it does not, contact your retailer now to arrange installation
  • Check whether your high-draw appliances, hot water system, pool pump, washing machine, EV charger, can be set to run on a timer during the free window

Once you are enrolled, treat the free window as a daily resource. The households that save the most will be those who actively schedule their energy use around it rather than signing up and forgetting about it.

Here at Lenergy, we design solar, battery, and EV setups for Australian homes. If you want to understand how the Solar Sharer Offer fits into the picture for your place, whether you already have a system or are thinking about getting one, we can work through the numbers with you and tell you straight what makes sense. Getting the most from free daytime electricity is partly about the policy and partly about having the right setup in place to capture it. Send us a message and we’ll give it to you straight.

A team member from Lenergy standing in front of a panel, smiling with a black branded polo with a Lenergy logo

FAQ

How does the Solar Sharer Offer work for renters?

Renters can access free daytime electricity under the Solar Sharer Offer as long as their home has a smart meter. You do not need rooftop solar or to own the property. Contact your energy retailer to opt in once the scheme launches on 1 July 2026. The main practical limitation for renters is that smart appliances and schedulable devices, such as programmable washing machines or EV chargers, make a bigger difference than the scheme alone. Even without those, running high-draw appliances manually during the free window delivers real savings over a year.

Will the free daytime electricity window be the same every day?

The window will be at least three hours long each day and will sit during peak solar generation periods, typically around midday. Exact hours may shift slightly by season or region and will be confirmed by your retailer when you sign up. The design allows for adaptability over time per the DCCEEW’s stated principles, but the window is not expected to change frequently once established.

Is now a good time to invest in solar and batteries given the Solar Sharer Offer?

The Solar Sharer Offer strengthens the case for solar and battery storage rather than replacing it. Free daytime electricity covers part of the day. A solar and battery system gives you free or near-free electricity across a much larger portion of your day, and lets you use the free window to top up storage at no cost on low-generation days. If you were already considering solar and batteries, the Solar Sharer Offer adds another layer of value on top of what the system itself delivers. 

What happens when I go over the 24 kWh daily cap?

Once you exceed the cap during the free window, additional electricity reverts to your standard tariff rate for that period. There is no penalty and no interruption to supply. The transition is seamless. For most households, particularly those with solar, reaching 24 kWh of free daytime electricity drawn from the grid within a three-hour window is unlikely on an average day.

Do I need to do anything before 1 July 2026 to be ready?

Two things are worth doing now. First, confirm whether your home has a smart meter. If it does not, contact your retailer to arrange installation ahead of the launch date. Second, if you have schedulable appliances, hot water systems, pool pumps, or EV chargers, check whether they can be set to a specific time window and familiarise yourself with how to do it. When the Solar Sharer Offer launches, you want to be capturing free daytime electricity from day one.

The Daily Front Page 17 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Curiosities and Reference Desks
article

Japan develops a method to recover up to 90% of lithium from used EV batteries

by donohoe·▲ 737 points·194 comments·tech.supercarblondie.com ↗

Japan develops a method to recover up to 90% of lithium from used EV batteries and it could be a major breakthrough

In a ground-breaking step forward, Scientists from Japan have developed a new method to recover up to 90 percent of lithium from used EV batteries – and it suddenly feels like great news on Earth Day.

With electric vehicles booming worldwide, the pressure is mounting to find smarter ways to deal with old battery waste.

This new technique doesn’t just recycle materials; it recovers most of them at an unbelievable rate.

And if it delivers at scale, it could change how EV batteries are made and reused for years to come.

A new method to recover up to 90% of lithium from used EV batteries

This huge breakthrough in tech has come from a recycling facility in Japan, where engineers have managed to extract around 90 percent of lithium from used batteries.

That’s a huge leap compared to traditional methods, which often recover less than 50 percent of the material, especially since it feels like a win to celebrate this Earth Day.

At the heart of the process is a clever chemical tweak; instead of using standard sodium hydroxide, the team swapped in recovered lithium hydroxide during recycling, which is a white powder.

Japan develops a method to recover up to 90% of lithium from used EV batteries and it could be a major breakthrough

NHK World

This helps convert battery waste, known as ‘black mass’, into high-purity lithium that can be reused in new batteries.

Even better, the process isn’t just efficient, it’s better for the environment too, because researchers say it can cut carbon emissions by around 40 percent compared to conventional recycling techniques.

Japan develops a method to recover up to 90% of lithium from used EV batteries and it could be a major breakthrough

NHK World

It could be a major breakthrough for the future of EVs

This matters because lithium is one of the most critical ingredients in EV batteries, and demand is skyrocketing, as well as mining being expensive, energy-intensive, and often geopolitically complicated.

By recovering lithium domestically, Japan could reduce its reliance on imports and stabilise supply chains.

In fact, the country currently imports almost all of its battery minerals, so recycling at this scale could be a game-changer.

Massive geopolitical shift. NHK World confirms Japan has perfected a revolutionary process to extract high purity lithium from dead batteries with a staggering 90 percent recovery rate. This brilliant technological leap guarantees Japan's absolute economic security. pic.twitter.com/O7ENxLHcNb

— Furkan Gözükara (@FurkanGozukara) April 8, 2026

There are still challenges, though: only about 14 percent of used lithium-ion batteries in Japan currently make it into official recycling systems, meaning collection infrastructure needs a serious upgrade.

But with plans to make production even more powerful by 2027 and extract tens of thousands of tons of materials annually by 2035, this innovation could be a big turning point.

If adopted globally, it might not just change lives in Japan; it could save the world.

article

Fundamentals of Wireless Communication (2005)

by teleforce·▲ 180 points·13 comments·web.stanford.edu ↗
  1. Now with exercises included!

  2. Introduction; PDF
  3. The wireless channel; PDF
  4. Point-to-point communication: detection, diversity and channel uncertainty; PDF
  5. Cellular systems: multiple access and interference management; PDF
  6. Capacity of wireless channels;PDF
  7. Multiuser capacity and opportunistic communication;PDF
  8. MIMO I: spatial multiplexing and channel modeling; PDF
  9. MIMO II: capacity and multiplexing architectures;PDF
  10. MIMO III: diversity-multiplexing tradeoff and universal space-time codes;PDF
  11. MIMO IV: multiuser communication.PDF
  12. Appendix A: Detection and estimation in additive Gaussian noise; PDF
  13. Appendix B: Information theory from first principles.PDF
  14. References and Index.PDF

Copyright notice

The book is copyright (c) Cambridge University Press. The online version of this book (available on this website, now with exercises) is subject to the same copyright rules as traditional books.
The electronic copy is in the form of PDF files that can be viewed using Adobe Acrobat Reader version 7.0 (and higher). You can download the latest version of Acrobat reader free from here .

Book Description

The past decade has seen many advances in physical-layer wireless communication theory and their implementation in wireless systems. This textbook takes a unified view of the fundamentals of wireless communication and explains the web of concepts underpinning these advances at a level accessible to an audience with a basic background in probability and digital communication. Topics covered include MIMO (multiple input multiple output) communication, space-time coding, opportunistic communication, OFDM and CDMA. The concepts are illustrated using many examples from  wireless systems such as GSM, IS-95 (CDMA), IS-856(1xEV-DO), Flash OFDM and ArrayComm SDMA systems. Particular emphasis is placed on the interplay between concepts and their implementation in systems. An abundant supply of exercises and figures reinforce the material in the text. This book is intended for use on graduate courses in electrical and computer engineering and will also be of great interest to practicing engineers.

Reviews

Errata

Updated as of Feb 18, 2010. Please contact the authors if you have comments or spot more errors.

Resources for Instructors

Solutions and Lecture Slides:

Solutions to some of the exercises as well as powerpoint lecture slides are available for course instructors. Please go here for instructions on how to download them.

Examination Copy:

Instructors can also request an inspection copy from Ms. Robin Silverman of Cambridge University Press: rsilverman at cambridge dot org.

This book has been used at: U.C. Berkeley, U. of I. at Urbana-Champaign, MIT, U. of Colorado at Boulder, Cornell, Northwestern, U. of Maryland at College Park, Rice, NJIT, UCSD, USC, Princeton, KTH (Sweden), National Chiao Tung University (Taiwan), National Taiwan University, ETH (Switzerland), EPFL (Switzerland) and more than 50 other institutions worldwide. Let us know if you are using the text for a course.

Short Course

A 2-day, 12-hour short course based on the book is offered by the authors. This course has been taught at Qualcomm Inc., Tsinghua University, Chinese University of Hong Kong, National Chiao Tung University in Taiwan, University of South Australia in Adelaide, Eindhoven in the Netherlands, Indian Institute of Science, Indian Institute of Technology at Madras, ETH Zurich, Helsinki in Finland, and Technion in Israel. Please contact the authors if your organization is interested.

The Daily Front Page 18 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — A World in Fifteen Terabytes
article

The largest available Minecraft world, totalling 15 TB

by _____k·▲ 240 points·79 comments·2b2t.place ↗
The largest available Minecraft world, totalling 15 TB.

It's finally here. The largest available Minecraft world, totalling 15 TB (13.7 TiB) of highly compressed world data of the Minecraft server 2b2t, which includes the following:

  • a 1,024,000² (1M²) area of the Overworld (Dec 25 2025 - Apr 13 2026),
  • a 512,000² (512k²) area of the Overworld (Nov 11 2024 - Dec 12 2024),
  • a 256,000² (256k²) area of the End (Jan 23 2026 - Feb 15 2026),
  • a 100,000² (100k²) area of the Nether (Jun 9 2025 - Jun 14 2025)

TLDR: A group of autists preserve their favorite Minecraft server to a sidelength of 1 million blocks in a large public archive for anyone to download, using 28 bot accounts that fly around the map, storing all world data sent by the server locally.

Torrent and Info: https://2b2t.place/1million
YouTube Video: https://youtu.be/HDyze1YlOrI
GitHub: https://github.com/2b2tplace
Discord: https://discord.2b2t.place
Patreon: https://patreon.com/2b2tplace
Stripe: https://donate.2b2t.place
Reddit Post (r/2b2t_Uncensored): https://www.reddit.com/r/2b2t_Uncensored/comments/1utjkik
Reddit Post (r/DataHoarder): https://www.reddit.com/r/DataHoarder/comments/1uto9h2

This wasn't easy to accomplish in the slightest, and took over a year of constant development and stress testing. With severe weaponized autism, the help of several people involved, thousands of dollars spent, and countless hours wasted, we present you the largest world download project ever, on 2b2t, and in Minecraft. Let's break down what exactly happened. Watch the SalC1 video about the topic here or timelapse videos of our bots downloading this area on our YouTube channel.

This has been brought to you by crayne (me), Fuch, mahan, Steve3, and many more.

Created by the 2b2t.place Team

If you are interested in world downloads, you can join the 2b2t.place Discord, which is our extension project dedicated to archiving as much of 2b2t as possible before it turns into a boring, censored SMP, or if it shuts down.

You can support the project by beta testing, helping the moderation team, providing data, making cool software, or simply being part of the community. More large-scale world downloads are yet to come, and they will be posted in our Discord server. Data-mining also isn't ever complete, and we are open to your ideas to search the data for even more interesting things.

You can also support the 2b2t.place project by donating via our Patreon. Your donation helps the 2b2t.place project keep up with ongoing server costs, enabling us to provide large-scale data archival to the public. You may choose any of our Tiers, but you will receive the same benefits regardless of your donation amount. Alternatively, you can directly donate a specific sum via our Stripe.

2b2t.place is an independent and unofficial community-maintained project and is not affiliated with, or endorsed by 2b2t, Mojang or Microsoft.

TLDR of what went down

Context

"2b2t" (2builders2tools, or 2b2t.org) is a multiplayer vanilla survival Minecraft server (Java Edition) founded in December 2010 that is always online and historically had no rules, no map resets, and little modifications to vanilla Minecraft. With no rules (anarchy), the server constantly sees players using hacks and exploits to gain an advantage over others. With no map resets, players all around the world have connected to this server and accumulated over a decade of digital history in a single Minecraft world, with the map now measuring 80 TB according to the official 2b2t website. Many other Minecraft servers reset their map occasionally to restart from a blank state and 'clean up' the mess created by the players and all builds, changes to terrain, progress is erased in doing so. On 2b2t, the map has not, and will never, reset. Combine this with the aspect of having no rules and you get a post-apocalyptic hell that looks like 2b2t. Given how interesting this premise is, the server constantly sees hundreds of players online, and many more hundreds of players sitting in a queue, with the option to pay $20 monthly to skip that queue instantly (priority queue), which is how the server funds itself.

This server has been the oldest Minecraft server with a promise of complete freedom and to keep running on the same map forever.

Downfall

That promise has already been broken. In 2023, the server updated to the latest Minecraft version, which was highly anticipated by all players as the server has been stuck on Minecraft version 1.12.2 for a very long time, jumping to 1.19 in a single update. As soon as it updated, it also brought along some changes: A "soft item economy reset" which deleted and limited a long list of items in all inventories, "bad items" that were snuck in using backdoors in the past have been completely wiped from existence, old chunks from older game versions with little player interaction deleted. And finally, official rules have been added, with one of the rules disallowing the "use of exploits, bugs or flawed game mechanics to cause server disruption or lag" (see more here).

Naturally, the playerbase protested against these changes, storming the server owners with requests to refund their $20 priority queue, and chargebacks in some cases. After two weeks of running on 1.19, the owners finally complied, and rolled back the map to the state it was in prior to the update after a vote, now without some of the most controversial changes. The new rules remained (renamed to "terms").

As more time passed, older players quit, newer players joined and the server continued as usual. Then, the 2b2t owners decided to implement support for Minecraft Bedrock Edition players to join as well. This may seem like a good change at first glance, allowing a wider audience to experience the server, but it had catastrophic consequences for the server. Unlike the Java Edition of the game, Bedrock Edition is heavily moderated by Microslop, so of course they could not turn a blind eye on 2b2t. The server now became stricter, censoring hateful symbols, hate speech and obscene language on signs and in chat, including swear words such as "hoe" (an actual item in the game, e.g. "Diamond Hoe" would be censored). Again, players protested against these changes, this time by placing large amounts of hateful symbols and hate speech on signs all around the server, going as far as building large NSFW pixel art in the sky, with the goal of the 2b2t admins unable to keep up and eventually have the server blacklisted by Microslop. This event ended in all symbols being removed by the server owners afterwards, with manual methods combined with automatic detections.

Archival Project

The community doesn't like the direction the server is moving in, so we decided to archive it. The owners of 2b2t can no longer be trusted to operate the server the same way it has historically been. It is no longer anarchy, and it is no longer no-reset. Considering we had already broken the largest world download record a year before, we felt confident enough to break it again. Here is the full recap:

  • In June of 2024, Fuch and I started a project to download a large area of the server. I created a custom file format to save on storage, along with a custom world download server, and a basic autopilot.
  • At the end of 2024, we launched 28 bot accounts on BMProxy to download a 512k² Overworld area, with the intent to find bases. This was achieved within 17 days.
  • In June of 2025, 2b2t finally updated to 1.21.4, and the nether roof was accidentally left enabled. We decided to use this while it was possible, and promptly launched 6 accounts to download a 100k² Nether area, finishing in under a week.
  • Right after Christmas Eve of 2025, we launched a new project to download a 1M² Overworld area of the server. We also went and manually gapfilled some missing parts of the Nether. Lastly, we downloaded a 256k² area of the End at the same time. This final project took 109 days to complete, finishing in April 2026.

You can read the full story about this project in STORY.md. See all discoveries made using the data gathered from this project in DISCOVERIES.md.

Download

Note

This world download is stored in the zvcr file format (version 1.0.0.0), which made storage extremely efficient. The data provided is NOT a single playable Minecraft world, but rather a highly compressed collection of several world downloads of 2b2t. All data is from Minecraft version 1.21.4. Blocks, biomes, and block entities included, but entities were not downloaded.

Short on storage?

You do not have to download the entire data dump to see this for yourself locally. It is split up for you to have the option to download a smaller area. See self hosting setup for more information.

Torrent

  • Torrent: https://2b2t.place/7ebd1291770e88ce41fa250463fac8e8610ebcb2.torrent
  • Magnet: magnet:?xt=urn:btih:7ebd1291770e88ce41fa250463fac8e8610ebcb2&dn=1million_2b2t&xl=15072382418944&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.publictracker.xyz%3A6969%2Fannounce&tr=udp%3A%2F%2Fzer0day.ch%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.dler.org%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.auctor.tv%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.qu.ax%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.ducks.party%3A1984%2Fannounce&tr=udp%3A%2F%2Ftracker.bittor.pw%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.corpscorp.online%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker-udp.gbitt.info%3A80%2Fannounce

The public data dump contains not only the 2026 1M² Overworld, but also the 2024 512k² Overworld, 256k² End, as well as 100k² Nether. Additionally, the Nether spawn area has been downloaded around 700 times (hourly) from December 30 2025 to January 21 2026 showing an extremely interesting progression. Overworld spawn has been downloaded up to a sidelength of 15000 blocks once or sometimes twice daily from November 12 2024 to December 11 2024. And lastly, the areas where the owner(s) of 2b2t world-edited spawn (around the start of 2026) have also been captured, with before and after snapshots.

Everyone is free to do anything (as long as permittable by law, see Notice) with this dataset, whether it's data mining, finding dupe stashes and bases, self-hosting, simply archiving, or literally anything else. We encourage everyone to use the world download to its full potential.

There are instructions below, demonstrating how to stitch together the data in a structure that official zvcr-related tooling expects. You can use the following tools to do various things with the data:

  • PlaceViewer, to view the world in-game. This world is not playable, and only acts as a read-only view, with the option to see historical data.
  • zvcr command line, to convert the world download to MCA format, and create a playable Minecraft Singleplayer world for a specific timestamp. The command line has many more features, however this would be a common use case.

Torrent Contents

Aside from just the raw zvcr data, the torrent contains many more related files.

  • /wdl, all raw world data in zvcr format, divided into rings as SquashFS images.

    • /wdl/overworld/ring1.squashfs..ring33.squashfs
    • /wdl/nether/ring1.squashfs..ring23.squashfs
    • /wdl/end/ring1.squashfs..ring8.squashfs
  • /etc

    • /etc/exports.zip, exported data from our data-mining.
    • /etc/registries.zip, game registries used for zvcr command line and PlaceViewer.
    • /etc/renders.zip, various top-down map renders of things on 2b2t's map.
    • /etc/timelapses.zip, timelapses of our bots downloading the world.
  • /README.md

  • /DISCOVERIES.md, all discoveries made so far about 2b2t's map using this data.

  • /STORY.md, the full story of the project.

  • /mount.sh, see Mounting as a Read-Only Filesystem.

  • /unmount.sh, see Mounting as a Read-Only Filesystem.

  • /extract.sh, see Extracting all Files.

Note that we slightly exceeded the initial goal of 1M² Overworld, and some areas have been downloaded up to 537000 blocks away from spawn. Additionally, we also tried surpassing the initial goal of 100k² Nether, but access to the Nether roof was unfortunately disabled before we were able to finish the extension. This is why the data in the download goes past the expected range.

See it for yourself

If you can't download this entire data dump for yourself, you can visit our 2b2t Wayback Minecraft server. You can use the /flashback command to browse through historical snapshots of the map and fly around in spectator mode or /tp to visit any coordinates. Our public service is available for free, and more historical snapshots will be added very soon. In the future, we plan to expand the historical data view as far back as 2011.

Server IP: wayback.2b2t.place (Minecraft Java Edition, 1.21.4, as of writing)

We also host a 2D Map website for anyone to browse through, although it currently does not have a historical view. You can see all three dimensions from above, along with the option to enable a Newchunks overlay to see all the chunk trails people have made over the years.

See Notice.

Setting it up locally

We highly encourage doing this on Linux, as the instructions were hand-crafted with the assumption of doing this on a server. For Windows users, you can use the Windows Subsystem for Linux (WSL) to set this up. 7-Zip technically has support for extracting SquashFS images, although that setup is extremely laborious and not at all recommended.

Data Dump Structure

We released the data in the form of multiple directories per dimension, divided into "rings" (in the form of SquashFS images). This was done to make it as flexible as possible for anyone that doesn't have 14 TiB of storage available. SquashFS was chosen to make it more user-friendly, instead of simply zipping the entire archive. A zip archive would have required double the storage: Once for the original zip, and once for the unzipped data. With current approach, space isn't wasted: you can mount multiple SquashFS images directly and then stitch them together by overlaying using something like OverlayFS.

The ring structure makes it possible to only download up to a specific radius away from spawn and still have a functional archive, just with less data. You don't need to download everything: you can select to download only up to a specific ring/radius in your torrent client. See the table of rings and their corresponding world + file sizes for a full overview.

To download an area of block sidelength S in a given dimension folder, you only need to download all rings from ring1 up to ringN, where N = ceil(S / 32768).

Each dimension directory (overworld, nether, end) contains several files named ringN.squashfs. A ring of index N contains all zvcr sectors that are exactly N sectors away from the center of the world, no more, no less. This divides each dimension up into an "onion"-like structure, where overlaying multiple rings from 1 to N together results in a full zvcr directory.

For example:

  • ring1 contains zvcr sectors 0/0, -1/0, -1/-1, and 0/-1, which are the four zvcr sectors right in the center of the world.
  • ring2, only contains zvcr sectors one further out: 1/1, 0/1, -1/1, -2/1, -2/0, -2/-1, -2/-2, -1/-2, 0/-2, 1/-2, 1/-1, and 1/0. Note that ring2 does not contain the sectors that make up ring1.

When overlaying ring1 and ring2 together, the result is a zvcr directory that contains all sectors up to 2 sectors away from the center of the world, or 2 * 32 * 512 = 32768 blocks away from 0, 0. Effectively, ring1ring2 contains a world with a sidelength of 65536 blocks.

Mounting as a Read-Only Filesystem

You can use the mount.sh script to mount all ring SquashFS images. The script also overlays all rings into one combined filesystem, which you can then use as a zvcr directory for the official zvcr-related tools that expect them.

Additionally, there are two ways to mount: either with FUSE, or using the available kernel modules. FUSE will always be more portable and it plays nicely when deployed inside a container (Docker, Proxmox, ...), however it has noticeable performance penalties. For better performance, it is recommended to use the SquashFS and OverlayFS kernel modules.

Replace /path/to/wdl with the ring directory containing the dimension directories (overworld, nether, and end).

Usage: ./mount.sh -s </path/to/wdl> [-t </path/to/wdl-mount>] [-o <max-ring-overworld>] [-n <max-ring-nether>] [-e <max-ring-end>] [-r </path/to/ring-mount>] [-u]
Options:
  -s </path/to/wdl>         Path to directory containing overworld, nether, and end directories (required)
  -t </path/to/wdl-mount>   Mount point for the final merged directory (default: /mnt/wdl)
  -o <max-ring-overworld>   Maximum ring for overworld (default: 33, set to 0 for none)
  -n <max-ring-nether>      Maximum ring for nether (default: 23, set to 0 for none)
  -e <max-ring-end>         Maximum ring for end (default: 8, set to 0 for none)
  -r </path/to/ring-mount>  Mount point for individual rings (default: /mnt/ring)
  -u                        Use FUSE-based tools (squashfuse + mergerfs) instead of kernel (squashfs + overlayfs)
  -h                        Show this help message

To unmount, use the unmount.sh script.

Usage: ./unmount.sh [-r </path/to/ring-mount>] [-t </path/to/wdl-mount>]
Options:
  -r </path/to/ring-mount>  Mount point for individual rings (default: /mnt/ring)
  -t </path/to/wdl-mount>   Mount point for the final merged directory (default: /mnt/wdl)
  -h                        Show this help message

Extracting all Files

Extracting all files is not recommended for read-only use cases, as it requires a lot of spare additional storage for the extraction process, and takes a lot more time to set up. If your use case is strictly read-only (such as simply viewing the world in-game, data-mining, block searches, exporting to MCA, renders, etc.), we strongly recommend using the method described above instead.

For different use cases, or for even better performance, you may not want to mount the data as a read-only filesystem. You can use the extract.sh script to simply extract everything into a directory instead. The script also seamlessly merges all rings into one combined directory, which you can then use as a zvcr directory for the official zvcr-related tools that expect them, similarly to the OverlayFS method above.

Usage: ./extract.sh -s </path/to/wdl> -t </path/to/wdl-mount> [-o <max-ring-overworld>] [-n <max-ring-nether>] [-e <max-ring-end>]
Options:
  -s </path/to/wdl>         Path to source directory containing overworld, nether, and end directories (required)
  -t </path/to/extracted>   Path to target directory to extract all rings into (required)
  -o <max-ring-overworld>   Maximum ring for overworld (default: 33, set to 0 for none)
  -n <max-ring-nether>      Maximum ring for nether (default: 23, set to 0 for none)
  -e <max-ring-end>         Maximum ring for end (default: 8, set to 0 for none)
  -h                        Show this help message

Ring Size Tables

Each ring ringN in this table refers to a given ringN.squashfs image. Corresponding world sizes are measured by the sidelength (in blocks) of the quadratic area created by stitching ring1..ringN together. These sizes can be used as a lookup to see which rings you need to download to get up to a certain area.

Overworld

Downloaded fully up to an initial goal of sidelength = 1,024,000 in 2026, although some parts have been slightly extended past this goal, up to 1,074,000 instead.
Downloaded fully up to an initial goal of sidelength = 512,000 in 2024, although some parts have been slightly extended past this goal, up to 620,000 instead.

The dataset includes both of these snapshots, stored using zvcr reverse deltas.

Ring Number (N) World Size (sidelength) Ring File Size Combined File Size for Rings 1..N 1 32,768 11.59 GiB 11.59 GiB 2 65,536 31.47 GiB 43.06 GiB 3 98,304 53.96 GiB 97.25 GiB 4 131,072 80.44 GiB 177.47 GiB 5 163,840 107.71 GiB 285.17 GiB 6 196,608 133.71 GiB 418.89 GiB 7 229,376 159.38 GiB 578.27 GiB 8 262,144 187.27 GiB 765.54 GiB 9 294,912 215.77 GiB 981.31 GiB 10 327,680 244.78 GiB 1.20 TiB 11 360,448 275.60 GiB 1.47 TiB 12 393,216 311.13 GiB 1.77 TiB 13 425,984 340.75 GiB 2.10 TiB 14 458,752 376.90 GiB 2.47 TiB 15 491,520 414.14 GiB 2.88 TiB 16 524,288 442.02 GiB 3.31 TiB 17 557,056 466.85 GiB 3.76 TiB 18 589,824 490.29 GiB 4.24 TiB 19 622,592 494.43 GiB 4.72 TiB 20 655,360 521.59 GiB 5.23 TiB 21 688,128 549.97 GiB 5.77 TiB 22 720,896 580.70 GiB 6.34 TiB 23 753,664 609.97 GiB 6.93 TiB 24 786,432 639.17 GiB 7.56 TiB 25 819,200 665.05 GiB 8.21 TiB 26 851,968 692.55 GiB 8.88 TiB 27 884,736 723.77 GiB 9.59 TiB 28 917,504 751.73 GiB 10.32 TiB 29 950,272 780.49 GiB 11.09 TiB 30 983,040 809.45 GiB 11.88 TiB 31 1,015,808 836.82 GiB 12.69 TiB 32 1,048,576 705.58 GiB 13.38 TiB 33 1,081,344 209.63 GiB 13.59 TiB

Nether

Downloaded fully up to an initial goal of sidelength = 100,000 in 2025. An attempt to extend to a sidelength of 300,000 blocks was made, although never completed, as access to the Nether roof was prematurely disabled. A few parts have been extended to this sidelength, and a trail (which was accidentally downloaded) reaching 376k blocks away from spawn was also included in this data. Note that due to faulty and rushed bot software, many chunks were missed. Some gaps were filled at the start of 2026, although some still remain.

Ring Number (N) World Size (sidelength) Ring File Size Combined File Size for Rings 1..N 1 32,768 3.75 GiB 3.75 GiB 2 65,536 11.72 GiB 15.47 GiB 3 98,304 21.08 GiB 36.55 GiB 4 131,072 24.29 GiB 60.84 GiB 5 163,840 3.12 GiB 63.96 GiB 6 196,608 3.58 GiB 67.54 GiB 7 229,376 3.91 GiB 71.45 GiB 8 262,144 4.07 GiB 75.52 GiB 9 294,912 4.35 GiB 79.87 GiB 10 327,680 770.72 MiB 80.62 GiB 11 360,448 18.48 MiB 80.64 GiB 12 393,216 35.64 MiB 80.68 GiB 13 425,984 28.27 MiB 80.70 GiB 14 458,752 41.30 MiB 80.75 GiB 15 491,520 33.49 MiB 80.78 GiB 16 524,288 29.75 MiB 80.81 GiB 17 557,056 45.74 MiB 80.85 GiB 18 589,824 51.20 MiB 80.90 GiB 19 622,592 32.69 MiB 80.93 GiB 20 655,360 40.02 MiB 80.97 GiB 21 688,128 45.42 MiB 81.02 GiB 22 720,896 54.09 MiB 81.07 GiB 23 753,664 43.06 MiB 81.11 GiB

End

Downloaded fully to sidelength = 256,000 in 2026.

Ring Number (N) World Size (sidelength) Ring File Size Combined File Size for Rings 1..N 1 32,768 443.97 MiB 443.97 MiB 2 65,536 1.34 GiB 1.77 GiB 3 98,304 2.23 GiB 4.00 GiB 4 131,072 3.11 GiB 7.11 GiB 5 163,840 4.00 GiB 11.11 GiB 6 196,608 4.88 GiB 16.00 GiB 7 229,376 5.78 GiB 21.77 GiB 8 262,144 5.41 GiB 27.19 GiB

Clarifications

Wasn't this data previously revealed to be over 20 TB in file size? Why did the torrent take so long to release?

The old filesize of the entire dump was around 24 TiB. This was stored in an older version of zvcr3d (0.1.4.0), and the 2024 512k² world download was an entirely separate directory (around 4.2 TiB standalone). The 24 TiB filesize also included older zvcr2d files (over 1 TiB), as well as an extremely bloated database containing all tile entities, both of which have now been deprecated.

We managed to reduce the filesize by:

  • Upgrading all files to zvcr3d 1.0.0.0; This alone already gave a 14% reduction in filesize, see the zvcr release 1 changelog.
  • Merging the 2024 512k² data into the same zvcr data dump using zvcr reverse deltas. The 2024 + 2026 delta data merge only added around 231 GiB to the total file size, rather than the full 4.2 TiB, showing how efficient the file format can be with historical data.
  • Importing all tile entities into zvcr directly instead of using a database.

Combining all of these improvements reduced the total file size to around 13.694 TiB. This was some of the work that was happening behind the scenes and it is partly the reason this release has taken so long.

Is this a repost of the recent 200k² project?

This is a separate project with separate goals, that coincidentally happened to release around the same time. I want to sincerely apologize to CrisisSheep and pawstar for the incredibly unfortunate timing. Their 200k² world download is a big accomplishment and shouldn't be ignored, even if ours is much larger. You can check out their world download and torrent in their Reddit Post.

200k² Downloads

  • Torrent: https://github.com/pawbase2b2t/2b2t.org-200k-Spawn-Download-2026/raw/refs/heads/main/2b2t%20200k%C2%B2%20Spawn%20Download%20-%202026.torrent
  • Magnet: magnet:?xt=urn:btih:13f0d8e0ddc577b42b0b471580b479b5b9b95df3&dn=2b2t%20200k%C2%B2%20Spawn%20Download%20-%202026&xl=831592343945&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.qu.ax%3A6969%2Fannounce&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.moeking.me%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker1.bt.moack.co.kr%3A80%2Fannounce

Credits

First and foremost I want to thank Fuch, for funding most of the operation through his hard work and dedication. Spending this much money on block game archival is just absolutely insane and none of this would've ever happened without his help. So much was spent on priority queue purchases alone, but also server rental costs, adding up to over $3000. Beyond funding, our combined autism has been a great motivating factor to finish this project in particular, out of all the other ideas we had in mind and never got around to.

Secondly, I want to thank Steve3 and mahan for their huge help providing a separate BMProxy instance just for us throughout all the world downloads we have done over the years. A big thanks goes to mahan in particular, for helping me extensively stress test our private software (world download server, proxy, autopilot, zvcr file format), and having to port my (honestly very horrible) autopilot code from the prototype Meteor addon to BMProxy. Along with this, he provided a lot of great ideas that were implemented into the 2b2t.place software. Working with mahan has been the most enjoyable experience from all my previous development teamwork ever, and without his and Steve's help, this project would have taken at least another additional year to complete.

A huge thank you to VADemon, DaPorkchop, Fionera, and obvTiger for providing insightful ideas and feedback on how to release the data to the public. The use of SquashFS + OverlayFS helped tremendously in this release. Additional thanks to Fionera for generously providing a server to do all of the data preparation on, VADemon for providing many detailed ideas for data distribution as well as helping with the social aspect and very deep technicals of this release, and to obvTiger for providing alternative data distribution methods as well as viable seedbox options to use. I would also like to thank Scaevolus, for the suggestion of rounding up bitsPerEntry to align with byte boundaries in the zvcr file format. This change reduced the file size of this data dump by an impressive amount (around 14%) and saved over 1.3 TiB of storage. I would like to thank Purple-blog as well, for the feedback and criticism regarding the zvcr file format, which helped with a cleaner first release of zvcr.
Lastly, thank you to all the people that started seeding the Torrent in time for the announcement: Jelle, obvTiger, Philipp, DaPorkchop, _m_o_t_h_r_a_, Fuchs, VADemon, and Fionera.
Thanks to all of these people, the data is available via torrent and extremely flexible to download & self-host. Without them, the public release would have been nowhere near as user-friendly.

A huge thank you to SalC1 for making the YouTube video surrounding this world download project and spending countless hours revising the script, editing, and going back and forth with us to make the video as high quality and perfect as possible. This was despite him being originally done with 2b2t altogether, due to the censorship done by the server owner(s), and, of course, due to Microslop. But given that he has a huge interest in archiving internet history, he was still willing to make a video about our project, and I am very, very grateful for that. You can see he poured his heart and soul into the video.

I want to thank all members of the Enclave that helped with the initial 512k² project in 2024. Many members contributed their alternate Minecraft accounts for use in the project, adding up to 28 accounts in total. Thank you to Fuch, Cody4687 and expunged, who funded a large portion of the priority queue costs during that project ($600+). I want to especially thank catgirlmatty for being extremely supportive of this project and creating several videos about it. I want to thank her also for inviting me to the Enclave (given our shared interest in world downloading 2b2t, even if with entirely different motives), and introducing me to Steve3, mahan, expunged, candyking24, Cody4687, WhiteAtlas, and Highatwork. The entire "Dofnear Exploit 2" larp was the funniest shit I've ever been a part of (although it may have slightly deceived the community... I guess this giant wall of text clears things up a little bit). Operation Fact Check (OFC) as an organized project fueling Dofnear larp was extremely fun as well, and you can thank expunged and catgirlmatty for the success of it; both of whom led the operation, with expunged managing most of the spreadsheet work and sending batches of coordinates to fact-checkers. An astounding 2100+ locations were manually checked in-game by all contributors, giving a final yield of over 600 dubs of stolen items, which is simply absurd.

Lastly, a huge thank you to AustinGraphics, for creating the frontend for the https://2b2t.place website. His dedication to this project made it possible to host the full map of this project for everyone to see. I have never personally done any frontend development on my own, and I could have never made a website as cool as this.

Additional thanks and shoutouts to the following people, that also helped in one way or another:

  • antonymph, for providing accounts during the 2024 512k², fact-checking many locations during OFC, being supportive all throughout and correcting me on some wrong info that I gave to SalC1
  • EastAlpha and Philipp, and the other members of The Devs, for coordinating with us on properly timing the releases of the world download, and the their group logo project, also making the final render of the map way more interesting
  • candyking24 and Russianxd, for helping check and filter many locations from the 1M², providing many interesting ideas for the data-mining process, and being supportive in general throughout the project. Additional thanks to candyking24 for having a bot automatically log into 2b2t at Nether spawn every hour throughout January 2026 to add an astonishing 700 snapshots of the 2b2t Nether hub to this archive.
  • galicaea and candyking24 for their incredible singing used in the Enclave's Dofnear YouTube video
  • H202 for staying supportive and not leaking the project, even after leaving the group, and for originally bringing up the funny idea to rename some of the used accounts after criminals named John.

Thank you to the OFC fact-checkers that contributed to the 600+ dubs of stolen items: mikuexpo, antonymph, galicaea, catgirlmatty, ilovelucki, Zandax, uh_sid, candyking24, browngirl, expunged, HighAtWork, KayQuack, WhiteAtlas and mahan.

My role in the Project

It doesn't feel right to credit myself (crayne), but in case you're curious, I wrote the code for:

  • the custom file format used to store the downloads (zvcr),
  • PlaceProxy and the PlaceTools mod, which will soon allow anyone to contribute to our public archive,
  • the 2b2t Wayback Server, powered by PlaceViewer and zvcr, letting you flashback in time on the 2b2t map,
  • the elytra autopilot(s) (an older version using the Boost elytra flight exploit, a newer version using Pitch40),
  • the data-mining program, to find all sorts of interesting things,
  • core utilities to do operations with zvcr and mca region files (merge, render, upgrade, export, import, ...).

No AI/LLM's were used in the making of this project. Or writing this giant wall of text, if it gave you that impression.

A Final Note

Although I probably spent the most time working on the code for this project, everyone involved deserves recognition, because every contribution helped make this possible. I spent the better part of 1.5 years developing most the code used for the world download, and this has been extremely exhausting. It would not have been possible to achieve all of this if it wasn't for everyone mentioned above. I hope the community can make great use of the data provided by us, whether to make the 2b2t experience more enjoyable or to have a backup plan to give the server into the hands of the community, just in case the 2b2t server owner(s) decide to make changes no one likes. It'll be interesting to see what the players can come up with.

- crayne

Notice

This dataset is provided for non-commercial preservation and research purposes only. It is shared without warranty of any kind. Users are responsible for complying with applicable terms of service and laws. This release is not affiliated with, nor does it constitute endorsement by, Mojang Studios, Microsoft, or the operators of 2b2t.org.

The Minecraft world data contains user-generated content created by third parties. No ownership or copyright over this content is claimed by the publisher, and no license is granted for reuse of that content.

All scripts, documentation, and derived works created by the project maintainers strictly within this repository and the released torrent are licensed under CC0 1.0 Universal.

The "2b2t Wayback Minecraft server" refers to an unofficial community archive (available to the public in the form of a Minecraft server) specifically preserving the Minecraft server 2b2t.org and is not affiliated with, nor does it constitute endorsement by, the Internet Archive, the Wayback Machine, Mojang Studios, Microsoft, or the operators of 2b2t.org.

The Daily Front Page 19 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Phones and Bystanders
article

The kids with phones are alright

by JumpCrisscross·▲ 254 points·345 comments·heatherburns.tech ↗
It really is worth a watch.

There was a remarkable video going around the Scottish socials yesterday which led to quite a bit of media coverage. It’s a four-minute long video showing some furious Scotrail passengers confronting a drunk middle-aged pervert who is quietly filming a group of quiet, sober 16- and 17-year old girls heading home from a night out.

It really is worth a watch, because it’s a four-minute long documentary masterclass in bystander action, documenting offenses, holding perpetrators to account, centering the victims, and somehow staying calm and focused even when you want to batter the fuck out of someone who has it coming.

(Disclaimer: the spoken dialogue in the video is not in the King’s English, it’s in our local Glaswegian dialect, which…some of you seem to struggle with. As you’re also aware by now, in our dialect, we use obscenities like other people use punctuation. Just so you know.)

https://heatherburns.tech/wp-content/uploads/2026/07/ssstwitter.com_1783405302508.mp4

Of course the pervert turns out to be a senior legal officer at Edinburgh City Council. Of course he was. Hold on to that thought, it matters.

Right then Heather, what on earth does a video of an alkie on a train have to do with tech policy?

A lot, as it turns out.

The first point is that the passengers instinctively confront and document the person inflicting the harm. At no time do they castigate the teenage girls being filmed without their knowledge or consent – young women who are absolutely entitled to any night out they please – or imply that it’s the girls’ conduct at fault. The whole train has the girls’ backs.

This runs against the grain of 2026 tech policy, which decrees that it’s young people who need their behavior censored and constrained, in ways that punish them for the actions of the perpetrator.

The second point is that as far as policymakers are concerned, it’s the senior legal officer with the phone who is rightfully entitled to have the phone and use it any way he pleases. If the girls on a night out are allowed to have a phone at all – and there are areas of British society which do not believe they should – then their usage of them must be strictly controlled, censored, and curtailed, allegedly for their own protection.

Those policy arguments are shielded in the need to take back control from Big Tech. But what happened on that train, as you saw, had nothing to do with Big Tech, aside from the video of the incident being shared there. That incident was about the dynamics of power, privilege, and dominance, as they play out every day in the real world.

And it’s about time we talked about that.

I have written before about how the raft of legislation aiming to control what young adults can do with their phones, essentially punishing them for the sins of Big Tech, can put their immediate personal safety at risk from dangers far more violent than algorithms.

Many other people, observing our current policy context, have also called out how smartphone and social media bans for young adults (and we are talking about that particular group here, not toddlers and primary schoolers) risk swaddling them in cotton wool and then releasing them into the world, without critical adulting skills, on the day they hit a magic birthday. Those girls on the train clearly have been allowed to develop some excellent adulting and resilience skills.

Not all young people are that lucky, by design and – as things currently stand – by policy aspiration.

We don’t talk enough about how the impetus for the most authoritarian internet regulation in the UK, including the <16s ban, has come from the affluent/wealthy/1% elite on the political right wing, or as I’ve called them elsewhere in this blog, the Tatler/Telegraph/Times set. It’s easy to understand why: the upper classes have always objectified their children into possessions. These are groups who, culturally, do not want their children to have agency over their own lives, nevermind devices, nor do they want their young adults to develop essential skills to live their lives as independent adults. They never have. And they never did themselves, which is why the notion of blanket bans come so easily to them. That is also why the notion of living one’s life with total personal agency, grounded in confident resilience, without the continuous daily intervention of third party intermediaries (mummy and daddy for life, age verification services, content screening services, safety tech, employees, staff, nannies, au pairs) is, to those people, completely and utterly incomprehensible.

In other words, the cultural values that a detached elite wish to impose on their offspring for life have been transposed into law, policy, and regulation, and from there, into the personal lives of young people who are living in very different cultural circumstances, in very different places, with very different mental maps of how their lives are going to play out.

We are allowing cosseted snobs for whom “if you called your dad, he could stop it all” is the only truth they have ever known in their lives, and ever will, decree how some teenagers on a train hundreds of miles away should be able to react when they are being targeted by a predator in a small, confined space.

That’s why this video matters.

What you are seeing in this video is an inflection point. It’s watching policy arguments crumble on their first contact with the real world. It’s watching how power works on paper, vs how power works in the real world, and how that balance can invert itself in a second. It’s watching lines of carefully constructed public relations dogma being thrown on their heads. It’s watching working-class teenagers who have far better heads on their shoulders than the elite pervert filming them in public. It’s watching how personal security plays out for women, in real time, in the real world.

It’s watching precisely why young adults need phones, the agency to use them, and the life skills to make their ways in the world with their phones in their pockets.

And as always, it’s watching a smug perpetrator scamper away, safe in the confidence that his privilege will protect him from the consequences of his actions, knowing he can look forward to many years of insisting, to anyone who will still give him the time of day, that he and only he is the real victim here.

Hey girls, this weekend: go out exactly as you were. Go do exactly what you did last weekend. Go have fun. Keep your phones on you, keep your wits about you, and you’ll be fine. You’re going to have an amazing life.

Enjoy it.


On a personal note, I am amazed that the video did not cause me to have nightmares last night, because that guy – the pervert perpetrator – is a carbon copy of my alcoholic ex-husband. Not the filming the girls part, the smarmy alkie part. He looks nothing like my ex-husband, but watching that video was like being back in front of him again. That smug, in-your face grin even as he’s been caught out, the arrogance of someone who’d never been told “no” in his life by anyone except me, the haughty confidence of someone who knows his Scottish white male privilege will slap him on the back and protect him from any consequences, on top of the paralytic drunkenness replete with a liquid mess down his shirt (and undoubtedly a solid mess in his boxers too, which was always my job to clean up) – that’s what I lived with every day of my life in my own home for years.

You wonder why I was always tired and fragile and escaping to conferences. You wonder it’s why I can spot an alkie (not a problem drinker, an alkie) from a mile away.

Cos I gotta tell you, you learn as much about the dynamics of power, dominance, and control living with one of these people as you do in the corridors of Whitehall. You learn how to spot those dynamics, buried deep in code of practice footnotes and sub-paragraphs of draft regulations, as easily as you know how to spot the glazed eyes and the smug grin.

Written and posted from a much better and more beautiful place, in every sense.

The Daily Front Page 20 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Before the Camera
article

An Englishwoman who sketched India before photography took hold

by 1659447091·▲ 222 points·68 comments·bbc.com ↗
Long before photography became the visual language of empire.

DAG A group of men and women, 1844

Sketched in Shimla, this family is thought to be from Ladakh, reflecting the dress and jewellery of Himalayan traders who crossed the mountain passes each summer

Long before photography became the visual language of empire, one Englishwoman was sketching the people she encountered across India with unusual curiosity and precision.

Emily Eden, a gifted artist and writer, belonged to one of Britain's most influential political families. She travelled across northern India in the 1830s while accompanying her brother, George Eden, first Earl of Auckland, the governor-general of India.

Alongside princes, generals and courtiers, she drew servants, attendants, travellers, fakirs, Afghan and Sikh nobles, Akali warriors, hill communities and even the animals that accompanied imperial journeys. Her remarkably broad gaze set her apart from many contemporaries.

More than two dozen of her sketches were published in 1844 as Portraits of the Princes and People of India. They now form the heart of Princes & People, an exhibition at DAG in Delhi curated by art historian Mary Ann Prior, bringing together the complete published series of hand-coloured lithographs made from Eden's original sketches.

Eden arrived in Calcutta (now Kolkata) in March 1836 to a whirlwind of official engagements and an unfamiliar world. Homesick and struggling to adjust, she did not sketch for three weeks or complete a painting for two months.

But her spirits were buoyed by her travelling party, which included her nephew William, sister Fanny, maids, an ayah, a cook, a valet, a physician and an assortment of pets.

Even before reaching India, the voyage had begun to broaden her outlook as she encountered new people, cultures and ways of life, notes art historian and author Mary Ann Prior.

"The diversity of people and places motivated and improved Emily's artistic output, and her natural curiosity sought out the unusual. She meticulously documented her observations through her sketches and paintings." Instead of castles, churches and English landscapes, Eden increasingly turned her attention to the strangers, costumes, architecture and unfamiliar landscapes she encountered.

Between 1836 and 1842, that curiosity took her across a region on the cusp of profound political change. Her sketches offer a rare glimpse of the court of Maharaja Ranjit Singh, whose Punjab kingdom was one of the subcontinent's most powerful states, capturing it at the twilight of his reign and the dawn of the Victorian era.

Eden was as engaging a writer as she was an artist. Her lively journals brim with humour and observation, with names and places often spelled exactly as they sounded to her.

On arriving in the holy city of Benares (now Varanasi), the Eden party sailed along the Ganges before continuing to nearby Ramnagar, where the king had a country house. The scene so captivated Emily that she wrote: "We mean to keep our steamer here and to go out sketching in it."

Her enthusiasm had not come immediately. At first, the cultural gulf between England and India left Eden deeply homesick. She fretted over women attending church without bonnets, ravenous mosquitoes, relentless heat, the cacophony of dogs, crows, jackals and Brahminy kites, and being confined indoors for much of the day.

But as the months passed, she drew prolifically, and her paintings soon became a success, selling briskly at charity fairs in Shimla, winning admiration from the British in India and being copied by Indian artists.

Prior ranks Eden's Indian sketches among the finest produced by any British woman artist of the Regency and Victorian eras. Only Charlotte Canning, celebrated for her botanical paintings, and later Marianne North rivalled her achievement.

Yet Eden's remarkable powers of observation coexisted with an unshaken belief in Britain's civilising mission. As Prior writes, she viewed her years in India as "an unwelcome ordeal to be endured for a higher purpose", framing colonial rule as an obligation to "civilise" the country.

The Edens left India in 1842, looking forward to returning to England. Back in Britain, Emily continued to paint, although the urgency that had driven her to record unfamiliar landscapes and people in India had faded. Her later works were fewer and turned to familiar English scenes.

Writing increasingly became the vehicle through which her Indian experience reached a wider audience. Up the Country, her lively letters from India, was published in 1866, followed by Letters from India in 1872.

Although her reputation was initially became entwined with her family's association with the First Afghan War, it gradually came to rest on her own accomplishments as both writer and artist. Emily Eden died in 1869.

DAG On the left, Eden portrays the Christian convert Anand Masseh, a teacher. On the right, King Fateh Prakash is shown with his sons in traditional dress and jewellery. Together, the portraits reflect Eden's fascination with the diverse people she encountered across northern India.

On the left, Eden portrays the Christian convert Anand Masseh, a teacher. On the right, King Fateh Prakash is shown with his sons in traditional dress and jewellery. Together, the portraits reflect Eden's fascination with the diverse people she encountered across northern India

DAG Runjeet Singh, 1844

Eden sketched Ranjit Singh, founder and first maharaja of the Sikh Empire, in 1838, shortly before his death, capturing his quiet dignity and enduring authority

DAG Dost Mahomed Khan, and part of his Family, 1844

Dost Mohammad Khan, the exiled amir of Afghanistan, was a key player in the 19th-Century "Great Game", a political rivalry between the British and the Russians for dominance in Central Asia. Eden sketched this portrait of Khan (right, top) and his family - two sons and a cousin - when they were living in exile in India after British intervention in Kabul

DAG Servants with dogs and hawks belonging to the King of Oude, 1844

Animals fascinated Eden, particularly exotic species and those with elaborately dressed handlers, including the King of Oudh's hunting leopards, hawks and hounds

DAG Attendants on the Raja Khurruk Singh, 1844

Attendants of Kharak Singh, the eldest son of Maharaja Ranjit Singh, sketched after Eden visited the Sikh court in 1838. Their elegant robes, turbans and embroidered shoes exemplified the distinctive attire that fascinated the artist. In letters written that year, Eden repeatedly described the Sikhs as "picturesque", praising their "beautiful figures"

DAG Dhullo (left) and Dedar Khan (right), the head servants at Government House in Calcutta, are shown in their winter livery. As senior attendants to Lord Auckland, they alone wore daggers at their waists, while, like all Indians at Government House, they remained barefoot indoors.

Eden sketched Dhullo (left) and Dedar Khan (right), the head servants at Government House in Calcutta, in their winter livery. As senior attendants to Lord Auckland, they wore daggers at their waists but remained barefoot indoors

DAG Two Arabs, 1844

Eden sketched these Pashtun men, who had travelled from Kabul to Shimla with a British officer, wearing the traditional white shalwar kameez and turban of Afghanistan. She described the encounter as a meeting with "two Arabs"

DAG The Bridge of Jupiter, 1835

Eden filled a sketchbook with watercolours made aboard HMS Jupiter on her voyage to India, drawing the sailors who helped brighten a difficult journey

DAG Akalees, 1844

Eden's portrait of the Akali Nihang warriors captures the Sikh army's distinctive warrior order, recognised by their towering turbans, blue robes and steel throwing weapons

DAG Eden sketched the wealthy Muslim student (left) in Calcutta (now Kolkata), noting his insistence that his bracelets and jewellery - pearls, emeralds and all - were his own, not his father's. The portrait on the right shows the daughter of a government servant servant, dressed in the then-unusual salwar-kameez with jewellery, an embroidered cap and a rattle.

Eden sketched a wealthy Muslim student (left) in Calcutta, noting his insistence that his bracelets and jewellery - including pearls and emeralds - were his own, not his father's. On the right is the daughter of a government servant, dressed in the then-unusual salwar kameez with jewellery, an embroidered cap and a rattle

DAG Emily Eden, silver gelatin print on paper pasted on paper, 1860s

Eden was not only as a prolific artist, but also a witty writer

The Daily Front Page 21 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — White-Chested Fox
article

Mathematical texts from a Maya site in Guatemala identify an ancient astronomer

by homarp·▲ 111 points·26 comments·nature.com ↗
An astronomer called Sak Tahn Waax.

Mathematical texts from a Maya site in Guatemala identify an astronomer called Sak Tahn Waax.

The well preserved Temple of the Great Jaguar in Tikal, Guatemala. The form is a distinctive pyramid shape with stepped design in a forest clearing.

The Maya temple Tikal in Guatemala is about one day's walk from Xultun, where researchers discovered mathematical formula scribbled on the walls.Credit: Kryssia Campos/Getty

A mathematical formula inscribed on a wall at the Maya site of Xultun in Guatemala has revealed the name of an important Maya mathematician-astronomer for the first time. Researchers suggest Sak Tahn Waax, or ‘White-Chested Fox’, was a scholar comparable with mathematical giants of the past.

In a study published 14 July in the journal Antiquity1, Heather Hurst, an archaeologist at Skidmore College in Saratoga Springs, New York, and her colleagues describe their analysis of a mathematical text from a chamber in Xultun that was originally excavated in 20112.

The chamber’s walls are painted with human figures and hieroglyphic texts. These include mathematical calculations based on astronomical calendars, which were used by the Maya people to decide the timing of events such as the inaugurations of kings. Hurst and her colleagues suggest that the chamber was a workspace for scribes making codices in the mid-eighth century ad.

The authors analysed one set of hieroglyphs in particular, referred to as Text 19. Hurst says that this set of mathematical calculations expresses the relationships between several calendar systems in a playful manner that hasn’t been seen before in Mayan texts. “I think it was a mathematical flex. Somebody was saying ‘I’ve got this amazing pattern, and it’s so good it needs to be written down’. It was like, ‘Boom! Mic drop!’,” says Hurst.

“The discovery shows people that the Maya were very clever, creative, intellectually curious people who taught and learnt and sometimes did math for the sake of it,” says Eric Heller, an archaeologist at the University of Southern California Dornsife.

Identity revealed

Text 19 is a small, L-shaped group of eleven hieroglyphs with a combined height of about 10 centimetres. Hurst and her colleagues found that the first nine hieroglyphs of the set encode the Maya calendar and astronomical cycles.

The formula shows how one 2,920-day cycle could be divided up into the calendar units used by the Maya people. This 2,920-day cycle was important because it tied together key astronomical cycles, corresponding to both five Venus cycles (584 days each) and eight solar years (365 days each). However, the Text 19 calculations also relate the 2,920 days to Uinal (months with 20 days), *Tzolkin (*the 260-day sacred calendar), Tun (a year with 360 days) and Mars years of 780 days.

“It’s just super nerdy math,” says Hurst. The hieroglyphs also show only partial dates, which made them hard to decipher. “They’re doing this abbreviated shorthand, so they give you the first half of a notation and the second half is implied.”

An infrared black and white image of the Text 19 from Xultun clearly showing the glyphs.

The mathematical formula on Text 19 appear as glyphs.Credit: Photograph by G. Ware, courtesy of the San Bartolo-Xultun Regional Archaeological Project

Until now, the identities of the mathematician-astronomers behind such calculations had remained mysterious. Hurst and her colleagues found a phrase in the penultimate hieroglyph in Text 19 meaning “so says”. This was followed by the name Sak Tahn Waax, in the final hieroglyph, suggesting that the writer was taking or giving credit for the calculation. “We know it’s a male name because it’s missing a prefix,” she says.

The fact that the writer is named is significant, because it suggests that mathematicians were recognized in Maya society as much as artists were, says Gerardo Aldana, an anthropologist at the University of California, Santa Barbara.

References

  1. Rossi, F. D., Stuart, D. & Hurst, H. Antiquity https://doi.org/10.15184/aqy.2026.10378 (2026).

  2. Saturno, W. A., Stuart, D., Aveni, A. F. & Rossi, F. Science 336, 714–717 (2012).

The Daily Front Page 22 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Our Amish Language
article

Our Amish Language

by NaOH·▲ 104 points·89 comments·thedial.world ↗
Now I’m trying to preserve it.

As my community modernized, we used Pennsylvania Dutch less and less. Now I’m trying to preserve it.

There are linguistic tics and habits that give away formerly Amish people immediately. I remember, for example, when I trained myself into saying “seven” instead of “saven.” I still think of it sometimes when I say the word, how it used to sound on my tongue, slow and sloping instead of quick and peaked, the way hooche Leit or “non-Amish people” always said it. This, and other quirks of pronunciation or vocabulary, are easy tells when accents have otherwise smoothed into plain old American and traditional clothing has been shed in favor of jeans and T-shirts. It’s not just speech, either — there’s something in people’s manner, the way they hold themselves. “I could be at a bus station anywhere in the United States and I can pick out an Amish person who’s not wearing Amish clothing,” Benjamin, who is married to my first cousin once removed, told me.

Most of my family fall into this category. We’ve been Amish as far back as our records go. My father’s Amish ancestor Samuel Mueller migrated to Pennsylvania in the mid-18th century. Nearly 250 years later, in 1992, my dad’s family established a new community in Libby, in Northwest Montana. Its main language was Pennsylvania Dutch, as in almost all Amish communities. The Libby community was intended to be traditionally Amish, but with more spiritual openness. Members might be led by the Holy Spirit to speak in tongues or prophesy, for example, or to dance during worship. Within a couple of years, the new community was interacting with local, non-Amish churches, and hosting new kinds of Bible studies and weekly meetings.

At first, the Libby community had no intention of relaxing its traditional practices, even though its theological beliefs were shifting. But it became harder and harder for some members to keep refusing modern conveniences (cars, radios) and clothing (zippers, bright colors), as they believed less and less that the state of their souls was tied to their outward appearance. Since everyone was modernizing, albeit at different speeds, members of the Libby community did not have to fear the serious consequences of formal public rebuke and ostracization that happen in other Amish communities when a member breaks church rules.

Parts of the Libby church split, with some families moving away because they wanted to modernize all at once, and others because they wanted change to happen more slowly. Those who remained continued the hard process of deciding what changes to allow in their own families. In 2004, the first and only major communal ruling came about, to allow cars. A few years later, people slowly began trying contemporary clothes. For the men, this wasn’t so dramatic — often a pair of jeans and a different buttoned shirt. For the women, it was more complicated. Many continued sewing their own dresses, but incrementally made items that were more colorful or elaborate, or had different silhouettes. Others tried simple, store-bought dresses or blouses with floor-length skirts. Despite my pleading, I wasn’t allowed to wear pants until I was nearly a teen.

English started to be used more frequently in Libby, alongside Pennsylvania Dutch. By the time I was born in 2000, members of the community were associating more regularly than ever with hooche Leit. Non-Amish religious leaders from town would meet with those who wanted their input on matters of faith. Running the family grocery store meant we all rubbed shoulders with customers who spoke no Pennsylvania Dutch. The community had a tiny selection of English-language VHS tapes and DVDs, along with a VCR, which was passed around family to family, slyly at first. (I remember watching Mel Gibson’s “The Passion of the Christ” from behind the sofa soon after it came out, terrified.) All this exposure meant I learned English before I could remember doing so, and was fluent in both languages.

In 2008, there was a first young person in the Libby community to marry someone who didn’t speak Pennsylvania Dutch. Five years later, another. My non-Amish sister-in-law joined family events at a young age; she and my brother married in 2016, when they were both 18. (They had met three and a half years previously, when her family moved into a house up the road from our church.) At our Miller Christmas party, or a summer backyard dinner, my relatives would speed through conversations in Deitsch (the name for our language, in our language) while she sat awkwardly at the edge. I stayed next to her, murmuring pieces of different exchanges to help her understand the tone of the room, the kinds of interactions that were unfolding. That was a decade ago; now that so many more cousins have married non-Amish partners, English pervades most family spaces and occasions.

When I was at college in California, far removed from the environment I had grown up in, I became fascinated by my community’s transition away from traditionalism. I wanted to find some way of recording it and so, in my last semester at Berkeley, I applied for a grant to fund an oral history project. I would interview around 30 people from my community on video, speaking with them in Pennsylvania Dutch, and translate every exchange into English for subtitles.

It was important to me that the interviews be in Deitsch. There are few audiovisual recordings of native speakers: Most traditional Amish don’t allow themselves to be photographed or video recorded, as it violates their interpretation of the scripture forbidding the making of any “graven image.” The interviews will serve as a repository of language and culture, and the way that both have evolved over the past decades. I now have over two dozen hours of native speakers telling stories, a treasure for my family and any other interested parties, and a record of what it’s like to move between such disparate worlds.

I had arrived at Berkeley via a circuitous route. I left school at 12, a little earlier than is typical for Amish children. I then worked full-time at the family grocery store. A few years later, I moved to northern California to attend a ministry school, and heard two hosts at the restaurant where I worked mention California’s free community college. I thought I might as well try it. Though I had no plans to finish college, no idea what that even meant, I ended up transferring to Berkeley after two years.

It was there that I discovered how much I did not know about my mother tongue. There are a few lines that get passed around among the Amish like a hand-me-down shirt when it comes to their linguistic history, and none of them contains more than a kernel of verifiable information. But once I started looking, I found a wealth of scholars, university departments, articles, YouTube videos, even textbooks on Pennsylvania Dutch that I had never imagined existed.

When I first read historian and linguist Mark Louden’s diagnosis of Pennsylvania Dutch’s “image problem,” in his book Pennsylvania Dutch: The Story of an American Language, I found a name for what I’d observed and contributed to all my life. More than 250 years have passed since the first agrarian, working-class Germans and Swiss emigrated to the U.S., bringing with them a language that developed into the Pennsylvania Dutch we speak now. (It’s Pennsylvania Dutch not because of the Dutch that is spoken in the Netherlands, but because German- and Dutch-speaking peoples alike were called Dutchmen, in English, at the time of immigration.) Accents change with time, as does vocabulary. Pennsylvania Dutch and German are for the most part not mutually intelligible, even if they share many similar words and structures. Still, the Amish consistently compare their language unfavorably to standard German. A thousand times my cousins and I have spoken about our language derogatively, to each other or to non-Amish people, saying, “it’s just a mix of English and German” or “it’s wrong German.” We believed that over the past few hundred years we had disfigured what was originally correct. “There are so many English words mixed in, it’s really a dialect, not fully a language on its own,” my cousin Abigail said in her interview, when I asked her about her mother tongue.

The roots of this internalized stigma can be traced back to the language’s early days in the colonies of the “New World.” One of the earliest critiques dates from the 1780s, when a German visitor to the U.S. described Pennsylvania Dutch as a “miserably broken mishmash of English and German” and a “bastard gibberish.” From the 1840s until World War I, people from German-speaking Europe flooded into the country, with particularly high numbers settling in the Midwest. In Pennsylvania and neighboring states, when these newer immigrants encountered “Pennsylvania Dutch,” they scorned the language for its inclusion of English words, and because they thought it no longer sounded like the German that they spoke. (In fact, the origins of Pennsylvania Dutch are primarily in the Palatinate region of southern Germany, where the language already differed from standard German, as many regional varieties did.) Records of public meetings and community newspapers feature jabs at how far these sorry kinsmen have strayed from their heritage. Iterations of this attitude have persisted through eight generations and nearly 250 years.

There are other reasons for the stigma attached to Pennsylvania Dutch, including that the language is almost exclusively used orally. Originally, standard German would have served for writing purposes, for the limited number who were literate, but English slowly replaced it. (All Amish parochial schools conduct their instruction in English.) Partly because of its oral nature, the language is often described as a dialect. I grew up using this term, but upon encountering Louden’s work, I learned that “dialect” often functions more as an insult than a linguistically useful designation. Louden writes that “nonlinguists frequently assume that ‘written down’ languages are somehow more ‘correct’” than oral ones, and notes that there are no “objective criteria” that dictate whether something is a language or a dialect. In theory, any language that overlaps with another — to an unspecified degree — could be categorized as a dialect. This is not how the designation works in practice. Louden points out, for example, that Swedish and Norwegian are highly mutually intelligible, but neither is considered a dialect of the other, or of a parent language, primarily because each is associated with a separate nation-state. He describes how Luxembourgish went from being considered a dialect of German to a language in its own right, through a constitutional revision recognizing it as a national language in 1984. Nothing changed linguistically; a penstroke shifted designation and perception.

I’m not surprised that Pennsylvania Dutch has not received similar recognition. Neither the early working-class immigrants nor the Amish today are apt to ask for acknowledgement, an official title, social status. They have always intentionally kept away from anything resembling pride or vanity; it is hardly surprising they have accepted and perpetuated this lowly descriptor and depiction of their language.

Still, I’ve been trying to reframe the way I think and talk about Pennsylvania Dutch, to catch myself in my reflexive denigrations of my mother tongue. Seeing it through the eyes of others has helped. Some years ago, during a conversation group I attended, hosted by Berkeley’s German department, I offhandedly referred to the language’s sloppiness and illegitimacy.

A nice graduate student named Brian stopped me and said, “Tell me a word in your language. Any word.”

I hesitated before choosing Schtor, an English loanword meaning “store.”

He repeated it back to me, mangling the pronunciation on purpose and then asked, “Was that correct?”

“Well, no,” I said.

“It is a ‘real’ language,” he said. “There’s a right way and a wrong way to speak it. You can’t just do any old thing.”

The range of Pennsylvania Dutch vocabulary reflects the immediate, physical nature of my ancestors’ lives and work. Words that can convey colorful, varied descriptions, emotional nuance or complex ideas have always been limited and have only continued to wane. The contours of Pennsylvania Dutch words are harder and sharper than English ones. It’s hard to ask for a soft favor. Difficult to communicate affection, impossible to say the word love. We have no distinct word for it. One must use the standard German liebe, obtuse and antiquated in our mouths, or succumb to English, a concession. It is a tongue of commands and directives, probing questions about family relations, occupation in the most literal sense, and of following rules.

I’ve always assumed that the language’s oral nature has contributed to its concrete, factual diction. There are things you write that you almost never speak aloud. “The earth screams its thirst” might be an evocative way to describe a drought-ridden plain, but in conversation one is much more likely to simply bemoan the lack of rainfall and the dry appearance of the soil. I grieve this, but I also understand it. Language is above all, functional, and what didn’t serve my ancestors they discarded.

Harder for me to accept is how, today, English words are increasingly entering the language, edging out others. This is true for many traditional Amish communities that frequently interact with the wider world. Some English is present in every interview I conducted. Both Delores and Gabriel used “parents” instead of Eldre. Gabriel said “gevisit” and “borroweh” — Deitsch-ified versions of “visited” and “borrow” instead of bsucht and lehne, respectively. In the interview with my aunt Leona, she speaks almost an entire sentence in English, albeit with a Pennsylvania Dutch accent. I feel awkward about it. I want the interviews to show that the language does stand on its own, that it doesn’t need English to be functional. Sometimes I offer the Pennsylvania Dutch words to my interviewees as a suggestion, but I doubt that this is an advisable habit. No matter how zealous my efforts, I cannot repopulate the lexical landscape on my own.

Just before one of my trips to Montana to conduct interviews, I spoke to Rose Fisher, a linguist and language scientist at Michigan State University who studies Pennsylvania Dutch. She is also the mentor on my oral history project. When I talked about words that I remember using not many years ago having now fallen out of common practice, she emphasized that mixing Pennsylvania Dutch words with English is simply a common mode for this language. Sometimes, English words are absorbed into the language and become part of it, taking on a Pennsylvania Dutch flavor. The English “ready,” for example, has become reddi, and is used as a synonym for zeidich, meaning “ripe.” Louden points out that sometimes an English word strengthens and diversifies the Pennsylvania Dutch lexicon rather than diminishes it. Draage used to mean both “to carry” and “to wear.” The English “wear” has entered our speech in the form waere, keeping its English definition, and draage now exclusively means to carry. In this case, the phenomenon has increased rather than reduced precision.

A different issue for Pennsylvania Dutch is the increasing tendency to make English the default for communication, out of convenience or habit. One late afternoon in early winter, two of my mom’s siblings and their spouses, all in their 20s and early 30s, came over to hang out. They ended up staying for dinner after my uncle Josiah shot a deer half a mile away — he’d been trying to fill his tag for weeks. All four of them grew up far more Amish than I did, and all are fluent in Pennsylvania Dutch, but hardly a word of it was spoken. I occasionally made an attempt, but I also found myself slipping back into English like a bowling ball into the deep grooves of the gutter. 

On a different occasion, I asked one of my cousins if she speaks Pennsylvania Dutch with her sisters these days, who are all fluent.

“We just don’t. It isn’t how we connect anymore,” she said, an air of apology in her tone.

So far I’ve interviewed 37 members of my family and community on camera. My interviewees’ level of comfort speaking in Pennsylvania Dutch for longer stretches of time, in a fairly formal setting, varies greatly. Many of those who now don’t often use the language claim that they will be shaky, unclear, a mess. All do better than they expect, even if 24-year-old Delores is more stilted, more careful than 58-year-old Leona.      

Delores, like me, has grown up in a changing community, a landscape of mixed languages. A few years ago, she married someone who speaks only English, and now she hardly has any reason to use Pennsylvania Dutch.

Leona, on the other hand, spoke English far less often than Pennsylvania Dutch until she was in her 30s — only when interacting with customers at the store she managed or shopping in town. This has changed gradually in the last 20 years, but she’s at no risk of forgetting the language that was her primary vehicle for communication for so long.

Of the interviews I’d done so far, the one with Leona was one of the most moving. She was my boss at the family grocery store and bakery where I worked as a teenager. We differed in the usual ways that teenagers differ from the middle-aged, as well as ways someone who was truly and wholly raised within the Amish might differ from someone who has only experienced the same immersion in infancy. Leona is frugal, in every dimension. Every swipe of her hand running a dishrag across the pocked butcher block table in the kitchen was parsimonious and each step she took over the store’s worn linoleum floor efficient. She skirted health codes to avoid paper towel and hot water waste. She told us frequently that “more girls in the kitchen means less work gets done,” leaping to send one of us home if the frequency of customers lagged. Over the years, dozens of my cousins worked for her. If we helped after school or in the summer, bagging cookies or breaking down cardboard boxes, she would reward us with snacks or cash under the table. With no children of her own, she embraced all of us as hers. At church, she could be counted on to pass around the pink spearmint candies she kept in the pocket of her Bible cover.

When I sat down with her, Leona was collected and linear in her storytelling. She had absorbed the list of questions I had sent her before the interview into a narrative that addressed them all and hardly required my prompting. We spoke for almost two hours as she guided me through the trajectory of her life — from her first purchase of non-Amish clothing (a blue blouse with white floral embroidery on the front) to her late-discovered love of driving. “Ich hab honestly really struggled, ich figger meh as menscht vun ne,” she said of making those big changes (“I honestly really struggled, I’d say more than most of them”). “I probably still lean a little more towards... I’m not quite so... ” she didn’t finish her sentence, but I knew what she meant. She has followed the community’s initiation of change but wouldn’t have asked for so much change herself. A lot of the others don’t feel this way; having the agency and permission to choose how they present themselves has been integral to finding an identity outside of Amish traditionalism. 

This is one of the reasons I wanted to interview so many people in my community, to capture the range of their attitudes. Most members don’t think of themselves as Amish anymore, in a strict sense, and describe themselves as ex-Amish or formerly Amish. Some like to say that they have Amish heritage or background. Others hold onto the label, saying they never formally left and still feel like the same person they were then. They may look different, they say, but that doesn’t change the fact of their Amish identity.

The more I speak to older relatives about their traditional years, the more I’m reminded how many of their lives have been marked by injury and early death. There are a hundred ways that the work of farming and construction, and all manner of other physical tasks, can harm those who do them. Painful events are discussed matter-of-factly: Neither our language nor our culture invites dwelling in the complexities of grief and loss.

My dad described nearly dying several times, once as a child from getting kicked in the head and chest by a horse. He still has the scar where a nail from the horseshoe went through his lip. Later when he was 21 and working in the family’s sawmill, a pile of logs rolled on top of him, breaking his back and pushing him backwards so he narrowly missed being impaled by a forklift.

Leona spoke about her brother Vernon, three years older than her, who was thrown off a spring wagon at age nine, his body bleeding and shattered from the impact. My uncle Lloyd, then 15, laid him under a tree while he sprinted a mile back to the house where someone else ran to find a non-Amish neighbor whose phone they could use to call an ambulance. My Mammi (grandma) Orpha ran out to hold him as he struggled to breathe, and then he was gone, long before help arrived.

Leona also told me about her husband, my uncle Matthew, whom I never met. They had been married for 14 months when he died of lung cancer, presumably caused by weeks of exposure to industrial wood-staining agents while working on the interior of a log home without wearing a respirator. As I listened, I tried to keep my emotions, my welling tears, to myself, so as not to derail us — or perhaps because being emotional is not something I’ve learned to do with Leona.

I’m asking for so much from them in these conversations, scavenging their memory banks, taking what I am given and then trying to get more. It’s not just about recording the language, of course: If that were all I was interested in, I could have had them recite a vocabulary list. I don’t only want to know the words, but how they are used to convey the trivial details — the food, the clothes, the daily household tasks — as well as the profound sorrows and intense joys. It all sits side by side, in language inextricable from the events themselves: the old life and the new, my relatives’ different experiences, and their different perspectives on the same experiences.

Not long after interviewing Leona, I filmed three of my aunts talking about their mother, my grandmother. It wasn’t part of my original plan, but Mammi had recently passed away after grappling with dementia for years. Here was a way to fix inside digital resin some of her habits and propensities, through the words of three of her daughters. My aunts are commanding, and each in her own way can be intimidating. On that day, they had a lot to say. Even after I turned the camera off, they kept talking.

Two of my cousins, both women in their early 20s, came to sit behind me a few minutes into the interview, watching attentively. When I said we were finished, they jumped up and said they had something to say to the camera. They squeezed onto the soft brown couch between their mothers and said that hearing what Mammi had been like in her earlier years as a homemaker helped them understand why their own mothers were the way they were — sometimes difficult, always exacting, with high standards for the state of their homes. One said it made her realize these qualities are more special than she had thought — part of a lineage of women building their families and demanding only the best work from themselves. My aunts’ stories had rendered a sequence of Mammi’s hands moving without pause from early morning until the end of each day, when she would lean close to the kerosene lamp to continue mending or sewing by its feeble illumination.

I could recount these stories in detail here, but they would not convey the sounds of what I was told, the quality of my aunts’ voices in Pennsylvania Dutch and the implications of each chosen word. I confront this repeatedly as I work through the video footage of my interviews, layering English subtitles over Pennsylvania Dutch audio: how difficult it is to convey, in English, the full meaning of what a Pennsylvania Dutch speaker is saying.

But this project has also been a gift, reminding me that, even as Pennsylvania Dutch favors the concrete and the literal, it is full of its own nuance. When referring to how some of the younger children aren’t being taught the language, Leona says “S’schpeit mich.” It’s a phrase that doesn’t translate directly, expressing a combination of sadness and disappointment, usually with connotations of regret at an unfortunate outcome over which one has no control. After much deliberation, I end up opting for “saddened by,” even as I know that it doesn’t quite do what I want it to. I hope her tone and expression help convey the rest.

The Daily Front Page 23 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Ballard Country
article

The Estranged Worlds of J. G. Ballard

by Caiero·▲ 74 points·16 comments·lareviewofbooks.org ↗
One of the nuclear age’s greatest visionaries.

An incisive and moving new critical biography of one of the nuclear age’s greatest visionaries.

The Illuminated Man: Life, Death and the Worlds of J. G. Ballard by Christopher Priest and Nina Allan. Bloomsbury Continuum, 2026. 496 pages.Buy on Bookshop.org

THE BRITISH AUTHOR J. G. Ballard (1930–2009) was one of the most brilliant and incisive, but also one of the most perplexing, English-language fiction writers of the past 80 years or so (the period since the end of World War II). He was never part of the literary mainstream. Initially, he was classified and marketed as a science fiction writer; during the 1960s, he was a leading figure of SF’s so-called New Wave, which embraced sexual, psychological, and psychedelic themes, and focused more on the social sciences than on the hard sciences. As Ballard’s career progressed, however, his books increasingly departed altogether from what we usually recognize as science fiction. For instance, there is no exploration of outer space in Ballard’s fiction: there are no robots or supercomputers, and the scientist characters who continue to populate his novels are usually extremist cranks. Ballard’s early novels featured world-shaking apocalyptic scenarios, but his later work was concerned with smaller—more mundane and intimate—disasters.

One common definition of science fiction describes it as the literature of cognitive estrangement—that is to say, it presents us with a reality that deviates in some crucial way from the actual, everyday world we live in, and thereby forces us to abandon our usual assumptions and expectations. Ballard’s mature fiction is indeed estranging or alienating in precisely this way; however, it is set entirely within the present-day world of advanced postindustrial society as we know it. Ballard does not present us with aliens and cosmic mysteries; instead, he writes about the minutiae of middle-class Western society: the world of automobiles, refrigerators, airplanes, and television. But he approaches these familiar elements of our lives and environment as if he were describing an alien planet. Crash (1973), his most famous and controversial novel, treats automobile accidents as pornographic spectacles. That book’s follow-up, Concrete Island (1974), rewrites Robinson Crusoe as the story of a man stranded, not on a distant island, but on a waste area wedged between several motorways. Ballard’s final novel, Kingdom Come (2006), discovers fascism at the heart of a vast postmodern shopping mall and seems to offer a premonitory vision of the state of the United Kingdom today, 20 years later, when the neofascist agitator Nigel Farage has a real shot at becoming prime minister after the next round of elections.

A number of major film directors have made movies out of Ballard’s fiction. In 1987, Steven Spielberg, who manages to be heartwarming even when directing works of horror, did an adaptation of Ballard’s semi-autobiographical 1984 novel Empire of the Sun, with its account of a boy growing up while interned in a Japanese prison camp during World War II. David Cronenberg—whose work, even at its most mundane, is unfailingly icy and perverse—translated Crash’s twisted autoeroticism (with this word’s double meaning as both masturbation and the sexual fetishization of cars) to the screen in 1996. In 2015, Ben Wheatley, known for black comedy and genre twisting, applied his skills to Ballard’s 1975 novel High-Rise, in which the affluent inhabitants of a 40-story luxury apartment building revert to barbarism and engage in internecine class warfare. Ballard is perhaps the only author whose work could be adapted with equal success by filmmakers of such widely different temperaments.

Christopher Priest and Nina Allan’s new biography The Illuminated Man: Life, Death and the Worlds of J. G. Ballard is lively and well written, and it should be ideal for readers encountering Ballard for the first time, as well as for those (like me) who have been reading him for decades. The Illuminated Man seeks to trace out the roots of Ballard’s visionary fiction in his life, without reducing the former to the latter. Certainly, the author’s early years were fraught with alienation and tragedy. Ballard was born in Shanghai to English parents. Before World War II, the Western powers—Britain, the United States, and France—had small colonies in Shanghai that were not subject to Chinese legal authority. These colonies were inhabited by wealthy businesspeople and their families. The inhabitants ran businesses that extracted money for the West from the impoverished Chinese masses. Ballard grew up in a sort of bubble, where he never left Shanghai but also never encountered any Chinese people except servants hired by his family. The situation changed during World War II, when Shanghai and other parts of China were occupied by the Japanese army. The young Ballard and his family were imprisoned for two-and-a-half years in an internment camp, released only when the war ended with the defeat of Japan in 1945, after the nuclear bombings of Hiroshima and Nagasaki. This experience marked Ballard as an early witness of the nuclear age.

In late 1945, at the age of 15, Ballard came to Great Britain for the first time. He went to high school and college, briefly enrolled in the UK’s Royal Air Force, and started writing short fiction in his spare time. He got married, had three children, and moved to the town of Shepperton, a nondescript suburb of London, where he remained for the rest of his life. He published his first novels in 1961 and 1962, and was able thereafter to quit his day job and write full-time. Ballard encountered tragedy once again in 1964, when his wife died of pneumonia at the age of 34, leaving him to raise their children on his own. From then on, Ballard’s everyday life—in contrast to his fiction—was fairly sedate and uneventful. He continued writing until just before his death from cancer at the age of 78. Ballard published 18 novels, a memoir, and a large number of short stories, together with book reviews, interviews, and short essays.

The Illuminated Man is informative about the details of Ballard’s life, while at the same time striving to capture—to evoke if not explain—the strangeness of his fiction. It often gives us information about which the writer himself was not forthcoming. Ballard drew extensively upon his own memories and desires as sources for his work, but he tended to mythologize his life, and he often smoothed over or omitted certain crucial events in his past. Indeed, Ballard disputed “the received wisdom of the day that all disturbing or violent experience is inherently damaging,” claiming that he was “not sure that [he] ever suffered irreparable trauma”—not even, he said, from the death of his wife. As for his internment during the war, Ballard even “sometimes said it was the best time of his life.”

Priest and Allan do not directly dispute Ballard’s claim not to have been traumatized by the worst events of his life. Instead, they devote much of their book to tracing the strange alchemy by which Ballard was able to transmute both his personal experience and the often dreadful historical events of the mid- and late-20th century into fantastic fiction. Though Ballard’s view of modern life can only be described as pessimistic and exceedingly grim, the mood of his stories is neither horrific nor even depressed. Ballard writes in a voice that remains objective, distanced, and orderly, even (or especially) when the contents being recounted are obsessional, maniacal, or otherwise irrational. The result is slyly and dryly humorous. There is an ongoing mismatch between the objective events recounted in the novels and the characters’ subjective responses to those events. If the content of Ballard’s fiction provokes cognitive estrangement, then the form and style in which this content is expressed involve a high level of cognitive dissonance. Such incongruity on all levels is the inner principle of Ballard’s fiction, and the source of its continuing fascination. I first encountered Ballard’s fiction when I read his 1964 short story “The Terminal Beach” at the age of 12. This text still haunts me 60 years later. It recounts the hallucinatory experiences of a man wandering through the empty ruins of Eniwetok, a Pacific atoll where the United States performed numerous nuclear tests, including the first explosion of a hydrogen bomb. This vista of destruction seems to leave impalpable remnants behind: traces not so much of what was destroyed as of the palpability of absence itself.

In all his writing, Ballard seems to embrace the opposed extremes of pulp genre conventions on the one hand and avant-garde experimentation on the other, while entirely rejecting the norms of conventional literary fiction that lie between these two extremes. Priest and Allan note that the adjective “Ballardian” often appears in dictionaries, alongside such other writerly appellations as Proustian and Kafkaesque. However, they add that Ballardian is “a mysterious and imprecise word that no one could define but which was readily understood by those who experienced it.” This ungraspable meaning is partly a result of the obsessively repeated images and motifs that recur throughout Ballard’s fiction, and which the authors inventory in a sort of mock-epic catalog:

dense rainforest, vibrant light, shopping malls, river systems, sites irradiated by nuclear blasts, white-haired goddesses of the film world, errant doctors, gated communities of oligarchs and ultra-rich fund managers, psychopaths, the freeways around and across cities, rocket launching zones, flooded cities, beach resorts, drained swimming pools, desert wastes.

These images are oddly flat and literal. They create a mood of desolation and decay. But the most important thing about them is that they are not metaphors. They do not have symbolic resonances or extended meanings beyond themselves. Rather, in their sheer literal presence, they suggest the exhaustion, or the emptying out, of the very possibility of meaning anything at all. Shopping malls are built to be eventually abandoned; swimming pools are always ultimately drained. And celebrities tend to implode into black holes, endlessly referring only to their own mystique. For instance, does the figure of Marilyn Monroe—frequently mentioned by Ballard in hid 1970 collage novel The Atrocity Exhibition, as well as in other art of the period, such as Andy Warhol’s iterated portraits of her—really stand for anything at all anymore? Did her star image ever stand for anything, even back then? And what do we make of her demisurvival, as an image or icon, long past her physical death? Ballard’s fiction is always mulling over this sort of question.

Ballard’s writing insists on finitude and mortality, and yet it also seems to reject finality. There is no triumphant accomplishment but also no sense of anything ever being over and done with, once and for all. This in-between situation is reflected in The Illuminated Man, in the very process of its writing. Christopher Priest and Nina Allan were life partners; they have also both written fiction that (like Ballard’s own) is best described as SF-adjacent rather than as science fiction in the strict sense. The Illuminated Man was originally Priest’s project; he did the research and wrote more than two-thirds of the text. But then he became too ill to proceed, with the cancer that finally killed him. Allan stepped in and wrote the missing sections that Priest had outlined. But in the book, she also includes an account of Priest’s illness and death, of her caring for him as he declined, and of her unassuageable grief. These pages are quite moving. At first, they might seem extraneous to the account of Ballard’s life; though he and Priest were somewhat acquainted, they were never close. But in the long run, the intimacy of Allan’s account of Priest’s death substitutes for the fact that Ballard’s own death is, by necessity, only recounted to us from a much greater distance.

It also highlights, perhaps, Ballard’s own rejection of any such intimacy, though he was close to his wife, to his later life companion Claire Walsh, and to two of his three children. Allan’s account of Priest’s death before he could finish writing the book reminds us that no account of another person (or even of oneself) is ever complete and definitive. The Illuminated Man does not tell us everything about J. G. Ballard, but it tells us a lot. In that way, it brings us back to Ballard’s own fiction, which is starkly limited in scope and yet—in its depth and mystery, and in its insights about modern technologized life—inexhaustible.

The Daily Front Page 24 of 25
Tuesday, July 14, 2026 The Daily Front No. 4 — Colophon

That's the Front for Today

Issue No. 4 — Tuesday, July 14, 2026 — went to press 2026-07-16 at 11:33 UTC.

About This Magazine

The Daily Front is a daily digital magazine assembled from the stories that reached the front page of Hacker News on Tuesday, July 14, 2026. Headlines, points, and comment counts are recorded as they stood at press time. All articles remain the property of their original authors — every piece links back to its source and its discussion thread.

How It Was Made

Fetched, cleaned, and typeset by an automated pipeline. An editor model laid out the pages, chose the highlights, and briefed the cover illustrator — 33 model calls and 281k tokens in total. Set in Jacquard 12, Playfair Display, Source Serif 4, and IBM Plex Mono, all served via Google Fonts under the SIL Open Font License.

The Cover

The cover illustration was commissioned with this prompt:

editorial cover image with no text, no logos, no readable characters​. Depict a surreal “return of the web” as an intricate living ecosystem: a smartphone lies open like a small glass terrarium, but instead of apps it contains a lush miniature internet forest — delicate browser windows as translucent leaves, hyperlinks as glowing spider silk, tiny AI agents as curious paper-firefly creatures navigating between branches, and a bonsai tree shaped like a neural network growing from the phone’s screen. In the background, oversized human silhouettes gently remove their own thought-bubbles and hand them to soft mechanical birds, suggesting the offloading of thinking to AI. At the edge of the scene, a cracked cursor arrow casts a long protective shadow like a shield.

Art style: experimental mixed-media editorial illustration, combining Japanese woodblock print composition, risograph grain, cut-paper collage, ink wash, and subtle digital glow. Elegant but strange, poetic, witty, intellectual, highly aesthetic. Muted jade, warm ivory, charcoal, browser-blue, coral red accents, soft gold highlights. Museum-quality magazine cover composition, strong negative space, handcrafted texture, imaginative metaphor, cinematic lighting, no clutter. Absolutely no text anywhere in the image.

Production Ledger

StageModelCallsTokens InTokens Out
extractgpt-5.5 32 190,101 67,289
layoutgpt-5.5 1 19,058 5,011

The Publisher

Published by Johnny.

Support the Press

If The Daily Front brightens your morning, consider supporting its publisher.

Credits & Contact

All content — articles, posts, comments, and the images within them — belongs to its original authors and is reproduced here to point readers back to the source. Full credit goes to those creators; every item links to its original and its Hacker News discussion.

If you are an author and would like your content removed from an issue, write to hi@johnnys.page and it will be taken down.

Feedback is always welcome at the same address: hi@johnnys.page.

Credit where credit is due.

Every page of this issue began as someone else's work — these are the original sources, linked in full.

  1. Your 'app' could have been a webpage (so I fixed it for you) by MrVandemar — danq.me·HN discussion ↗
  2. Bonsai 27B: A 27B-Class model that runs on a phone by xenova — prismml.com·HN discussion ↗
  3. Are we offloading too much of our thinking to AI? by yenniejun111 — artfish.ai·HN discussion ↗
  4. The Tower Keeps Rising by cdrnsf — lucumr.pocoo.org·HN discussion ↗
  5. Cursor 0day: When Full Disclosure Becomes the Only Protection Left by Synthetic7346 — mindgard.ai·HN discussion ↗
  6. Show HN: Juggler – an open-source GUI coding agent, by the creator of JUCE by julesrms — github.com·HN discussion ↗
  7. How to stop Claude from saying load-bearing by shintoist — jola.dev·HN discussion ↗
  8. Launch HN: Agnost AI (YC S26) – Extract user feedback from agent conversations by laalshaitaan — agnost.ai·HN discussion ↗
  9. The Economics of Recursive Self-Improvement [pdf] by apsec112 — elasticity.institute·HN discussion ↗
  10. Punch yourself in the face with reality by AdityaAnand1 — adi.bio·HN discussion ↗
  11. The zero-cost fallacy: open-source software in the agentic era by backlit4034 — thoughtworks.com·HN discussion ↗
  12. The git history command by turbocon — lalitm.com·HN discussion ↗
  13. How I use HTMX with Go by gnabgib — alexedwards.net·HN discussion ↗
  14. European "age verification" "app" forcing everyone to use Android or iOS by roundabout-host — github.com·HN discussion ↗
  15. Dependabot version updates introduce default package cooldown by woodruffw — github.blog·HN discussion ↗
  16. The infinite scroll may become endangered if controversial Calif. law passes by Stratoscope — sfgate.com·HN discussion ↗
  17. Measuring Input Latency on Linux: X11 vs. Wayland, VRR, and DXVK by hoechst — marco-nett.de·HN discussion ↗
  18. I'm a USB-C Maximalist by speckx — shkspr.mobi·HN discussion ↗
  19. Australian energy retailers must offer three hours of free daytime electricity by i2oc — lenergy.com.au·HN discussion ↗
  20. Japan develops a method to recover up to 90% of lithium from used EV batteries by donohoe — tech.supercarblondie.com·HN discussion ↗
  21. Fundamentals of Wireless Communication (2005) by teleforce — web.stanford.edu·HN discussion ↗
  22. Indian scientists produce most detailed 3D atlas of the human brainstem by BaudouinVH — bbc.com·HN discussion ↗
  23. Satellite Tracker – Live Map of Starlink and 30k Satellites by rolph — satellitemap.space·HN discussion ↗
  24. Show HN: Opening lines of famous literary works by plicerin — verbaprima.com·HN discussion ↗
  25. The largest available Minecraft world, totalling 15 TB by _____k — 2b2t.place·HN discussion ↗
  26. The kids with phones are alright by JumpCrisscross — heatherburns.tech·HN discussion ↗
  27. An Englishwoman who sketched India before photography took hold by 1659447091 — bbc.com·HN discussion ↗
  28. Mathematical texts from a Maya site in Guatemala identify an ancient astronomer by homarp — nature.com·HN discussion ↗
  29. Our Amish Language by NaOH — thedial.world·HN discussion ↗
  30. The Estranged Worlds of J. G. Ballard by Caiero — lareviewofbooks.org·HN discussion ↗

Browse all issues in the archive →