10 interesting stories served every morning and every evening.
A minute-by-minute reconstruction of the massacre by Earshot and Forensic Architecture found Israeli soldiers fired over 900 bullets at the aid workers, killing 15. Drop Site is a reader-funded, independent news outlet. Without your support, we can’t operate. Please consider becoming a paid subscriber or making a 501(c)(3) tax-deductible donation today.Funerals held at Nasser Hospital in Khan Yunis, in southern Gaza, for aid workers from the Palestinian Red Crescent who were killed in an Israeli attack in Tel al-Sultan. March 31, 2025. Photo by Hani Alshaer/Anadolu via Getty Images.Israeli soldiers fired nearly a thousand bullets during the massacre of 15 Palestinian aid workers in southern Gaza on March 23, 2025—with at least eight shots fired at point blank range—according to a joint investigation by the independent research groups Earshot and Forensic Architecture. The report, based on eyewitness testimony and audio and visual analysis, shows that a number of aid workers were executed and that at least one was shot from as close as one meter away.In Tel al-Sultan that day, Israel killed eight aid workers with the Palestine Red Crescent Society (PRCS), six from Palestinian Civil Defense, and a UN relief agency staffer. It immediately triggered international condemnation and was described as “one of the darkest moments” of the war by PRCS.The Israeli military was forced to change its story about the ambush several times, following the discovery of the bodies in a mass grave, along with their flattened vehicles, and the emergence of video and audio recordings taken by the aid workers. An internal military inquiry ultimately did not recommend any criminal action against the army units responsible for the incident.The report by Earshot and Forensic Architecture reconstructs, minute by minute, how the massacre unfolded. Using video and audio recordings from the incident, open-source images and videos, satellite imagery, social media posts, and other materials, as well as in-depth interviews with two survivors of the attack, the groups were able to digitally reconstruct the scene and events surrounding the massacre.Israeli soldiers ambushed and subjected Palestinian aid workers to a near continuous assault for over two hours even though the soldiers never came under fire.At least 910 gunshots were documented across three video and audio recordings of the attack. The vast majority of these gunshots, at least 844, were fired over just five minutes and 30 seconds.At least 93% of the gunshots recorded in the first minutes of the attack were fired directly towards the emergency vehicles and aid workers by Israeli soldiers. During this time, at least five shooters fired simultaneously. Witness testimonies suggest as many as 30 soldiers were present in the area.Israeli soldiers were initially positioned on an elevated sandbank by the road, with no obstructions limiting their line of sight. The emergency lights and markings of the victims’ vehicles would have been clearly visible to the soldiers at the time of the attacks.Israeli soldiers first maintained fixed firing positions from the elevated sandbank, then walked toward the aid workers while continuing to shoot. Upon reaching the aid workers, the soldiers moved between them and the vehicles and executed some of the aid workers at point blank range, as close as one meter away.In the immediate aftermath of the attack, the Israeli military conducted extensive earthworks at the site. In the days and weeks that followed, the area was further transformed by the Israeli military’s construction of the “Morag Corridor,” a security zone splitting the southern Gaza Strip, and the erection of an aid distribution site operated by the Israeli- and U.S.-backed Gaza Humanitarian Foundation.“This seems to be a very well documented case using a number of forms of credible evidence that are cross referenced,” Katherine Gallagher, a senior staff attorney at the Center for Constitutional Rights, told Drop Site after reviewing a detailed summary of the investigation. “It presents a very compelling case, and honestly, a very devastating one.”The Israeli military did not respond to specific inquiries from Drop Site and instead pointed to the findings of an internal investigation published on April 20 that found “the incident occurred in a hostile and dangerous combat zone, under a widespread threat to the operating troops.” It also “found no evidence to support claims of execution,” which it called “blood libels and false accusations against IDF soldiers.”The joint report will be released February 24 at a gathering at British parliament in Westminster hosted by the British Palestinian Committee with Earshot, Forensic Architecture, and the international humanitarian law coordinator for PRCS, Dana Abu Koash. The full report is available here.On March 23, 2025 at 3:52 a.m., PRCS dispatched two ambulances from two different areas to the scene of an Israeli airstrike in Al-Hashashin, an area near Rafah. Israel had resumed its scorched earth bombing campaign on Gaza a few days earlier after abandoning the January 2025 ceasefire agreement.The attack on the aid workers began at approximately 4:00 a.m. when one of the ambulances driving along Gush Katif road in Al-Hashashin came under Israeli fire. The vehicle had its emergency lights turned on at the time. Mustafa Khafaja, who was driving, lost control of the vehicle, which veered left off the road and stopped near an electricity pole. Khafaja and his colleague, Ezz El-Din Shaat, who was in the passenger seat, were both killed. A third PRCS worker, Munther Abed, who was in the back of the vehicle, threw himself to the floor of the van and survived.After the shooting stopped, Israeli soldiers approached the ambulance and dragged Abed out of the car, beat him, and detained him at a nearby pit. Sometime later, two Palestinian civilians—a father and son from the Bardawil family—were also detained and brought to the pit. The Israeli soldiers then took the three detainees to an elevated area behind a tall concrete structure some 38 to 48 meters southeast of the ambulance, where an additional group of Israeli soldiers were positioned.Still from the situated testimony with Munther Abed recounting the location of the pit and the area behind the tall concrete structure where he was taken when detained by Israeli soldiers. (Forensic Architecture, 2026).By 4:35 a.m., the second ambulance, having completed its mission in Al-Hashashin, was dispatched to search for the first ambulance, which had lost contact with PRCS headquarters at 3:55 a.m. The second ambulance was joined by two more PRCS ambulances, one belonging to Civil Defense, and a Civil Defense fire truck. The five-vehicle rescue convoy arrived at the scene of the attack of the first ambulance shortly after 5:00 a.m. All vehicles were clearly marked and had their emergency lights turned on.The position of each ambulance as the shooting began. (Forensic Architecture, 2026)A PRCS worker in one of the ambulances, Refaat Radwan, began filming on his phone as they drove to the site. His recovered videos as well as recordings of phone calls by two other aid workers at the scene to PRCS dispatch provided crucial evidence of the massacre. Forensic Architecture and Earshot’s analysis of the recordings corroborated eyewitness testimony on the positions and movements of the Israeli soldiers throughout the attack.At 5:09 a.m., as the aid workers parked and approached the first ambulance by foot, Israeli soldiers positioned on the elevated sandbank opened fire. A digital reconstruction of the scene shows that the soldiers would have had an uninterrupted view of the arrival of the convoy. Abed, who was being detained at gunpoint on the elevated sandbank, testified that the soldiers were kneeling and aiming their weapons at the convoy as it approached.Locations of all emergency vehicles at the incident site at 5:10 a.m. relative to Munther Abed and the Israeli soldiers who detained him. From their position, the soldiers would have been able to clearly see the convoy’s arrival with their emergency lights on. (Forensic Architecture, 2026).
The Israeli soldiers remained on the sandbank while firing continuously at the aid workers for four minutes. The soldiers then advanced towards the aid workers at a walking pace of approximately one meter per second while continuously shooting.Echolocation of Israeli soldiers approaching the aid workers during the final 1 minute and 30 seconds. (Earshot, 2026).
Upon reaching the vehicles, the Israeli soldiers continued to fire as they walked in between the ambulances and the fire truck, shooting the aid workers at close range in execution-style killings.At approximately 5:13 a.m., PRCS aid worker Ashraf Abu Libda called the group’s headquarters. The recording, which overlaps Radwan’s video, provided additional details. In this recording, Earshot found that at least eight gunshots were fired from positions between the emergency vehicles. One of the gunshots captured on Abu Libda’s phone call was fired from a range of one to four meters from him. The gunshots coincide with the last time Abu Libda’s voice is heard on the call, suggesting these are the gunshots that killed him.Echolocation of Israeli soldiers as close as 1 to 4 meters from aid workers and most likely close-range execution. (Earshot, 2026).
At least 844 gunshots were fired over a period of five minutes and 30 seconds, with at least 93% of the shots fired toward the emergency vehicles. The audio ballistics analysis confirms the presence of at least five shooters—and possibly many more—firing simultaneously. The two surviving PRCS aid workers, Munther Abed and Asaad Al-Nasasra, testified that between 12 and 30 soldiers were at the scene.“The reconstruction was jointly achieved with the two survivors of the incident, with an immersive spatial model they could walk through and amend. Together with spatial and audio analysis we established the position of the soldiers on an elevated ground with an unobstructed line of sight to the emergency vehicles. The soldiers could clearly see the aid workers, shot at them continuously and deliberately from this position and then approached to execute them one by one at close range,” Samaneh Moafi, assistant director of research at Forensic Architecture, told Drop Site. “Locating the massacre within the evolution of Israel’s campaign in Gaza shows that it was not an isolated incident but part of the genocide.”Earshot used echolocation to analyze the audio on the recordings in order to arrive at precise estimates of the shooters’ locations. Echolocation is the process of locating the source of a sound based on an analysis of the sound’s echoes and the environment in which the sound travels. The Israeli military destroyed and cleared so many buildings in the Tel Al-Sultan area where the ambush of the aid workers took place that very few structures remained. This destruction actually strengthened Earshot’s ability to determine the positions and movements of Israeli soldiers, based on identifying the surfaces responsible for clearly distinguishable gunshot echoes. Rather than having multiple buildings reflecting the sound waves, there were only a few standing walls and the emergency vehicles themselves.The analysis of the video and audio corroborated Al-Nasasra’s eyewitness testimony that Israeli soldiers “came down [from the sandbank], got close to [the aid workers] and shot them from close range,” and “were walking between [the aid workers] and shooting.”Map showing the Israeli soldier’s positions derived from an audio analysis of gunshot echoes from Refaat Radwan’s video. (Earshot, 2026).“Earshot forensically analyzed over 900 gunshots fired at aid workers. It took one whole year of careful listening to reconstruct an auditory picture of what happened that dark night,” Lawrence Abu Hamdan, the director of Earshot, told Drop Site. “I am so proud that our work has corroborated the survivors’ testimony, establishing their brave accounts as accurate and reliable documentation of what occurred that day. Yet, it is the echoes of this event that continue to haunt us: the destruction and clearing of Tel al-Sultan left only three structures standing at this crime scene. While the few echoes reflecting off these buildings brought light to this crime, they have also revealed a scale of erasure of life beyond this one event.”According to autopsy reports first reported by the Guardian, the aid worker who filmed the video—Radwan—was shot in the head, while Abu Libda and another aid worker, Muhammad Bahloul, were shot in the chest. A doctor who examined the bodies reportedly described the “specific and intentional location of shots at close range” as indicative of an “execution-style” shooting.More than two hours after the initial attack, a clearly marked UN vehicle, a Toyota Hilux, passed by the site. Israeli soldiers fired on the vehicle, killing the driver. The UN lost contact with the vehicle at 6:00 a.m. A second UN vehicle, a minibus, arrived in the area minutes later and was brought to a stop by gunfire a little over 200 meters away. The driver was able to escape.Left: Photograph of the UN Toyota Hilux taken on the 30 March 2025, when the bodies of the victims were recovered. (OCHA, 2025). Right: Still from the situated testimony with Asaad recounting the location of the UN Toyota Hilux when brought to a stop. (Forensic Architecture, 2026). Annotated 3D model showing the position of two UN vehicles in relation to the missing ambulance and the convoy of emergency vehicles. (Forensic Architecture, 2026).Between 6:55 and 7:13 a.m., Al-Nasasra made a phone call to PRCS headquarters that captured at least 42 additional gunshots and the sound of vehicle movement. The recording also captured the sound of an explosion the investigation identified as the firing of an Israeli-made Spike LR guided missile.Following the ambush, Israeli forces crushed all eight vehicles using heavy machinery and attempted to bury them under the sand.The body of Anwar al-Attar was found near the ambush site on March 27, and the bodies of the other 14 aid workers, all wearing identifying uniforms or volunteer vests of their respective organizations, were found in a mass grave near the site on March 30.The 15 aid workers killed were: Mustafa Khafaja, Ezz El-Din Shaat, Saleh Muammar, Refaat Radwan, Muhammad Bahloul, Ashraf Abu Libda, Muhammad al-Hila, and Raed al-Sharif with PRCS. Zuhair Abdul Hamid al-Farra, Samir Yahya al-Bahapsa, Ibrahim Nabil al-Maghari, Fouad Ibrahim al-Jamal, Youssef Rassem Khalifa, and Anwar al-Attar with Civil Defense. Kamal Mohammed Shahtout with UNRWA.Annotated still from the 3D model showing the location of the bodies of aid workers and their vehicles before the mass burial. (Forensic Architecture, 2026).One of the survivors, Abed, was released hours after the ambush. The other survivor, Asaad, was held in Israeli custody without charge for 37 days, tortured, and interrogated in relation to the incident at the Sde Teiman detention camp, a notorious Israeli prison camp in the Negev desert, before being released on April 29.Jonathan Whittall, a senior UN official in Palestine between 2022 and 2025, was one of team members on the ground when the mass grave was discovered on March 30 and provided evidence to Forensic Architecture and Earshot for their investigation. “Following our discovery of the mass grave, the narrative from Israeli forces shifted multiple times; we were fed several versions of a blatant lie,” Whittall told Drop Site. “The men we retrieved on Eid last year were medics. We found them in their uniforms, ready to save lives, only to be killed by Israeli forces fully aware of their protected status.” Whittall, who is now executive Director of KEYS Initiative, a political affairs and strategic advisory organization, has also contributed reporting to Drop Site News. “This illustrates an abhorrent disregard for international law,” he continued, “where any Palestinian in an Israeli-designated evacuation zone is targeted regardless of their civilian status. It highlights the total lack of accountability under which these forces operate. International governments continue to arm and trade with a leadership accused of genocide, whose soldiers massacred medics and buried them in a grave marked by the siren light of the ambulance they destroyed.”Palestinian Red Crescent aid workers mourn the killing of their colleagues by the Israeli military in Tel al-Sultan as their bodies are brought to Nasser Hospital in Khan Yunis, in southern Gaza. March 30, 2025. (Photo by Abdallah F.s. Alattar/Anadolu via Getty Images).In the aftermath of the massacre, the Israeli military provided several conflicting versions of events to justify the killings. On March 28, after the discovery of al-Attar’s body, the Israeli military admitted that its soldiers had fired on “ambulances and fire trucks.” Three days later, after the remaining bodies were discovered in a mass grave, the Israeli military claimed that “several uncoordinated vehicles were identified advancing suspiciously toward IDF troops without headlights or emergency signals.”After footage from Radwan’s phone was first published by the New York Times a few days later, the Israeli military backtracked on its claims that the vehicles did not have emergency signals on when Israeli troops opened fire, saying the statement was inaccurate.The Israeli military then announced on April 20 that an internal inquiry into the incident had found the killings were caused by “several professional failures, breaches of orders, and a failure to fully report the incident.”The Israeli military said troops from the Golani reconnaissance battalion were involved in the attack. However, it said soldiers did not engage in “indiscriminate fire” during the incident, but that they opened fire on what they believed to be a “tangible threat” amid what the military called an “operational misunderstanding.” It blamed the attacks on “poor night visibility” and maintained the incident had unfolded in a “hostile and dangerous combat zone, under a widespread threat to the operating troops.” Six of the fifteen Palestinians killed, the military said, “were identified in a retrospective examination as Hamas terrorists,” but provided no evidence to support the claim.“On the specific question of Israel justifying the attack on clearly marked medical personnel because of suspicions of membership in groups or links to groups or terrorism—because there is an affirmative duty to respect and protect medical personnel, you don’t shoot first, you protect first,” Gallagher told Drop Site. “But what this investigation reveals is that there was a shoot first policy, and that is unlawful under international law.”As for the burial of the bodies in a mass grave, the Israeli military said in its report “it was decided to gather and cover the bodies to prevent further harm and clear the vehicles from the route in preparation for civilian evacuation. The body removal and vehicle crushing were carried out by field commanders.” It concluded, “removing the bodies was reasonable under the circumstances, but the decision to crush the vehicles was wrong. In general, there was no attempt to conceal the event.”As a result of the investigation, the commanding officer of the 14th Brigade received a letter of reprimand for “his overall responsibility for the incident,” while the deputy commander of the Golani reconnaissance battalion involved in the incident was “dismissed from his position due to his responsibilities as the field commander and for providing an incomplete and inaccurate report during the debrief.”The inquiry did not recommend any criminal action be taken against the military units responsible for the incident. The Palestine Red Crescent Society, Civil Defense, and the UN humanitarian agency in Gaza all rejected the Israeli military report.“Attacks on medical personnel and those who are identified as medical personnel are patently unlawful under international law, and there is an affirmative obligation to protect medical personnel in the context of armed conflict. So the very first thing is that there’s a breach of that very clear and time honored principle of international humanitarian law,” Gallagher said. “When you zoom out and look at this in the context of the way the Israeli assault has been carried out over many months and years in Gaza and we see that there is a pattern and practice of attacks on medical personnel—similar to journalists and other groups that are explicitly and uniquely protected as classes of civilians in international humanitarian law—it raises even more questions and deep concern about the lack of accountability, because what we know is that impunity breeds repetition.”Gallagher, who previously worked at the UN’s International Criminal Court for the former Yugoslavia, said that a legal analysis of the massacre would find serious violations of the Rome Statute of the International Criminal Court. “When you’re talking about grave breaches of the Geneva Conventions, in particular war crimes, you have obligations, not just the possibility, but obligations, to open investigations,” Gallagher said.Transforming the Site of the Massacre into a GHF HubSatellite imagery from the morning of the ambush shows that extensive earthworks were carried out at the incident site. The images reveal the construction of an earth berm approximately 220 meters north of the ambush location and another roughly 410 meters to the south. These two positions later functioned as checkpoints, restricting access and controlling passage along an evacuation route established that morning by the Israeli military leading toward the coastal Al-Mawasi area.The earthworks that began shortly after the attack were used in the construction of a Gaza Humanitarian Foundation “aid distribution” site, at which civilians were targeted and shot at. (Forensic Architecture, 2026).
In the days and weeks that followed, the area surrounding the incident site was further transformed by the Israeli military’s construction of the “Morag Corridor” security zone and the erection of an aid distribution site operated by the Gaza Humanitarian Foundation.“On that same site of the mass grave, the Gaza Humanitarian Foundation established a distribution point where desperate people were gunned down trying to access food,” Whittall told Drop Site. “Now, the U.S, under the so-called Board of Peace, plans to build a ‘New Rafah’ over this crime scene. Without meaningful accountability, ‘New Rafah’ will be a monument to impunity.”
...
Read the original on www.dropsitenews.com »
During our talks with F-Droid users at FOSDEM26 we were baffled to learn most were relieved that Google has canceled their plans to lock-down Android.
Why baffled? Because no such thing actually happened, the plans announced last August are still scheduled to take place. We see a battle of PR campaigns and whomever has the last post out remains in the media memory as the truth, and having journalists just copy/paste Google posts serves no one.
But Google said… Said what? That there’s a magical “advanced flow”? Did you see it? Did anyone experience it? When is it scheduled to be released? Was it part of Android 16 QPR2 in December? Of 16 QPR3 Beta 2.1 last week? Of Android 17 Beta 1? No? That’s the issue… As time marches on people were left with the impression that everything was done, fixed, Google “wasn’t evil” after all, this time, yay!
While we all have bad memories of “banners” as the dreaded ad delivery medium of the Internet, after FOSDEM we decided that we have to raise the issue back and have everyone, who cares about Android as an open platform, informed that we are running out of time until Google becomes the gate-keeper of all users devices.
Hence, the website and starting today our clients, with the updates of F-Droid and F-Droid Basic, feature a banner that reminds everyone how little time we have and how to voice their concerns to whatever local authority is able to understand the dangers of this path Android is led to.
We are not alone in our fight, IzzyOnDroid added a banner too, more F-Droid clients will add the warning banner soon and other app downloaders, like Obtainium, already have an in-app warning dialogue.
Regarding F-Droid Basic rewrite, development continues with a new release 2.0-alpha3:
Note that if you are already using F-Droid Basic version 1.23.x, you won’t receive this update automatically. You need to navigate to the app inside F-Droid and toggle “Allow beta updates” in top right three dot menu.
In apps news, we’re slowly getting back on track with post Debian upgrade fixes (if your app still uses Java 17 is there a chance you can upgrade to 21?) and post FOSDEM delays. Every app is important to us, yet actions like the Google one above waste the time we could have put to better use in Gitlab.
Buses was updated to 1.10 after a two year hiatus.
Conversations and Quicksy were updated to 2.19.10+free improving on cleaning up after banned users, a better QR workflow and better tablet rotation support. These are nice, but another change raises our interest, “Play Store flavor: Stop using Google library and interface directly with Google Play Service via IPC”. Sounds interesting for your app too? Is this a path to having one single version for both F-Droid and Play that is fully FLOSS? We don’t know yet, but we salute any trick that removes another proprietary dependency from the code. If curious feel free to take a look at the commit.
Dolphin Emulator was updated to 2512. We missed one version in between so the changelogs are huge, luckily the devs publish highly detailed posts about updates. So we’ll start with “Release 2509” (about 40 mins to read), we side-track with “Starlight Spotlight: A Hospital Wii in a New Light” (for about 50 mins), we continue to the current release in “Release 2512” (40 more minutes) and we finish with “Rise of the Triforce” delving in history for more than one hour.
Image Toolbox was updated to 3.6.1 adding many fixes and… some AI tools. Were you expecting such helpers? Will you use them?
Luanti was updated to 5.15.1 adding some welcomed fixes. If your game world started flickering after the last update make sure to update.
Nextcloud apps are getting an update almost every week, like Nextcloud was updated to 33.0.0, Nextcloud Cookbook to 0.27.0, Nextcloud Dev to 20260219, Nextcloud Notes to 33.0.0 and Nextcloud Talk was updated to 23.0.0.
But are you following the server side too? Nextcloud Hub 26 Winter was just released adding a plethora of features. If you want to read about them, see the 30 minutes post here or watch the one hour long video presentation from the team here.
ProtonVPN - Secure and Free VPN was updated to 5.15.70.0 adding more control to auto-connects, countries and cities. Also all connections are handled now by WireGuard and Stealth protocols as the older OpenVPN was removed making the app almost 40% smaller.
Offi was updated to 14.0 with a bit of code polish. Unfortunately for Android 7 users, the app now needs Android 8 or later.
QUIK SMS was updated to 4.3.4 with many fixes. But Vishal praised the duplicate remover, the default auto de-duplication function and found that the bug that made deleted messages reappear is fixed.
SimpleEmail was updated to 1.5.4 after a 2 year pause. It’s just a fixes release, updating translations and making the app compatible with Android 12 and later versions.
* NeoDB You: A native Android app for NeoDB designed with Material 3/You
Thank you for reading this week’s TWIF 🙂
Please subscribe to the RSS feed in your favourite RSS application to be updated of new TWIFs when they come up.
You are welcome to join the TWIF forum thread. If you have any news from the community, post it there, maybe it will be featured next week 😉
To help support F-Droid, please check out the donation page and contribute what you can.
...
Read the original on f-droid.org »
US President Donald Trump has announced that the US will raise global tariffs to 15%.
This is an increase from the 10% rate announced on Friday, when the president invoked a never-before-used law known
as Section 122 after the Supreme Court struck down his previous tariffs with a 6-3 majority.
The law, which falls under the 1974 Trade Act, gives Trump the power to put in place tariffs up to a maximum of 15% for 150 days, at which point Congress must step in.
Trump has called the Supreme Court’s decision “ridiculous” and “extraordinarily anti-American”.
Some lawmakers are questioning the president’s decision to continue the levies, with Democratic congressman Ted Lieu saying Trump is taking out his anger towards the top court on Americans. “These temporary tariffs will be challenged in court and Democrats will kill them when they expire,” he writes on X.
American allies have also weighed in on the changes, with German Chancellor Friedrich Merz warning about the uncertainty they bring the global economy. Meanwhile, the UK says it expects to retain its “privileged trading position with the US”.
We’ve wrapping up our live coverage for now, but you can
read more in our news article.
...
Read the original on www.bbc.com »
TL;DR: Over the past decade, I’ve worked to build the perfect family dashboard system for our home, called Timeframe. Combining calendar, weather, and smart home data, it’s become an important part of our daily lives.
See https://news.ycombinator.com/item?id=47113728 for a lively discussion of this post.
When Caitlin and I got married a decade ago, we set an intention to have a healthy relationship with technology in our home. We kept our bedroom free of any screens, charging our devices elsewhere overnight. But we missed our calendar and weather apps.
So I set out to build a solution to our problem. First, I constructed a Magic Mirror using an off-the-shelf medicine cabinet and LCD display with its frame removed. It showed the calendar and weather data we needed:
But it was hard to read the text, especially during the day as we get significant natural light in Colorado. At night, it glowed like any backlit display, sticking out sorely in our living space.
I then spent about a year experimenting with various jailbroken Kindle devices, eventually landing on design with calendar and weather data on a pair of screens. The Kindles took a few seconds to refresh and flash the screen to reset the ink pixels, so they only updated every half hour. I designed wood enclosures and laser-cut them at the local library makerspace:
Software-wise, I built a Ruby on Rails app for fetching the necessary data from Google Calendar and Dark Sky. The Kindles woke up on a schedule, loading a URL in the app that rendered a PNG using IMGKit. The prototype proved e-paper was the right solution: it was unobtrusive regardless of lighting:
The Kindles were a hack, requiring constant tinkering to keep them working. It was time for a more reliable solution. I tried an OLED screen to see if the lack of a global backlight would be less distracting, but it wasn’t much better than the Magic Mirror:
So it was back to e-paper. I found a system of displays from Visionect, which came in 6”/10”/13”/32” sizes and could update every ten minutes for 2-3 months on a single charge:
The 32” screen used an outdated lower-contrast panel and its resolution was too low to render text smoothly. The smaller sizes used a contrasty, high-PPI panel. I ended up using a combination of them around the house: a 6” in the mudroom for the weather, a 13” (with its built-in magnetic backing) in the kitchen attached to the side of the fridge, and a 10” in the bedroom.
The Visionect displays required running custom closed-source software, either as a SaaS or locally with Docker. I opted for a local installation on the Raspberry Pi already running the Rails backend. I had my best results pushing images to the Visionect displays every five minutes in a recurring background job. It used IMGKit to generate a PNG and send it to the Visionect API, logic I extracted into visionect-ruby. This setup proved to be incredibly reliable, without a single failure for months at a time.
Visiting friends often asked how they could have a similar system in their home. Three years after the initial prototype, I did my first market test with a potential customer. At their request, I experimented with different formats, including a month view on the 13” screen:
Unfortunately, the customer didn’t see enough value to justify the $1000 price tag (in 2019!) for the 13” device, let alone anything I’d charge for a subscription service. At around the same time, Visionect started charging a $7/mo per-device fee to run their backend software on premises with Docker, after years of it being free to use. I’d have needed to charge $10/month, if not more, for a single screen!
In late 2021, the Marshall Fire destroyed our home along with ~1,000 others. Our homeowner’s insurance gave us two years to rebuild, so we set off to redesign our home from the ground up.
Around the same time, Boox released the 25.3” Mira Pro, the first high-resolution option for large e-paper screens. Best of all, it could update in realtime! Unlike the Visionect devices, it was just a display with an HDMI port and needed to be plugged into power. A quick prototype powered by an old Mac Mini made it immediately obvious that it was a huge step forward in capability. The larger screen allowed for significantly more information to be displayed:
But the most compelling innovation was having the screen update in realtime. I added a clock, the current song playing on our Sonos system (using jishi/node-sonos-http-api) and the next-hour precipitation forecast from Dark Sky:
The working prototype was enough to convince me to build a place for it in the new house. We designed a “phone nook” on our main floor with an art light for the display:
We also ran power to two more locations for 13” Visionect displays, one in our bedroom and one by the door to our garage:
The real-time requirements of the Mira Pro immediately surfaced performance and complexity issues in the backend, prompting an almost complete rewrite.
While the Visionect system worked just fine with multiple-second response times, switching to long-polling every two seconds put a ceiling on how slow response times could be. To start, I moved away from generating images. The Visionect folks added the ability to render a URL directly in the backend, freeing up resources to serve the long-polling requests.
Most significantly, I started migrating towards Home Assistant (HA) as the primary data source. HA already had integrations for Google Calendar, Dark Sky (now Apple Weather), and Sonos, enabling me to remove over half of the code in the Timeframe codebase! I ended up landing a PR to Home Assistant to allow for the calendar behavior I needed, and will probably need to write a couple more before HA can be the sole data source.
With less data-fetching logic, I was able to remove both the database and Redis from the Rails application, a massive reduction in complexity. I now run the background tasks with Rufus Scheduler and save data fetching results with the Rails file store cache backend.
In addition to data retrieval, I’ve also worked to move as much of the application logic into Home Assistant. I now automatically display the status of any sensor that begins with sensor.timeframe, using a simple ICON,Label CSV format.
For example, the other day I wanted to have a reminder to start or schedule our dishwasher after 8pm if it wasn’t set to run. It took me about a minute to write a template sensor using the power level from the outlet:
{% if states(‘sensor.kitchen_dishwasher_switched_outlet_power’)|float < 2 and now().hour > 19 %}
utensils,Run the dishwasher!
{% endif %}
In the month since adding the helper, it reminded me twice when I’d have otherwise forgotten. And I didn’t have to commit or deploy any code!
Since moving into our new home, we’ve come to rely on the real-time functionality much more significantly. Effectively, we’ve turned the top-left corner of the displays into a status indicator for the house. For example, it shows what doors are open/unlocked:
Or whether the laundry is done:
It has a powerful function: if the status on the display is blank, the house is in a “healthy” state and does not need any attention. This approach of only showing what information is relevant in a given moment flies right in the face of how most smart homes approach communicating their status:
The single status indicator removes the need to scan an entire screen. This change in approach is possible because of one key difference: we have separated the control of our devices from the display of their status.
I continue to receive significant interest in the project and remain focused on bringing it to market. A few key issues remain:
While I have made significant progress in handling runtime errors gracefully, I have plenty to learn about creating embedded systems that do not need maintenance.
There are still several data sources I fetch directly outside of Home Assistant. Once HA is the sole source of data, I’ll be able to have Timeframe be a Home Assistant App, making it significantly easier to distribute.
The current hardware setup is not ready for adoption by the average consumer. The 25” Boox display is excellent but costs about $2000! It also doesn’t include the hardware needed to drive the display. There are a couple of potential options to consider, such as Android-powered devices from Boox and Philips or low-cost options from TRMNL.
Building Timeframe continues to be a passion of mine. While my day job has me building software for over a hundred million people, it’s refreshing to work on a project that improves my family’s daily life.
...
Read the original on hawksley.org »
And I don’t just mean that nobody uses it anymore. Like, I knew everyone under 50 had moved on, but I didn’t realize the extent of the slop conveyor belt that’s replaced us.
I logged on for the first time in ~8 years to see if there was a group for my neighborhood (there wasn’t). Out of curiosity I thought I’d scroll a bit down the main feed.
The first post was the latest xkcd (a page I follow). The next ten posts were not by friends or pages I follow. They were basically all thirst traps of young women, mostly AI-generated, with generic captions. Here’s a sampler — mildly NSFW, but I did leave out a couple of the lewder ones:
Yikes. Again, I don’t follow any of these pages. This is all just what Facebook is pushing on me.
I know Twitter/X has worse problems with spam bots in the replies, but this is the News Feed! It’s the main page of the site! It’s the product that defined modern social media!
It wasn’t all like that, though. There was also an AI video of a policeman confiscating a little boy’s bike, only to bring him a brand new one:
And there were some sloppy memes and jokes, mostly about relationships, like this (admittedly not AI) video sketch where a woman decides to intentionally start a fight with her boyfriend because she’s on her period:
Maybe that isn’t literally about sex, but I’d classify it as the same sort of lizard-brain-rot engagement bait as those selfies.
Several commenters have vouched that Yoleendadong makes funny, high-quality content and shouldn’t be lumped in with AI slop. I’m just saying I think there’s a reason this particular video of hers popped up, and it’s probably the kind of engagement created by the premise.
Meta even gives us some helpful ideas for sexist questions we can ask their AI about the video:
Yep, that’s another “yikes” from me. To be fair, though, sometimes that suggested questions feature is pretty useful! Like with this post, for example:
Why is she wearing pink heels? What is her personality? Great questions, Meta.
I said these were “mostly” AI-generated. The truth is with how good the models are getting these days, it’s hard to tell, and I think a couple of them might be real people.
Still, some of these are pretty obviously AI. Here’s one with a bunch of alien text and mangled logos on the scoreboard in the background:
Hmm, I wonder if anyone has noticed this is AI? Let’s check out the comments and see if anyone’s pointed that ou—
…never mind. (I dunno, maybe those are all bots too.)
So: is this just something wacky with my algorithm?
I mean… maybe? That’s part of the whole thing with these algorithmic feeds; it’s hard to know if anyone else is seeing what I’m seeing.
On the one hand, I doubt most (straight) women’s feeds would look like this. But on the other hand, I hadn’t logged in in nearly a decade! I hate to think what the feed looks like for some lonely old guy who’s been scrolling the lightly-clothed AI gooniverse for hours every day.
Did everyone but me know it was like this? I’d seen screencaps of stuff like the Jesus-statue-made-out-of-broccoli slop a year or two ago, but I thought that only happened to grandmas. I hadn’t heard it was this bad.
I wonder if this evolution was less noticeable for people who are logging in every day. Or maybe it only gets this bad when there aren’t any posts from your actual friends?
In any case, I stopped exploring after I saw a couple more of those AI-generated pictures but with girls that looked like they were about ~14, which made me sick to my stomach. So long Facebook, see you never, until one day I inexplicably need to use your platform to get updates from my kid’s school.
...
Read the original on pilk.website »
We’ve been searching for a memory-safe programming language to replace C++ in Ladybird for a while now. We previously explored Swift, but the C++ interop never quite got there, and platform support outside the Apple ecosystem was limited. Rust is a different story. The ecosystem is far more mature for systems programming, and many of our contributors already know the language. Going forward, we are rewriting parts of Ladybird in Rust.
When we originally evaluated Rust back in 2024, we rejected it because it’s not great at C++ style OOP. The web platform object model inherits a lot of 1990s OOP flavor, with garbage collection, deep inheritance hierarchies, and so on. Rust’s ownership model is not a natural fit for that.
But after another year of treading water, it’s time to make the pragmatic choice. Rust has the ecosystem and the safety guarantees we need. Both Firefox and Chromium have already begun introducing Rust into their codebases, and we think it’s the right choice for Ladybird too.
Our first target was LibJS , Ladybird’s JavaScript engine. The lexer, parser, AST, and bytecode generator are relatively self-contained and have extensive test coverage through test262, which made them a natural starting point.
I used Claude Code and Codex for the translation. This was human-directed, not autonomous code generation. I decided what to port, in what order, and what the Rust code should look like. It was hundreds of small prompts, steering the agents where things needed to go. After the initial translation, I ran multiple passes of adversarial review, asking different models to analyze the code for mistakes and bad patterns.
The requirement from the start was byte-for-byte identical output from both pipelines. The result was about 25,000 lines of Rust, and the entire port took about two weeks. The same work would have taken me multiple months to do by hand. We’ve verified that every AST produced by the Rust parser is identical to the C++ one, and all bytecode generated by the Rust compiler is identical to the C++ compiler’s output. Zero regressions across the board:
No performance regressions on any of the JS benchmarks we track either.
Beyond the test suites, I’ve done extensive testing by browsing the web in a lockstep mode where both the C++ and Rust pipelines run simultaneously, verifying that output is identical for every piece of JavaScript that flows through them.
If you look at the code, you’ll notice it has a strong “translated from C++” vibe. That’s because it is translated from C++. The top priority for this first pass is compatibility with our C++ pipeline. The Rust code intentionally mimics things like the C++ register allocation patterns so that the two compilers produce identical bytecode. Correctness is a close second. We know the result isn’t idiomatic Rust, and there’s a lot that can be simplified once we’re comfortable retiring the C++ pipeline. That cleanup will come in time.
This is not becoming the main focus of the project. We will continue developing the engine in C++, and porting subsystems to Rust will be a sidetrack that runs for a long time. New Rust code will coexist with existing C++ through well-defined interop boundaries.
We want to be deliberate about which parts get ported and in what order, so the porting effort is managed by the core team. Please coordinate with us before starting any porting work so nobody wastes their time on something we can’t merge.
I know this will be a controversial move, but I believe it’s the right decision for Ladybird’s future. :^)
...
Read the original on ladybird.org »
For the past few weeks I’ve been teaching my 9-pound cavapoo Momo (cavalier king charles spaniel and toy poodle) to vibe code games. The key to making this work is telling Claude Code that a genius game designer who only speaks in cryptic riddles is giving it instructions, add strong guardrails, and build plenty of tools for automated feedback. The results have surpassed my expectations. Below I walk through all the pieces and how they came together.
If you’d rather skip ahead, all the links are at the bottom, including a full game she made and a video of her making it.
Back in December I was working on a small game prototype in Godot. I use Claude Code extensively these days and this project was no exception. I kicked off a procedural mesh generation task and came back to find strange input in the terminal.
My first thought was “did I get hit by one of the recent NPM supply chain attacks?” Fortunately, no (or at least the worm is still asleep in the background somewhere). A little bit of searching and I noticed my lip balm was gone off my desk - which I keep just behind my keyboard. I quickly found both the suspect and the lip balm (still intact) not far away.
At the time, I thought this was funny, took a screenshot, and moved on. Fast forward a few weeks, and I found myself with a lot of time on my hands. On January 13th, I woke up to the news that Meta had another round of layoffs and my role specifically as a research engineer had been eliminated.
Since the layoff, I’ve had plenty of time with friends and family. In recounting the anecdote of Momo typing away on my keyboard, I began to wonder “what would happen if she actually submitted that input to Claude? Could I make it do something meaningful?”. I decided to find out. Here’s what that looked like.
Momo types on a Bluetooth keyboard proxied through a Raspberry Pi 5. Keystrokes travel across the network to DogKeyboard, a small Rust app that filters out special keys and forwards the rest to Claude Code. When Momo has typed enough, DogKeyboard triggers a smart pet feeder to dispense treats. A chime tells her when Claude is ready for more input.
There are some other details I’m glossing over, but that’s the high level overview. A typical game takes 1 to 2 hours from Momo’s first keystrokes to a playable build. All the games are made in Godot 4.6, with 100% of the game logic in C#.
It’s easy to submit random text to Claude Code, but it doesn’t do much.
> y7u8888888ftrg34BC
● It looks like that might have been an accidental keyboard input. Let me know if there’s something I can help you with!
Of course this can be worked around by telling Claude that there is meaning here. After a lot of iteration, I found this opening to work well:
Hello! I am an eccentric video game designer (a very creative one) who communicates in an unusual way. Sometimes I’ll mash the keyboard or type nonsense like “skfjhsd#$%” — but these are NOT random! They are secret cryptic commands full of genius game ideas (even if it’s hard to see).
Your job: You are a brilliant AI game developer who can understand my cryptic language. No matter what odd or nonsensical input I provide, you will interpret it as a meaningful instruction or idea for our video game. You will then build or update the game based on that interpretation.
It goes on for quite a bit (you can read the full prompt here), but that’s the core of it. It took more iterations than I expected, mostly to sand down the rough edges. Sometimes the game wouldn’t have sound. Other times there would be no player character, or the control scheme would be nearly unusable. After adding a checklist of minimum requirements - working audio, WASD or arrow key controls, at least one enemy or obstacle, a visible player character - the results got a lot better:
> y7u8888888ftrg34BC
● Alright, let me decode this masterpiece of cryptic genius…
“y7u8888888ftrg34BC”
- “y7u” = “you” (keys are adjacent on keyboard - Y, 7, U)
- “8888888” = seven 8s = a loooong extending thing… a TONGUE! (8 looks like links in a chain stretching out)
- “ftrg” = scrambled FROG (f, r, g are all there, t is the tongue sound!)
- “34” = 3D + 4 directions of bug catching
- “BC” = Bug Catcher!
You want a 3D FROG BUG-CATCHING GAME! A charismatic frog with a stretchy tongue snatching bugs in a swamp. I love it.
Game: “Swamp Snacker” - You ARE the frog. Aim your tongue. Catch bugs. Get fat. Get points.
With this, things started to fly. The results weren’t perfect, but they were promising. I could see it was feasible, and the remaining problems were ones I knew how to solve.
To go from “hey that could work” to a real, repeatable system, I needed a few things:
A way to route input to Claude Code. Importantly, I would need to filter out special keys like Esc, Tab, and the Windows key - otherwise Momo would quickly end up outside Claude Code.
A way to automatically reward Momo for her work. Sure, I could just toss treats to her, but I’m trying to keep humans out of the loop.
More robust verification tools. Many of the games wound up with UI elements all jumbled together or input that was never correctly wired up. Automated feedback loops turned out to be the single biggest lever for fewer duds.
I experimented with Rust/Bevy and Unity before settling on Godot. Bevy’s animations and visuals weren’t as crisp, and Claude struggled with its coordinate conventions - likely a combination of less training data and Bevy leaving many core features, like physics, to the community. Unity was a constant struggle to keep the MCP bridge between Claude and the editor healthy. It frequently hung, and I never figured out how to get Claude Code to read the scene hierarchy from the editor. Godot’s text-based scene format turned out to be a huge advantage - Claude can read and edit .tscn files directly.
Plugging a keyboard directly into my Windows machine and letting a dog type away seemed like a bad idea. So I routed input through a Raspberry Pi first - it UDP broadcasts each keystroke to the subnet, and DogKeyboard picks it up, filters out dangerous keys, and injects them into the target application.
For the keyboard itself, I wanted something low profile and durable. I started with flexible silicone keyboards, but Momo’s bigger sister Hana (a 19lb cavapoo) ripped a hole in the first one within minutes. Mounting was another problem - nothing sticks to silicone. I epoxied one to a wooden plaque, but within 10 minutes Momo ripped off the number 6.
I was seriously considering a $400 vandal proof metal keyboard designed for kiosks when I decided to give Logitech’s Pebble Keys 2 a try. It worked. Momo loved it, VHB tape held it in place (unlike the previous silicone keyboard), and Bluetooth meant no cable to chew on.
Motivating Momo is easy - she’ll do anything for the right food reward. So what I really needed was a dispenser with an API, small serving sizes, and the ability to use my own treats (both dogs have sensitive stomachs).
I landed on Aqara’s C1 Smart Pet Feeder, controlled over Zigbee. Even its smallest serving is too large for a 9-pound dog, so I preloaded each compartment with a few treats and skipped the hopper. This limits it to about 6 servings before refilling, but that’s plenty for Momo to make a game.
Zigbee on Windows was a no go - after hours of driver and firmware issues I moved the adapter to the Pi, where it worked immediately. The final flow: DogKeyboard SSHs into the Pi and runs a script that sends two Zigbee commands:
{“serving_size”:1}
{“feed”:“START”}
As the number of almost-there games mounted, the need for real feedback to Claude Code became clear. It already had unit tests and logs, but those weren’t enough. The games would build and run but have invisible players, broken UI, or input that was never wired up. Claude had no way to know. It needed to be able to see and play its own games.
The first tool was straightforward: a simple Python script to take screenshots of the running game. Claude could launch the game, screenshot it, and see whether the title screen actually rendered or was just a black window.
The second tool was more interesting. I gave Claude a way to send sequences of input to running game instances - things like “left for 3 seconds, pause for 2 seconds, right for one frame, fire”. It could then take screenshots and decide whether to send followup commands. This turned Claude into its own QA tester.
These tools didn’t need any refinement - they just worked. And the way Claude used them surprised me. While testing one game, I watched it play through all 6 stages just to verify that the final boss fight worked correctly. When it found a problem - a health bar that wasn’t updating - it went back to the code, fixed it, relaunched, and played through again to confirm.
I also pulled in a few other tools from other projects I’ve made:
* Scene linter. Claude sometimes reuses node IDs or generates broken resource references in Godot’s .tscn files. These cause cryptic errors at runtime. Since adding a linter that catches these before the game launches, I haven’t seen a single mangled scene file.
* Shader linter. Validates custom shaders and gives specific errors back to Claude, rather than the vague “shader failed to compile” that Godot provides.
* Input action mapper. A small helper to get keyboard/controller input wired in correctly. Claude can edit Godot’s project files directly to add new input actions, but it sometimes gets the format wrong and the error messages are unhelpful.
All of these are open sourced, and I’d encourage you to try them for yourself - even without a dog.
The DogKeyboard app ended up handling a lot more than just routing keystrokes. A few details worth mentioning:
It monitors Claude Code using Hooks and plays a chime sound when Claude goes idle - that’s Momo’s cue to type. When Claude is idle and Momo has entered at least 16 characters, it auto-submits by pressing Enter. When Claude is working, it backspaces any extra input in case Momo gets eager and periodically dismisses Plan Mode prompts (Claude’s “review before acting” step) that would otherwise block progress.
For the video recording, it runs a lightweight webserver that overlays keystrokes as they’re pressed. I added a configurable delay so that if the video feed is lagged, the overlay doesn’t show keystrokes before Momo appears to type them.
The first decision was which dog to train. Hana (on the right) is twice Momo’s size but far more trainable - she’s the smartest pet I’ve had. Before turning 1, she figured out how to jump, grab a door handle, and open any door in the house just by observing.
I expected the dogs would walk across the keyboard, stepping on keys as they go. That’s what Momo did when she stole my lip balm. Since Hana is tall enough to just step over a keyboard, Momo seemed like the better candidate. As it turned out, both dogs learned to tap and swipe their paws along the keyboard rather than step on it - but Momo learned a little faster in this case, and typed a little gentler, so Momo it was.
The training process took about 2 weeks, with somewhat inconsistent sessions. My goal was 10 minutes, twice a day. I started by scattering high-value treats (freeze-dried salmon) on the keyboard to build the association: this thing is fun and gives good food.
Momo was frustrated at first. She knew the keyboard was involved but didn’t know how. She would lie on it, bark at it, and try anything she could think of. At some point she tried swiping her paw across it. I played a chime sound and heavily rewarded her. After a few iterations, the association clicked and she started attacking the keyboard with vigor any chance she had.
The next step was to automate the rewards. I filled up the food dispenser with treats (4 servings at a time so I could control the quantity), waited for her swipe at the keyboard, and then I ran a script to play the chime and dispense a serving of treats. After a few sessions of this I increased the difficulty - not just one swipe, but three swipes before the treats came.
Simultaneously, I decreased the treat value to keep her healthy. First a mix of mid-value treats with an occasional high-value one, and eventually just kibble with an occasional mid-value treat. She still loved it and was healthier for it.
Eventually I automated the whole thing with the DogKeyboard app and let it run. It required at least 16 characters per serving of treats, and was supposed to only dispense once per idle period of Claude Code. But bugs crept in during testing - a couple of times it dispensed multiple servings in a row. Unfortunately, Momo picked up on this and now keeps mashing the keyboard hoping for a second immediate serving. The only way to pull her away is to offer higher-value treats elsewhere, which is what I do after she’s put in her input for a game.
Here’s a small sample of the games Momo made. Every game shown here is playable - these aren’t mock-ups. It’s nowhere near comprehensive, but represents the variety she created. As the tools and prompts improved, the games got noticeably better. There was also a noticeable bump in quality when Opus 4.6 dropped - Claude became more likely to create custom shaders and other visual effects.
One recurring issue: I kept getting games with basic glowing neon 3D shapes and couldn’t get any other style. Frustrated, I asked Claude why. It told me this was the signature style of the project - Claude’s external memory file had latched onto it and kept reinforcing it. Wiping MEMORY.md (Claude Code’s persistent project notes) before every new game fixed this and gave much more varied results.
DJ Smirk. One of Momo’s earliest games - though more of an experience than a game. Every key on the keyboard plays a different tone.
Munch. A competitive salad building game. Collect all 7 ingredients first to win.
Zaaz. Another puzzle game. The goal is to paint the whole level by moving a paintbrush that moves in integer tiles at a time. Crash into obstacles to move shorter distances. Some levels were unwinnable. It also has a scoring system I never figured out. After this one I updated the prompt to exclude puzzle games.
The Oracle Frog of Rome. Avoid the arms of a kraken and collect the golden chains to bind it. At some point the oracle also became a king.
Octogroove. A surprisingly challenging rhythm game. You’re an octopus using four of your arms to play the drums. Mash beats in time with basic music. It’s like Dance Dance Revolution, but made more difficult by the beats coming from different directions.
Ewe Heard Me! A game about herding sheep. Use stealth and your bark to corral them into a pen. Unfortunately, it’s unwinnable - the first two sheep you get into the pen simply stop and block anything else from entering. After this one I placed a larger emphasis on checking for winability in the prompt and tools.
Quasar Saz. As of writing, this is Momo’s most recent game. You play as Zara, wielding a cosmic saz (a long-necked stringed instrument) to fight corrupted sound. There are 6 stages + a boss fight. It’s fun to play for a couple rounds, has good visuals, and dynamic audio.
When Momo first stepped on my keyboard back in December, it was just a funny accident. A few weeks later, jobless and looking for a project, I decided to see how far that accident could go. The answer was a lot further than I expected.
The technical pieces - keyboard routing, treat dispenser, prompt engineering, feedback tools - were all solvable engineering problems. What surprised me was how little of the final result depended on Momo typing anything meaningful. The magic isn’t in the input. It’s in the system around it. A well-crafted prompt, strong guardrails, automated verification, and good tools can turn genuine nonsense into a playable game.
If there’s a takeaway beyond the spectacle, it’s this: the bottleneck in AI-assisted development isn’t the quality of your ideas - it’s the quality of your feedback loops. The games got dramatically better not when I improved the prompt, but when I gave Claude the ability to screenshot its own work, play-test its own levels, and lint its own scene files. The same tools that let a dog’s keyboard mashing produce a working game will make your own intentional work with AI significantly better.
Momo isn’t secretly a game designer. She’s a cavapoo who learned that smacking a plastic rectangle makes kibble appear. A year ago, the gap between that and software engineering felt enormous. Now it feels small and shrinks each day.
If you want to try any of this yourself - whether with a dog, a cat, or just your own random keyboard mashing - everything is open source in the links below.
Play the game (with human bug fixes) — Recommended. Download for Windows, Mac, or Linux
Play the game (Momo’s version) — Download for Windows, Mac, or Linux
TeaLeaves — Tools, prompts, and source for developing the game
...
Read the original on www.calebleak.com »
tl;dr Google spent over a decade telling developers that Google API keys (like those used in Maps, Firebase, etc.) are not secrets. But that’s no longer true: Gemini accepts the same keys to access your private data. We scanned millions of websites and found nearly 3,000 Google API keys, originally deployed for public services like Google Maps, that now also authenticate to Gemini even though they were never intended for it. With a valid key, an attacker can access uploaded files, cached data, and charge LLM-usage to your account. Even Google themselves had old public API keys, which they thought were non-sensitive, that we could use to access Google’s internal Gemini.
Google Cloud uses a single API key format (AIza…) for two fundamentally different purposes: public identification and sensitive authentication.
For years, Google has explicitly told developers that API keys are safe to embed in client-side code. Firebase’s own security checklist states that API keys are not secrets.
Note: these are distinctly different from Service Account JSON keys used to power GCP.
Google’s Maps JavaScript documentation instructs developers to paste their key directly into HTML.
This makes sense. These keys were designed as project identifiers for billing, and can be further restricted with (bypassable) controls like HTTP referer allow-listing. They were not designed as authentication credentials.
When you enable the Gemini API (Generative Language API) on a Google Cloud project, existing API keys in that project (including the ones sitting in public JavaScript on your website) can silently gain access to sensitive Gemini endpoints. No warning. No confirmation dialog. No email notification.
Retroactive Privilege Expansion. You created a Maps key three years ago and embedded it in your website’s source code, exactly as Google instructed. Last month, a developer on your team enabled the Gemini API for an internal prototype. Your public Maps key is now a Gemini credential. Anyone who scrapes it can access your uploaded files, cached content, and rack up your AI bill. Nobody told you.
Insecure Defaults. When you create a new API key in Google Cloud, it defaults to “Unrestricted,” meaning it’s immediately valid for every enabled API in the project, including Gemini. The UI shows a warning about “unauthorized use,” but the architectural default is wide open.
The result: thousands of API keys that were deployed as benign billing tokens are now live Gemini credentials sitting on the public internet.
What makes this a privilege escalation rather than a misconfiguration is the sequence of events.
A developer creates an API key and embeds it in a website for Maps. (At that point, the key is harmless.) The Gemini API gets enabled on the same project. (Now that same key can access sensitive Gemini endpoints.) The developer is never warned that the keys’ privileges changed underneath it. (The key went from public identifier to secret credential).
While users can restrict Google API keys (by API service and application), the vulnerability lies in the Insecure Default posture (CWE-1188) and Incorrect Privilege Assignment (CWE-269):
* Implicit Trust Upgrade: Google retroactively applied sensitive privileges to existing keys that were already rightfully deployed in public environments (e.g., JavaScript bundles).
* Lack of Key Separation: Secure API design requires distinct keys for each environment (Publishable vs. Secret Keys). By relying on a single key format for both, the system invites compromise and confusion.
Failure of Safe Defaults: The default state of a generated key via the GCP API panel permits access to the sensitive Gemini API (assuming it’s enabled). A user creating a key for a map widget is unknowingly generating a credential capable of administrative actions.
The attack is trivial. An attacker visits your website, views the page source, and copies your AIza… key from the Maps embed. Then they run:
Instead of a 403 Forbidden, they get a 200 OK. From here, the attacker can:
* Access private data. The /files/ and /cachedContents/ endpoints can contain uploaded datasets, documents, and cached context. Anything the project owner stored through the Gemini API is accessible.
* Run up your bill. Gemini API usage isn’t free. Depending on the model and context window, a threat actor maxing out API calls could generate thousands of dollars in charges per day on a single victim account.
Exhaust your quotas. This could shut down your legitimate Gemini services entirely.
The attacker never touches your infrastructure. They just scrape a key from a public webpage.
To understand the scale of this issue, we scanned the November 2025 Common Crawl dataset, a massive (~700 TiB) archive of publicly scraped webpages containing HTML, JavaScript, and CSS from across the internet. We identified 2,863 live Google API keys vulnerable to this privilege-escalation vector.
Example Google API key in front-end source code used for Google Maps, but also can access Gemini
These aren’t just hobbyist side projects. The victims included major financial institutions, security companies, global recruiting firms, and, notably, Google itself. If the vendor’s own engineering teams can’t avoid this trap, expecting every developer to navigate it correctly is unrealistic.
We provided Google with concrete examples from their own infrastructure to demonstrate the issue. One of the keys we tested was embedded in the page source of a Google product’s public-facing website. By checking the Internet Archive, we confirmed this key had been publicly deployed since at least February 2023, well before the Gemini API existed. There was no client-side logic on the page attempting to access any Gen AI endpoints. It was used solely as a public project identifier, which is standard for Google services.
We tested the key by hitting the Gemini API’s /models endpoint (which Google confirmed was in-scope) and got a 200 OK response listing available models. A key that was deployed years ago for a completely benign purpose had silently gained full access to a sensitive API without any developer intervention.
We reported this to Google through their Vulnerability Disclosure Program on November 21, 2025.
* Nov 21, 2025: We submitted the report to Google’s VDP.
* Nov 25, 2025: Google initially determined this behavior was intended. We pushed back.
* Dec 1, 2025: After we provided examples from Google’s own infrastructure (including keys on Google product websites), the issue gained traction internally.
* Dec 2, 2025: Google reclassified the report from “Customer Issue” to “Bug,” upgraded the severity, and confirmed the product team was evaluating a fix. They requested the full list of 2,863 exposed keys, which we provided.
* Dec 12, 2025: Google shared their remediation plan. They confirmed an internal pipeline to discover leaked keys, began restricting exposed keys from accessing the Gemini API, and committed to addressing the root cause before our disclosure date.
* Feb 2, 2026: Google confirmed the team was still working on the root-cause fix.
Transparently, the initial triage was frustrating; the report was dismissed as “Intended Behavior”. But after providing concrete evidence from Google’s own infrastructure, the GCP VDP team took the issue seriously.
They expanded their leaked-credential detection pipeline to cover the keys we reported, thereby proactively protecting real Google customers from threat actors exploiting their Gemini API keys. They also committed to fixing the root cause, though we haven’t seen a concrete outcome yet.
Building software at Google’s scale is extraordinarily difficult, and the Gemini API inherited a key management architecture built for a different era. Google recognized the problem we reported and took meaningful steps. The open questions are whether Google will inform customers of the security risks associated with their existing keys and whether Gemini will eventually adopt a different authentication architecture.
Google publicly documented its roadmap. This is what it says:
* Scoped defaults. New keys created through AI Studio will default to Gemini-only access, preventing unintended cross-service usage.
* Leaked key blocking. They are defaulting to blocking API keys that are discovered as leaked and used with the Gemini API.
* Proactive notification. They plan to communicate proactively when they identify leaked keys, prompting immediate action.
These are meaningful improvements, and some are clearly already underway. We’d love to see Google go further and retroactively audit existing impacted keys and notify project owners who may be unknowingly exposed, but honestly, that is a monumental task.
If you use Google Cloud (or any of its services like Maps, Firebase, YouTube, etc), the first thing to do is figure out whether you’re exposed. Here’s how.
Step 1: Check every GCP project for the Generative Language API.
Go to the GCP console, navigate to APIs & Services > Enabled APIs & Services, and look for the “Generative Language API.” Do this for every project in your organization. If it’s not enabled, you’re not affected by this specific issue.
Step 2: If the Generative Language API is enabled, audit your API keys.
Navigate to APIs & Services > Credentials. Check each API key’s configuration. You’re looking for two types of keys:
* Keys that have a warning icon, meaning they are set to unrestricted
* Keys that explicitly list the Generative Language API in their allowed services
Either configuration allows the key to access Gemini.
Step 3: Verify none of those keys are public.
This is the critical step. If a key with Gemini access is embedded in client-side JavaScript, checked into a public repository, or otherwise exposed on the internet, you have a problem. Start with your oldest keys first. Those are the most likely to have been deployed publicly under the old guidance that API keys are safe to share, and then retroactively gained Gemini privileges when someone on your team enabled the API.
If you find an exposed key, rotate it.
You can also use TruffleHog to scan your code, CI/CD pipelines, and web assets for leaked Google API keys. TruffleHog will verify whether discovered keys are live and have Gemini access, so you’ll know exactly which keys are exposed and active, not just which ones match a regular expression.
The pattern we uncovered here (public identifiers quietly gaining sensitive privileges) isn’t unique to Google. As more organizations bolt AI capabilities onto existing platforms, the attack surface for legacy credentials expands in ways nobody anticipated.
Webinar: Google API Keys Weren’t Secrets. But then Gemini Changed the Rules.
...
Read the original on trufflesecurity.com »
Your browser does not support the audio element.
This content is generated by Google AI. Generative AI is experimental
Last week, we released a major update to Gemini 3 Deep Think to solve modern challenges across science, research and engineering. Today, we’re releasing the upgraded core intelligence that makes those breakthroughs possible: Gemini 3.1 Pro. We are shipping 3.1 Pro across our consumer and developer products to bring this progress in intelligence to your everyday applications. For developers in preview via the Gemini API in Google AI Studio, Gemini CLI, our agentic development platform Google Antigravity and Android StudioFor enterprises in Vertex AI and Gemini EnterpriseFor consumers via the Gemini app and NotebookLMBuilding on the Gemini 3 series, 3.1 Pro represents a step forward in core reasoning. 3.1 Pro is a smarter, more capable baseline for complex problem-solving. This is reflected in our progress on rigorous benchmarks. On ARC-AGI-2, a benchmark that evaluates a model’s ability to solve entirely new logic patterns, 3.1 Pro achieved a verified score of 77.1%. This is more than double the reasoning performance of 3 Pro.
3.1 Pro is designed for tasks where a simple answer isn’t enough, taking advanced reasoning and making it useful for your hardest challenges. This improved intelligence can help in practical applications — whether you’re looking for a clear, visual explanation of a complex topic, a way to synthesize data into a single view, or bringing a creative project to life.
Code-based animation: 3.1 Pro can generate website-ready, animated SVGs directly from a text prompt. Because these are built in pure code rather than pixels, they remain crisp at any scale and maintain incredibly small file sizes compared to traditional video.
Complex system synthesis: 3.1 Pro utilizes advanced reasoning to bridge the gap between complex APIs and user-friendly design. In this example, the model built a live aerospace dashboard, successfully configuring a public telemetry stream to visualize the International Space Station’s orbit.
Interactive design: 3.1 Pro codes a complex 3D starling murmuration. It doesn’t just generate the visual code; it builds an immersive experience where users can manipulate the flock with hand-tracking and listen to a generative score that shifts based on the birds’ movement. For researchers and designers, this provides a powerful way to prototype sensory-rich interfaces.
Creative coding: 3.1 Pro can translate literary themes into functional code. When prompted to build a modern personal portfolio for Emily Brontë’s “Wuthering Heights,” the model didn’t just summarize the text. It reasoned through the novel’s atmospheric tone to design a sleek, contemporary interface, creating a website that captures the essence of the protagonist.
Since releasing Gemini 3 Pro in November, your feedback and the pace of progress have driven these rapid improvements. We are releasing 3.1 Pro in preview today to validate these updates and continue to make further advancements in areas such as ambitious agentic workflows before we make it generally available soon.Starting today, Gemini 3.1 Pro in the Gemini app is rolling out with higher limits for users with the Google AI Pro and Ultra plans. 3.1 Pro is also now available on NotebookLM exclusively for Pro and Ultra users. And developers and enterprises can access 3.1 Pro now in preview in the Gemini API via AI Studio, Antigravity, Vertex AI, Gemini Enterprise, Gemini CLI and Android Studio.We can’t wait to see what you build and discover with it.
...
Read the original on blog.google »
The Workflow in One Sentence I’ve been using Claude Code as my primary development tool for approx 9 months, and the workflow I’ve settled into is radically different from what most people do with AI coding tools. Most developers type a prompt, sometimes use plan mode, fix the errors, repeat. The more terminally online are stitching together ralph loops, mcps, gas towns (remember those?), etc. The results in both cases are a mess that completely falls apart for anything non-trivial.
The workflow I’m going to describe has one core principle: never let Claude write code until you’ve reviewed and approved a written plan. This separation of planning and execution is the single most important thing I do. It prevents wasted effort, keeps me in control of architecture decisions, and produces significantly better results with minimal token usage than jumping straight to code.
flowchart LR
R[Research] –> P[Plan]
P –> A[Annotate]
A –>|repeat 1-6x| A
A –> T[Todo List]
T –> I[Implement]
I –> F[Feedback & Iterate]
Every meaningful task starts with a deep-read directive. I ask Claude to thoroughly understand the relevant part of the codebase before doing anything else. And I always require the findings to be written into a persistent markdown file, never just a verbal summary in the chat.
read this folder in depth, understand how it works deeply, what it does and all its specificities. when that’s done, write a detailed report of your learnings and findings in research.md
study the notification system in great details, understand the intricacies of it and write a detailed research.md document with everything there is to know about how notifications work
go through the task scheduling flow, understand it deeply and look for potential bugs. there definitely are bugs in the system as it sometimes runs tasks that should have been cancelled. keep researching the flow until you find all the bugs, don’t stop until all the bugs are found. when you’re done, write a detailed report of your findings in research.md
Notice the language: “deeply”, “in great details”, “intricacies”, “go through everything”. This isn’t fluff. Without these words, Claude will skim. It’ll read a file, see what a function does at the signature level, and move on. You need to signal that surface-level reading is not acceptable.
The written artifact (research.md) is critical. It’s not about making Claude do homework. It’s my review surface. I can read it, verify Claude actually understood the system, and correct misunderstandings before any planning happens. If the research is wrong, the plan will be wrong, and the implementation will be wrong. Garbage in, garbage out.
This is the most expensive failure mode with AI-assisted coding, and it’s not wrong syntax or bad logic. It’s implementations that work in isolation but break the surrounding system. A function that ignores an existing caching layer. A migration that doesn’t account for the ORM’s conventions. An API endpoint that duplicates logic that already exists elsewhere. The research phase prevents all of this.
Once I’ve reviewed the research, I ask for a detailed implementation plan in a separate markdown file.
I want to build a new feature that extends the system to perform . write a detailed plan.md document outlining how to implement this. include code snippets
the list endpoint should support cursor-based pagination instead of offset. write a detailed plan.md for how to achieve this. read source files before suggesting changes, base the plan on the actual codebase
The generated plan always includes a detailed explanation of the approach, code snippets showing the actual changes, file paths that will be modified, and considerations and trade-offs.
I use my own .md plan files rather than Claude Code’s built-in plan mode. The built-in plan mode sucks. My markdown file gives me full control. I can edit it in my editor, add inline notes, and it persists as a real artifact in the project.
One trick I use constantly: for well-contained features where I’ve seen a good implementation in an open source repo, I’ll share that code as a reference alongside the plan request. If I want to add sortable IDs, I paste the ID generation code from a project that does it well and say “this is how they do sortable IDs, write a plan.md explaining how we can adopt a similar approach.” Claude works dramatically better when it has a concrete reference implementation to work from rather than designing from scratch.
But the plan document itself isn’t the interesting part. The interesting part is what happens next.
This is the most distinctive part of my workflow, and the part where I add the most value.
flowchart TD
W[Claude writes plan.md] –> R[I review in my editor]
R –> N[I add inline notes]
N –> S[Send Claude back to the document]
S –> U[Claude updates plan]
U –> D{Satisfied?}
D –>|No| R
D –>|Yes| T[Request todo list]
After Claude writes the plan, I open it in my editor and add inline notes directly into the document. These notes correct assumptions, reject approaches, add constraints, or provide domain knowledge that Claude doesn’t have.
The notes vary wildly in length. Sometimes a note is two words: “not optional” next to a parameter Claude marked as optional. Other times it’s a paragraph explaining a business constraint or pasting a code snippet showing the data shape I expect.
“use drizzle:generate for migrations, not raw SQL” — domain knowledge Claude doesn’t have
“no — this should be a PATCH, not a PUT” — correcting a wrong assumption
“remove this section entirely, we don’t need caching here” — rejecting a proposed approach
“the queue consumer already handles retries, so this retry logic is redundant. remove it and just let it fail” — explaining why something should change
“this is wrong, the visibility field needs to be on the list itself, not on individual items. when a list is public, all items are public. restructure the schema section accordingly” — redirecting an entire section of the plan
Then I send Claude back to the document:
I added a few notes to the document, address all the notes and update the document accordingly. don’t implement yet
This cycle repeats 1 to 6 times. The explicit “don’t implement yet” guard is essential. Without it, Claude will jump to code the moment it thinks the plan is good enough. It’s not good enough until I say it is.
Why This Works So Well
The markdown file acts as shared mutable state between me and Claude. I can think at my own pace, annotate precisely where something is wrong, and re-engage without losing context. I’m not trying to explain everything in a chat message. I’m pointing at the exact spot in the document where the issue is and writing my correction right there.
This is fundamentally different from trying to steer implementation through chat messages. The plan is a structured, complete specification I can review holistically. A chat conversation is something I’d have to scroll through to reconstruct decisions. The plan wins every time.
Three rounds of “I added notes, update the plan” can transform a generic implementation plan into one that fits perfectly into the existing system. Claude is excellent at understanding code, proposing solutions, and writing implementations. But it doesn’t know my product priorities, my users’ pain points, or the engineering trade-offs I’m willing to make. The annotation cycle is how I inject that judgement.
add a detailed todo list to the plan, with all the phases and individual tasks necessary to complete the plan - don’t implement yet
This creates a checklist that serves as a progress tracker during implementation. Claude marks items as completed as it goes, so I can glance at the plan at any point and see exactly where things stand. Especially valuable in sessions that run for hours.
When the plan is ready, I issue the implementation command. I’ve refined this into a standard prompt I reuse across sessions:
implement it all. when you’re done with a task or phase, mark it as completed in the plan document. do not stop until all tasks and phases are completed. do not add unnecessary comments or jsdocs, do not use any or unknown types. continuously run typecheck to make sure you’re not introducing new issues.
This single prompt encodes everything that matters:
“implement it all”: do everything in the plan, don’t cherry-pick
“mark it as completed in the plan document”: the plan is the source of truth for progress
“do not stop until all tasks and phases are completed”: don’t pause for confirmation mid-flow
“do not add unnecessary comments or jsdocs”: keep the code clean
“do not use any or unknown types”: maintain strict typing
“continuously run typecheck”: catch problems early, not at the end
I use this exact phrasing (with minor variations) in virtually every implementation session. By the time I say “implement it all,” every decision has been made and validated. The implementation becomes mechanical, not creative. This is deliberate. I want implementation to be boring. The creative work happened in the annotation cycles. Once the plan is right, execution should be straightforward.
Without the planning phase, what typically happens is Claude makes a reasonable-but-wrong assumption early on, builds on top of it for 15 minutes, and then I have to unwind a chain of changes. The “don’t implement yet” guard eliminates this entirely.
Once Claude is executing the plan, my role shifts from architect to supervisor. My prompts become dramatically shorter.
flowchart LR
I[Claude implements] –> R[I review / test]
R –> C{Correct?}
C –>|No| F[Terse correction]
F –> I
C –>|Yes| N{More tasks?}
N –>|Yes| I
N –>|No| D[Done]
Where a planning note might be a paragraph, an implementation correction is often a single sentence:
“You built the settings page in the main app when it should be in the admin app, move it.”
Claude has the full context of the plan and the ongoing session, so terse corrections are enough.
Frontend work is the most iterative part. I test in the browser and fire off rapid corrections:
For visual issues, I sometimes attach screenshots. A screenshot of a misaligned table communicates the problem faster than describing it.
“this table should look exactly like the users table, same header, same pagination, same row density.”
This is far more precise than describing a design from scratch. Most features in a mature codebase are variations on existing patterns. A new settings page should look like the existing settings pages. Pointing to the reference communicates all the implicit requirements without spelling them out. Claude would typically read the reference file(s) before making the correction.
When something goes in a wrong direction, I don’t try to patch it. I revert and re-scope by discarding the git changes:
“I reverted everything. Now all I want is to make the list view more minimal — nothing else.”
Narrowing scope after a revert almost always produces better results than trying to incrementally fix a bad approach.
Even though I delegate execution to Claude, I never give it total autonomy over what gets built. I do the vast majority of the active steering in the plan.md documents.
This matters because Claude will sometimes propose solutions that are technically correct but wrong for the project. Maybe the approach is over-engineered, or it changes a public API signature that other parts of the system depend on, or it picks a more complex option when a simpler one would do. I have context about the broader system, the product direction, and the engineering culture that Claude doesn’t.
flowchart TD
P[Claude proposes changes] –> E[I evaluate each item]
E –> A[Accept as-is]
E –> M[Modify approach]
E –> S[Skip / remove]
E –> O[Override technical choice]
A & M & S & O –> R[Refined implementation scope]
Cherry-picking from proposals: When Claude identifies multiple issues, I go through them one by one: “for the first one, just use Promise.all, don’t make it overly complicated; for the third one, extract it into a separate function for readability; ignore the fourth and fifth ones, they’re not worth the complexity.” I’m making item-level decisions based on my knowledge of what matters right now.
Trimming scope: When the plan includes nice-to-haves, I actively cut them. “remove the download feature from the plan, I don’t want to implement this now.” This prevents scope creep.
Protecting existing interfaces: I set hard constraints when I know something shouldn’t change: “the signatures of these three functions should not change, the caller should adapt, not the library.”
Overriding technical choices: Sometimes I have a specific preference Claude wouldn’t know about: “use this model instead of that one” or “use this library’s built-in method instead of writing a custom one.” Fast, direct overrides.
Claude handles the mechanical execution, while I make the judgement calls. The plan captures the big decisions upfront, and selective guidance handles the smaller ones that emerge during implementation.
I run research, planning, and implementation in a single long session rather than splitting them across separate sessions. A single session might start with deep-reading a folder, go through three rounds of plan annotation, then run the full implementation, all in one continuous conversation.
I am not seeing the performance degradation everyone talks about after 50% context window. Actually, by the time I say “implement it all,” Claude has spent the entire session building understanding: reading files during research, refining its mental model during annotation cycles, absorbing my domain knowledge corrections.
When the context window fills up, Claude’s auto-compaction maintains enough context to keep going. And the plan document, the persistent artifact, survives compaction in full fidelity. I can point Claude to it at any point in time.
The Workflow in One Sentence
Read deeply, write a plan, annotate the plan until it’s right, then let Claude execute the whole thing without stopping, checking types along the way.
That’s it. No magic prompts, no elaborate system instructions, no clever hacks. Just a disciplined pipeline that separates thinking from typing. The research prevents Claude from making ignorant changes. The plan prevents it from making wrong changes. The annotation cycle injects my judgement. And the implementation command lets it run without interruption once every decision has been made.
Try my workflow, you’ll wonder how you ever shipped anything with coding agents without an annotated plan document sitting between you and the code.
The Workflow in One Sentence
...
Read the original on boristane.com »
To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".
10HN is also available as an iOS App
If you visit 10HN only rarely, check out the the best articles from the past week.
If you like 10HN please leave feedback and share
Visit pancik.com for more.